link::create directly (no REST API Gateway), subscribes to the live click stream to update a
counter, and registers a user::confirm_destructive_op function the server calls when a delete
needs a human’s go-ahead.
Add the workers
A browser worker connects throughiii-worker-manager’s RBAC-gated listener, separate from the
trusted port your local workers use. We’ll encapsulate the authentication logic in an auth worker
to gate those connections, so scaffold it the same way you scaffolded link in Chapter 1:
If you’re continuing from the previous channel you might still be in the
test-channels folder.
Make sure to cd ..!Run two listeners
The engine’s built-in port at49134 is the trusted listener; local workers (link worker,
analytics worker) connect there. The browser must not. Add two iii-worker-manager entries: the
trusted one (local workers keep using it) and an RBAC-gated one on 3110 for browsers:
config.yaml
expose_functions is an allowlist of which functions a browser session can call. auth_function_id
names a function iii-worker-manager invokes on every connection to admit or reject it; you write
that next.
Gate connections with an auth function
Theauth worker owns connection gating, so the link worker stays focused on links.
auth::browser runs once per browser connection: it receives the request’s headers,
query_params, and ip_address, and returns the session’s permissions (allow/deny additions,
arbitrary context). Throw to reject. Replace the generated auth/src/index.ts:
auth/src/index.ts
Add a server-initiated delete
First give thelink worker a link::delete that removes a link from both the database and the
iii-state cache. Add it to link/src/index.ts:
link/src/index.ts
worker.trigger of a browser-registered function is the same primitive you’ve used
between server workers, in reverse. This lets us reverse the typical pattern: instead of the browser asking the server for
confirmation, the server asks the browser.
Other implementations would achieve this with SSE but here it works like normal synchronous
code.
link/src/index.ts
Scaffold the frontend
Initialize a Vite project
Create a Vite + React + TypeScript app underlinkly/frontend/:
Vite may ask you to “Install with npm and start now”, answer no here as we first need to install
iii-browser-sdkSetup a client-side worker
Connect the SDK to our iii instance infrontend/src/iii.ts. Note how we’re using a different URL
format and port, this goes back to the setup we did with iii-worker-manager and RBAC.
src/iii.ts
Create the application
We’ll buildsrc/App.tsx in pieces. You can replace the template’s src/App.tsx with the code
samples below.
Add imports
First the imports and types:Click is one row from the clicks table, and StreamEvent is the
wrapper iii-stream delivers to subscribers.
src/App.tsx
Add client-side state
Open the component and declare its state: the form fields, the newly created link, and the live click counter.src/App.tsx
Subscribe to clicks
Subscribe to the clicks stream we setup in Chapter 5. The useEffect registers a function the
browser exposes (ui::on_click) and a stream trigger that routes every new row to it; the cleanup
unregisters both on unmount:
src/App.tsx
Create a function
Register the function the server calls back when it needs human confirmation. It shows a native prompt and returns the user’s decision:This function registers and runs the exact same as other functions did in previous chapters.
Except for managing auth and permissions there is no functional difference between client side and
server side.
src/App.tsx
Create links directly, no gateways
Submit the form by callinglink::create directly.
There is no
fetch or REST API in the way here, the client worker in the browser works the exact
same as every other worker.src/App.tsx
Create the UI
Finally, the UI: a link shortener form, the last-created link, and the live streaming click counter.src/App.tsx
See it work
Start the UI:Shorten a link, then see the visits streamed in realtime
Shorten a link from the form, then visithttp://localhost:3111/s/<code> a few times. You’ll see
the “Live clicks” counter goes up in real time.
Request user confirmation directly from the backend
Then run a function in the browser via iii by runnning:Conclusion
The client is a worker that is exactly the same as every other worker. We connected it through an RBAC-gated listener (viaiii-worker-manager) that uses an auth function to admit it because the
browser isn’t trusted like our other workers. However any other worker can be gated this same way.
Once everything is set up our client calls server functions directly, subscribes to streams for live
updates, and registers functions the server calls back, all on the same iii bus as the rest of
Linkly.