mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-25 08:09:15 +00:00
docs: complex teams recipe (PUT-1986)
Showing which teammates have a file open, built from a folder shared with the team plus `puter.events` watching it. There is no presence API, so the recipe is about composing the two. One use case per the recipe guidance. Uses the masked path from `listShared()` so members address the owner's folder, keys presence files on `uuid` rather than username, and re-reads the folder on each event so a missed delivery cannot leave the list wrong.
This commit is contained in:
1 parent
a1eca46a80
commit
20fe5e457e
1 file changed
+131
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: See Who Has a File Open
|
||||
description: "Learn how to show which teammates are viewing a shared file with Puter.js, so people on a team don't overwrite each other's work."
|
||||
tags: [teams, fs, events]
|
||||
order: 63
|
||||
---
|
||||
|
||||
<div class="info">The Teams and Events APIs are in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
When a team shares a folder, two people can open the same file without knowing
|
||||
about each other, and the second one to save wins. Showing who else is in a file
|
||||
is usually enough to stop that happening.
|
||||
|
||||
There's no presence API to call. You build it out of two things you already
|
||||
have: a folder the team can write to, and
|
||||
[`puter.events`](/Events/) to watch it. Each person writes a small file saying
|
||||
they're here, and everyone watching the folder sees it appear.
|
||||
|
||||
To share the folder with the team in the first place, see
|
||||
[Share a File with a Team](/recipes/share-a-file-with-a-team/).
|
||||
|
||||
## Find the shared folder
|
||||
|
||||
The folder lives in one account and is shared with the team, so everyone else
|
||||
reaches it through [`puter.fs.listShared()`](/FS/listShared/). Shared items come
|
||||
back at a masked path like `/ada/4f2c…/team-docs`, which you can pass to any
|
||||
`puter.fs` method:
|
||||
|
||||
```js
|
||||
const { items } = await puter.fs.listShared();
|
||||
const folder = items.find(i => i.isDir && i.name === 'team-docs');
|
||||
|
||||
const root = folder ? folder.path : '~/team-docs'; // the owner uses their own
|
||||
```
|
||||
|
||||
The owner of the folder won't see it in `listShared()` — that only lists things
|
||||
shared *with* you — so fall back to their own path.
|
||||
|
||||
## Say you're here
|
||||
|
||||
When someone opens a file, write a small presence file into a `.presence`
|
||||
folder. Name it after the file and the person, so one write per person per file:
|
||||
|
||||
```js
|
||||
const me = await puter.auth.getUser();
|
||||
|
||||
async function enter(fileId) {
|
||||
await puter.fs.write(
|
||||
`${root}/.presence/${fileId}.${me.uuid}.json`,
|
||||
JSON.stringify({ username: me.username, at: Date.now() }),
|
||||
{ createMissingParents: true },
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Use `uuid` in the name rather than `username`. A user can change their username,
|
||||
and you'd end up with two files for one person.
|
||||
|
||||
Delete it when they leave:
|
||||
|
||||
```js
|
||||
async function leave(fileId) {
|
||||
await puter.fs.delete(`${root}/.presence/${fileId}.${me.uuid}.json`);
|
||||
}
|
||||
```
|
||||
|
||||
## Watch who else is there
|
||||
|
||||
Subscribe to the `.presence` folder with
|
||||
[`puter.events.onLocal()`](/Events/onLocal/). Everyone with access to the shared
|
||||
folder gets these events, so each person sees the others arrive and leave:
|
||||
|
||||
```js
|
||||
await puter.events.onLocal(`fs:${root}/.presence`, async ({ event }) => {
|
||||
if (event.self) return;
|
||||
render(await whoIsHere(currentFileId));
|
||||
});
|
||||
|
||||
async function whoIsHere(fileId) {
|
||||
const items = await puter.fs.readdir(`${root}/.presence`);
|
||||
const mine = items.filter(i => i.name.startsWith(`${fileId}.`));
|
||||
|
||||
const people = await Promise.all(mine.map(async i => {
|
||||
const blob = await puter.fs.read(i.path);
|
||||
const { username, at } = JSON.parse(await blob.text());
|
||||
return { username, at };
|
||||
}));
|
||||
|
||||
return people.filter(p => p.username !== me.username);
|
||||
}
|
||||
```
|
||||
|
||||
`event.self` is `true` for your own writes, so the check keeps your own arrival
|
||||
from re-rendering your list. Re-reading the folder on each event, rather than
|
||||
tracking it from the events themselves, means a missed delivery can't leave the
|
||||
list wrong.
|
||||
|
||||
## Clear out people who never left
|
||||
|
||||
`leave()` doesn't run if a tab crashes or the laptop lid closes, so presence
|
||||
files can be left behind. That's what the `at` timestamp is for: treat anything
|
||||
old as gone, and tidy it up when you see it.
|
||||
|
||||
Have each open file refresh its own entry on a timer, and ignore entries that
|
||||
have stopped refreshing:
|
||||
|
||||
```js
|
||||
const STALE_MS = 2 * 60 * 1000;
|
||||
|
||||
setInterval(() => enter(currentFileId), 30 * 1000);
|
||||
```
|
||||
|
||||
```js
|
||||
const fresh = people.filter(p => Date.now() - p.at < STALE_MS);
|
||||
```
|
||||
|
||||
Deleting other people's stale files is optional — they're small, and the next
|
||||
time that person opens the file they'll overwrite their own. If you do want to
|
||||
clean up, do it from one place rather than from everyone's browser at once.
|
||||
|
||||
## Notes
|
||||
|
||||
- Everyone needs `write` on the shared folder for this to work. A member with
|
||||
read-only access can see who's there but can't announce themselves.
|
||||
- Events never say *who* made a change — on a shared folder that would tell
|
||||
every subscriber who else has access. The username comes from inside the
|
||||
presence file, which the person wrote themselves.
|
||||
- Writes to the same file within 250 ms arrive as one event, so a refresh timer
|
||||
that lands close to another write costs one delivery, not two.
|
||||
- This is presence, not locking. It tells people someone else is there; it
|
||||
doesn't stop them saving.
|
||||
Reference in new issue
Block a user