aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-page.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-page.js31
1 files changed, 28 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
index 4fdf5f8..a25c07b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
@@ -16,6 +16,7 @@ import { FilePreview } from './files-app.js';
import { VideoPlayer } from './video-player.js';
import { GroupSettingsPanel } from './group-settings.js';
import { reportIndexPush } from './index-dock.js';
+import { clearPending, nodePkFromLink, pendingFor } from './invite-link.js';
/**
* The group shell: everything a group's "applications" (Chat, Files, and
@@ -398,6 +399,17 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// `transport` holds the attempt in progress, and keeps whichever one
// answers — so it is null after the loop exactly when none did.
let transport = null, ack = null, lastErr = null;
+ // An invitation link for this group names the node that holds its
+ // code: that node is tried first, and only it is handed the code —
+ // the transport refuses any other (docs/MESHBAY_DESIGN.md §3.4). A
+ // code typed into the form takes precedence and goes as it always has.
+ const link = session.pendingJoinCode ? null : pendingFor(groupId);
+ const joinCode = session.pendingJoinCode || (link ? link.c : null);
+ const joinNodePk = link ? nodePkFromLink(link.n) : undefined;
+ if (link) {
+ nodesData.nodes.sort((a, b) =>
+ (b.pk_node === joinNodePk) - (a.pk_node === joinNodePk));
+ }
for (const n of nodesData.nodes) {
// The same base the API calls use: signaling is a hub endpoint like
// any other, and two sources for one address is how they drift.
@@ -417,7 +429,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
try {
ack = await transport.connect(
n.node_id, live, groupId, null, sessionKeys, session.bundleKey,
- username, userId, session.pendingJoinCode, session.recoveryKey);
+ username, userId, joinCode, session.recoveryKey, joinNodePk);
break;
} catch (e) {
lastErr = e;
@@ -432,12 +444,18 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// A refusal that names a state of *this browser* — a code to enter,
// a passphrase, a device to approve — is the same answer from every
// node, and the operator to act on is this one's. Trying the next
- // node would only replace it with a less useful message.
- if (e.reason && e.reason !== 'not_hosted') throw e;
+ // node would only replace it with a less useful message. The one
+ // exception besides `not_hosted` is a link naming another host.
+ if (e.reason && e.reason !== 'not_hosted' && e.reason !== 'link_other_node') {
+ throw e;
+ }
}
}
if (!transport) throw (lastErr || new Error('no node served this group'));
session.pendingJoinCode = null;
+ // In: the invitation has done its job, whether its code was spent now or
+ // the node already knew us.
+ if (link) clearPending();
if (cancelled) return;
applyAck(ack);
transport.onAppsEnabled = (apps) => setEnabledApps(apps);
@@ -617,6 +635,13 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// one-time code from the operator before it will hand over the group
// key. Not an error to shout about — a step in joining.
if (err.reason === 'code_required') setNeedsCode(true);
+ // The link's code was refused by the node that issued it — used, or
+ // cancelled. It will not work on another try; say so, and drop it.
+ if (err.reason === 'code_invalid' && pendingFor(groupId)
+ && !session.pendingJoinCode) {
+ clearPending();
+ err.message = t('group.link_spent');
+ }
// The node has no bundle for us and this browser derived no key to make
// one — the passphrase form below is the way in, not a support request.
if (err.reason === 'no_keys') setNeedsPass(true);