diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-30 11:49:56 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-30 11:49:56 +0200 |
| commit | d3ad243c4ae3a273f623bd5fc631e3266aa4d0e4 (patch) | |
| tree | 95ff1252c80a71e93c5098822d31b835572d8b52 /packages/meshbay-hub/src/meshbay_hub/static | |
| parent | 69554fac7eba6eef7eb8a1c0111c5b92e7f21256 (diff) | |
| download | meshbay-d3ad243c4ae3a273f623bd5fc631e3266aa4d0e4.tar.gz | |
fix: only the owner decides who hosts a group, and nobody is made a member unasked
- hub: a node may host a group only if its account owns it or the owner
approved that node (new `group_hosts`). Membership was the ceiling, and
every member holds the group key, so any member's node could register as a
host and be the one clients kept. A node claiming a group it may not host
is recorded as a request; the owner is notified once and approves or
refuses it (GET/POST/DELETE /v1/groups/{id}/hosts[/{node_id}]), which
takes effect on a connected node at once.
- hub: an owner adding a username creates an invitation (new
`group_invitations`), accepted or declined by the invitee
(/v1/groups/invitations, /{id}/invitation/accept|decline). Until then the
group is not listed, not dialled, not searched and not in any token.
Invitation links, open joins and group creation still make members
directly: they are the account's own act.
- hub: the MNP token names only the group it is minted for (group_id is now
required), so a node operator no longer learns a member's other groups.
- SPA: invitations on the home page; invited people and host requests in
the group's settings; the transport sends group_id. Ten catalogues.
- Browser probes for both screens, run in Chrome and Firefox.
- Design §5.2, §7.2, §7.3, AV32, AV33; protocol §6.3; user guide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
13 files changed, 335 insertions, 2 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 875b1aa..3a85bf7 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -528,7 +528,66 @@ function NotificationFeed({ notifications, onMarkRead, onPurge }) { `; } -function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroups = true }) { +/** + * Groups somebody added this account to, waiting for an answer. + * + * Being added is an invitation, not a membership (the hub's `group_invitations`): + * until it is accepted the group is not in the sidebar, not dialled, not in + * Search and not named in any token this browser hands a node. Accepting is + * what makes it a group of ours; declining forgets it. + */ +function PendingInvitations({ token, onAccepted }) { + const [items, setItems] = useState([]); + const [busy, setBusy] = useState(''); + const [error, setError] = useState(''); + const load = useCallback(() => { + if (!token) return; + hubFetch('/v1/groups/invitations', { token }) + .then(data => setItems(data.invitations || [])) + .catch(() => setItems([])); + }, [token]); + useEffect(() => { load(); }, [load]); + + const answer = async (inv, verb) => { + setBusy(inv.group_id); setError(''); + try { + await hubFetch(`/v1/groups/${inv.group_id}/invitation/${verb}`, + { method: 'POST', token }); + setItems(prev => prev.filter(i => i.group_id !== inv.group_id)); + if (verb === 'accept' && onAccepted) onAccepted(inv.group_id); + } catch (err) { + setError(err.message); + } finally { + setBusy(''); + } + }; + + if (!items.length) return null; + return html` + <div class="settings-section"> + <h3 class="settings-heading">${t('home.invitations')}</h3> + <p class="settings-hint">${t('home.invitation_hint')}</p> + ${error && html`<p class="error-msg">${error}</p>`} + <ul class="invite-links"> + ${items.map(inv => html` + <li key=${inv.group_id} style="display:flex;gap:8px;align-items:center; + flex-wrap:wrap;word-break:break-word;margin:4px 0"> + <strong><${GroupName} name=${inv.name} owner=${inv.owner_username} /></strong> + ${inv.invited_by && html`<span style="color:var(--text-dim)"> + ${t('home.invited_by', { name: inv.invited_by })}</span>`} + <button class="admin-btn" type="button" disabled=${busy === inv.group_id} + onClick=${() => answer(inv, 'accept')}>${t('home.accept')}</button> + <button class="admin-btn" type="button" disabled=${busy === inv.group_id} + onClick=${() => answer(inv, 'decline')}>${t('home.decline')}</button> + </li> + `)} + </ul> + </div> + `; +} + +function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroups = true, + token, onGroupsChanged }) { const [setupDismissed, setSetupDismissed] = useState(false); if (groups.length === 0) { @@ -539,6 +598,7 @@ function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroup return html` <div> <h2>${t('home.welcome')}</h2> + <${PendingInvitations} token=${token} onAccepted=${onGroupsChanged} /> <${NotificationFeed} notifications=${notifications} onMarkRead=${onMarkRead} onPurge=${onPurge} /> <${JoinByLink} hubOrigin=${platform.hubOrigin()} /> @@ -555,6 +615,7 @@ function HomePage({ groups, notifications, onMarkRead, onPurge, allowPublicGroup return html` <div> <h2>${t('home.my_groups')}</h2> + <${PendingInvitations} token=${token} onAccepted=${onGroupsChanged} /> <${NotificationFeed} notifications=${notifications} onMarkRead=${onMarkRead} onPurge=${onPurge} /> <div class="group-grid"> @@ -958,6 +1019,15 @@ function App() { fetchNotifications(); }, [user]); + // After an invitation is accepted: the group is ours now, and only the hub + // knows its row the way `/mine` answers it. + const reloadGroups = useCallback(() => { + if (!user) return; + hubFetch('/v1/groups/mine', { token: user.token }) + .then(data => setGroups(data.groups || [])) + .catch(() => {}); + }, [user]); + // The group list lives here, so an edit made three components down has to come // back up rather than be re-fetched: a reload would drop the WebRTC connection // the page is holding. @@ -1216,6 +1286,7 @@ function App() { ? html`<${LazyAdminPage} token=${user.token} role=${user.role} />` : html`<${HomePage} groups=${groups} notifications=${notifications} allowPublicGroups=${allowPublicGroups} + token=${user.token} onGroupsChanged=${reloadGroups} onMarkRead=${markRead} onPurge=${purgeNotifications} />`; } else if (route === '/settings') { page = html`<${SettingsPage} user=${user} theme=${theme} @@ -1233,6 +1304,7 @@ function App() { } else { page = html`<${HomePage} groups=${groups} notifications=${notifications} allowPublicGroups=${allowPublicGroups} + token=${user.token} onGroupsChanged=${reloadGroups} onMarkRead=${markRead} onPurge=${purgeNotifications} />`; } diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js index fba8a0e..048f831 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -410,7 +410,14 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, onAppDirectories, onRefreshIndex, onPaired, onLeft }) { const [members, setMembers] = useState([]); + // Asked and not answered — the owner is the only one told (the hub leaves + // the field out for anyone else). + const [invited, setInvited] = useState([]); const [adminId, setAdminId] = useState(''); + // Nodes other than the owner's that asked to serve this group, and what the + // owner answered. Only the owner reads it. + const [hosts, setHosts] = useState([]); + const [hostBusy, setHostBusy] = useState(''); const [loading, setLoading] = useState(true); const [inviteUser, setInviteUser] = useState(''); const [inviting, setInviting] = useState(false); @@ -802,6 +809,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, hubFetch(`/v1/groups/${groupId}/members`, { token }) .then(data => { setMembers(data.members || []); + setInvited(data.invited || []); setAdminId(data.admin_id || ''); }) .catch(() => {}) @@ -810,6 +818,30 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, useEffect(() => { loadMembers(); }, [loadMembers]); + const ownsGroup = Boolean(group && group.is_admin); + const loadHosts = useCallback(() => { + if (!ownsGroup) return; + hubFetch(`/v1/groups/${groupId}/hosts`, { token }) + .then(data => setHosts(data.hosts || [])) + .catch(() => setHosts([])); + }, [groupId, token, ownsGroup]); + + useEffect(() => { loadHosts(); }, [loadHosts]); + + const decideHost = useCallback(async (host, approve) => { + setHostBusy(host.node_id); + setError(''); + try { + await hubFetch(`/v1/groups/${groupId}/hosts/${host.node_id}`, + { method: approve ? 'POST' : 'DELETE', token }); + loadHosts(); + } catch (err) { + setError(err.message); + } finally { + setHostBusy(''); + } + }, [groupId, token, loadHosts]); + useEffect(() => { hubFetch('/v1/users/me/preferences', { token }) .then(prefs => setInviteByEmail(prefs[INVITE_EMAIL_PREF] === 'true')) @@ -1382,12 +1414,58 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, </td> </tr> `)} + ${invited.map(m => html` + <tr key=${m.user_id}> + <td>${m.username}</td> + <td><span class="badge">${t('members.invited')}</span></td> + <td class="admin-actions"> + ${isAdmin && html` + <button class="admin-btn danger" disabled=${removing === m.user_id} + onClick=${async () => { + if (!await ask(t('members.remove_confirm', { user: m.username }))) return; + removeMember(m); + }}> + ${removing === m.user_id ? '...' : t('members.remove')} + </button> + `} + </td> + </tr> + `)} </tbody> </table> ${isAdmin && members.length > 1 && html` <p class="settings-hint">${t('members.remove_hint')}</p> `} </${CollapsibleSection}> + + ${ownsGroup && html` + <${CollapsibleSection} title=${t('hosts.title')}> + <p class="settings-hint">${t('hosts.hint')}</p> + ${hosts.length === 0 && html`<p class="settings-hint">${t('hosts.none')}</p>`} + ${hosts.length > 0 && html` + <table class="admin-table"> + <tbody> + ${hosts.map(h => html` + <tr key=${h.node_id}> + <td>${t('hosts.from', { name: h.username })} + <br /><code class="node-key">${h.pk_node}</code></td> + <td><span class="badge">${t(`hosts.status_${h.status}`)}</span> + ${h.online && html` <span class="badge">${t('hosts.online')}</span>`}</td> + <td class="admin-actions"> + ${h.status !== 'approved' && html` + <button class="admin-btn" disabled=${hostBusy === h.node_id} + onClick=${() => decideHost(h, true)}>${t('hosts.approve')}</button>`} + ${h.status !== 'refused' && html` + <button class="admin-btn danger" disabled=${hostBusy === h.node_id} + onClick=${() => decideHost(h, false)}>${t('hosts.refuse')}</button>`} + </td> + </tr> + `)} + </tbody> + </table> + `} + </${CollapsibleSection}> + `} </div> `; } diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index a83f44b..3c0f1ec 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -1222,4 +1222,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Umbenannt: „{name}“ ist nicht auf jedem System ein gültiger Name", 'transfers.renamed_n': "Namen geändert, damit sie auf jedem System gültig sind: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Einladungen", + 'home.invitation_hint': "Jemand hat Sie zu diesen Gruppen hinzugefügt. Nehmen Sie nur die an, die Sie erwarten: Eine Gruppe, der Sie beitreten, sieht Ihre Adresse, wenn Sie sie öffnen.", + 'home.invited_by': "eingeladen von {name}", + 'home.accept': "Annehmen", + 'home.decline': "Ablehnen", + 'members.invited': "eingeladen", + 'hosts.title': "Hosts", + 'hosts.hint': "Ihre eigenen Nodes stellen diese Gruppe ohne Rückfrage bereit. Ein anderer Node tut es erst, wenn Sie ihn hier genehmigen; ein Node, der anfragt, steht unten.", + 'hosts.none': "Kein anderer Node hat angefragt, diese Gruppe bereitzustellen.", + 'hosts.from': "Ein Node von {name}", + 'hosts.status_pending': "wartet auf Ihre Antwort", + 'hosts.status_approved': "genehmigt", + 'hosts.status_refused': "abgelehnt", + 'hosts.approve': "Genehmigen", + 'hosts.refuse': "Ablehnen", + 'hosts.online': "online", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index 46c5094..947c61d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -1203,4 +1203,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Renamed: “{name}” is not a valid name on every system", 'transfers.renamed_n': "Names changed to be valid on every system: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Invitations", + 'home.invitation_hint': "Somebody added you to these groups. Accept only the ones you expect: a group you join can see your address when you open it.", + 'home.invited_by': "invited by {name}", + 'home.accept': "Accept", + 'home.decline': "Decline", + 'members.invited': "invited", + 'hosts.title': "Hosts", + 'hosts.hint': "Your own nodes serve this group without asking. Another node serves it only once you approve it here; a node that asks is listed below.", + 'hosts.none': "No other node has asked to host this group.", + 'hosts.from': "A node of {name}", + 'hosts.status_pending': "waiting for your answer", + 'hosts.status_approved': "approved", + 'hosts.status_refused': "refused", + 'hosts.approve': "Approve", + 'hosts.refuse': "Refuse", + 'hosts.online': "online", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index 0d0e865..a0220d8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -1216,4 +1216,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Renombrado: «{name}» no es un nombre válido en todos los sistemas", 'transfers.renamed_n': "Nombres cambiados para ser válidos en todos los sistemas: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Invitaciones", + 'home.invitation_hint': "Alguien le ha añadido a estos grupos. Acepte solo los que espera: un grupo al que se une ve su dirección cuando lo abre.", + 'home.invited_by': "invitado por {name}", + 'home.accept': "Aceptar", + 'home.decline': "Rechazar", + 'members.invited': "invitado", + 'hosts.title': "Anfitriones", + 'hosts.hint': "Sus propios nodes sirven este grupo sin preguntar. Otro node lo sirve solo cuando usted lo aprueba aquí; un node que lo pide aparece abajo.", + 'hosts.none': "Ningún otro node ha pedido alojar este grupo.", + 'hosts.from': "Un node de {name}", + 'hosts.status_pending': "esperando su respuesta", + 'hosts.status_approved': "aprobado", + 'hosts.status_refused': "rechazado", + 'hosts.approve': "Aprobar", + 'hosts.refuse': "Rechazar", + 'hosts.online': "en línea", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index 3c86cd7..c4bc37e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -1231,4 +1231,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Renommé : « {name} » n’est pas un nom valide sur tous les systèmes", 'transfers.renamed_n': "Noms modifiés pour être valides sur tous les systèmes : {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Invitations", + 'home.invitation_hint': "Quelqu'un vous a ajouté à ces groupes. N'acceptez que ceux que vous attendez : un groupe que vous rejoignez voit votre adresse quand vous l'ouvrez.", + 'home.invited_by': "invité par {name}", + 'home.accept': "Accepter", + 'home.decline': "Refuser", + 'members.invited': "invité", + 'hosts.title': "Hôtes", + 'hosts.hint': "Vos propres nodes servent ce groupe sans rien demander. Un autre node ne le sert qu'une fois approuvé ici ; un node qui le demande apparaît ci-dessous.", + 'hosts.none': "Aucun autre node n'a demandé à héberger ce groupe.", + 'hosts.from': "Un node de {name}", + 'hosts.status_pending': "en attente de votre réponse", + 'hosts.status_approved': "approuvé", + 'hosts.status_refused': "refusé", + 'hosts.approve': "Approuver", + 'hosts.refuse': "Refuser", + 'hosts.online': "en ligne", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index 3a74b4d..59505b8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -1230,4 +1230,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Rinominato: «{name}» non è un nome valido su tutti i sistemi", 'transfers.renamed_n': "Nomi modificati per essere validi su tutti i sistemi: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Inviti", + 'home.invitation_hint': "Qualcuno ti ha aggiunto a questi gruppi. Accetta solo quelli che ti aspetti: un gruppo a cui ti unisci vede il tuo indirizzo quando lo apri.", + 'home.invited_by': "invitato da {name}", + 'home.accept': "Accetta", + 'home.decline': "Rifiuta", + 'members.invited': "invitato", + 'hosts.title': "Host", + 'hosts.hint': "I tuoi node servono questo gruppo senza chiedere. Un altro node lo serve solo dopo che lo approvi qui; un node che lo chiede compare qui sotto.", + 'hosts.none': "Nessun altro node ha chiesto di ospitare questo gruppo.", + 'hosts.from': "Un node di {name}", + 'hosts.status_pending': "in attesa della tua risposta", + 'hosts.status_approved': "approvato", + 'hosts.status_refused': "rifiutato", + 'hosts.approve': "Approva", + 'hosts.refuse': "Rifiuta", + 'hosts.online': "online", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index e0c38de..bba9564 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -1214,4 +1214,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "名前を変更しました:「{name}」はすべてのシステムで有効な名前ではありません", 'transfers.renamed_n': "すべてのシステムで有効になるよう変更した名前:{n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "招待", + 'home.invitation_hint': "誰かがあなたをこれらのグループに追加しました。心当たりのあるものだけを承諾してください。参加したグループは、開いたときにあなたのアドレスを知ることができます。", + 'home.invited_by': "{name} からの招待", + 'home.accept': "承諾", + 'home.decline': "辞退", + 'members.invited': "招待中", + 'hosts.title': "ホスト", + 'hosts.hint': "あなた自身の node は確認なしでこのグループを提供します。他の node は、ここで承認した後にのみ提供します。申請した node は下に表示されます。", + 'hosts.none': "このグループのホストを申請した node は他にありません。", + 'hosts.from': "{name} の node", + 'hosts.status_pending': "あなたの返答待ち", + 'hosts.status_approved': "承認済み", + 'hosts.status_refused': "拒否済み", + 'hosts.approve': "承認", + 'hosts.refuse': "拒否", + 'hosts.online': "オンライン", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index 83b9eed..87e5b87 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -1232,4 +1232,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Hernoemd: ‘{name}’ is niet op elk systeem een geldige naam", 'transfers.renamed_n': "Namen aangepast zodat ze op elk systeem geldig zijn: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Uitnodigingen", + 'home.invitation_hint': "Iemand heeft u aan deze groepen toegevoegd. Accepteer alleen de groepen die u verwacht: een groep waar u lid van wordt, ziet uw adres wanneer u hem opent.", + 'home.invited_by': "uitgenodigd door {name}", + 'home.accept': "Accepteren", + 'home.decline': "Weigeren", + 'members.invited': "uitgenodigd", + 'hosts.title': "Hosts", + 'hosts.hint': "Uw eigen nodes bedienen deze groep zonder te vragen. Een andere node doet dat pas nadat u hem hier goedkeurt; een node die erom vraagt, staat hieronder.", + 'hosts.none': "Geen andere node heeft gevraagd deze groep te hosten.", + 'hosts.from': "Een node van {name}", + 'hosts.status_pending': "wacht op uw antwoord", + 'hosts.status_approved': "goedgekeurd", + 'hosts.status_refused': "geweigerd", + 'hosts.approve': "Goedkeuren", + 'hosts.refuse': "Weigeren", + 'hosts.online': "online", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index 3edba2d..6d81b25 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -1258,4 +1258,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Zmieniono nazwę: „{name}” nie jest prawidłową nazwą w każdym systemie", 'transfers.renamed_n': "Nazwy zmienione, by były prawidłowe w każdym systemie: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Zaproszenia", + 'home.invitation_hint': "Ktoś dodał Cię do tych grup. Akceptuj tylko te, których się spodziewasz: grupa, do której dołączysz, widzi Twój adres, gdy ją otwierasz.", + 'home.invited_by': "zaprasza {name}", + 'home.accept': "Akceptuj", + 'home.decline': "Odrzuć", + 'members.invited': "zaproszony", + 'hosts.title': "Hosty", + 'hosts.hint': "Twoje własne node'y obsługują tę grupę bez pytania. Inny node robi to dopiero po Twojej akceptacji tutaj; node, który o to prosi, jest widoczny poniżej.", + 'hosts.none': "Żaden inny node nie prosił o hostowanie tej grupy.", + 'hosts.from': "Node użytkownika {name}", + 'hosts.status_pending': "czeka na Twoją odpowiedź", + 'hosts.status_approved': "zaakceptowany", + 'hosts.status_refused': "odrzucony", + 'hosts.approve': "Akceptuj", + 'hosts.refuse': "Odrzuć", + 'hosts.online': "online", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index 3f44570..7b12ea5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -1217,4 +1217,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "Renomeado: “{name}” não é um nome válido em todos os sistemas", 'transfers.renamed_n': "Nomes alterados para serem válidos em todos os sistemas: {n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "Convites", + 'home.invitation_hint': "Alguém adicionou você a estes grupos. Aceite apenas os que espera: um grupo em que você entra vê o seu endereço quando você o abre.", + 'home.invited_by': "convidado por {name}", + 'home.accept': "Aceitar", + 'home.decline': "Recusar", + 'members.invited': "convidado", + 'hosts.title': "Hosts", + 'hosts.hint': "Seus próprios nodes servem este grupo sem perguntar. Outro node só o serve depois que você o aprova aqui; um node que pede aparece abaixo.", + 'hosts.none': "Nenhum outro node pediu para hospedar este grupo.", + 'hosts.from': "Um node de {name}", + 'hosts.status_pending': "aguardando sua resposta", + 'hosts.status_approved': "aprovado", + 'hosts.status_refused': "recusado", + 'hosts.approve': "Aprovar", + 'hosts.refuse': "Recusar", + 'hosts.online': "online", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index 1168460..9f3c902 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -1203,4 +1203,22 @@ export default { // Names made writable everywhere when saved (portable-name.js) 'transfers.renamed': "已重命名:“{name}”并非在所有系统上都是有效的名称", 'transfers.renamed_n': "为在所有系统上有效而修改的名称:{n}", + + // Invitations waiting for an answer, and the nodes a group owner approves + 'home.invitations': "邀请", + 'home.invitation_hint': "有人把你加入了这些群组。只接受你预期的邀请:你加入的群组在你打开它时可以看到你的地址。", + 'home.invited_by': "由 {name} 邀请", + 'home.accept': "接受", + 'home.decline': "拒绝", + 'members.invited': "已邀请", + 'hosts.title': "主机", + 'hosts.hint': "你自己的 node 无需询问即可提供此群组。其他 node 只有在你于此处批准后才会提供;提出申请的 node 列在下方。", + 'hosts.none': "没有其他 node 申请托管此群组。", + 'hosts.from': "{name} 的 node", + 'hosts.status_pending': "等待你的答复", + 'hosts.status_approved': "已批准", + 'hosts.status_refused': "已拒绝", + 'hosts.approve': "批准", + 'hosts.refuse': "拒绝", + 'hosts.online': "在线", }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 98d010e..04c2d23 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -653,7 +653,10 @@ class MeshBayTransport { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this._accessToken}`, }, - body: JSON.stringify({ node_pk: this._nodePkTarget || '' }), + // The token names this connection's group and no other: it is handed to + // the node's operator, who has no business learning every group this + // account belongs to. + body: JSON.stringify({ node_pk: this._nodePkTarget || '', group_id: this._groupId }), }); if (!r.ok) throw new Error(`Could not obtain a node token: ${r.status}`); return (await r.json()).mnp_token; |