@arbidocs/client 0.4.166 → 0.4.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/index.cjs +566 -184
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +367 -97
- package/dist/index.d.ts +367 -97
- package/dist/index.js +558 -185
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11,55 +11,67 @@ var sodium__default = /*#__PURE__*/_interopDefault(sodium);
|
|
|
11
11
|
// src/client.ts
|
|
12
12
|
|
|
13
13
|
// src/session.ts
|
|
14
|
-
function createInitialState() {
|
|
14
|
+
function createInitialState(isSsoMode = false) {
|
|
15
15
|
return {
|
|
16
16
|
accessToken: null,
|
|
17
17
|
userEmail: null,
|
|
18
18
|
userExtId: null,
|
|
19
19
|
selectedWorkspaceId: null,
|
|
20
|
-
isSsoMode
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
isSsoMode,
|
|
21
|
+
user: null,
|
|
22
|
+
signedIn: false,
|
|
23
|
+
serverSessionKey: null
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
var stored = (s) => ({
|
|
27
|
+
accessToken: s.accessToken,
|
|
28
|
+
userEmail: s.userEmail,
|
|
29
|
+
userExtId: s.userExtId,
|
|
30
|
+
isSsoMode: s.isSsoMode,
|
|
31
|
+
user: s.user,
|
|
32
|
+
signedIn: s.signedIn
|
|
33
|
+
});
|
|
34
|
+
function createSessionManager(options = {}) {
|
|
35
|
+
const { storage } = options;
|
|
36
|
+
let state = { ...createInitialState(), ...storage?.load() ?? {} };
|
|
27
37
|
const listeners = /* @__PURE__ */ new Set();
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
function update(patch) {
|
|
39
|
+
state = { ...state, ...patch };
|
|
40
|
+
storage?.save(stored(state));
|
|
41
|
+
const snapshot = state;
|
|
42
|
+
for (const listener of [...listeners]) listener(snapshot);
|
|
33
43
|
}
|
|
34
44
|
return {
|
|
35
|
-
getState()
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
setAccessToken(token) {
|
|
39
|
-
state = { ...state, accessToken: token };
|
|
40
|
-
notify();
|
|
41
|
-
},
|
|
45
|
+
getState: () => state,
|
|
46
|
+
setAccessToken: (accessToken) => update({ accessToken }),
|
|
42
47
|
setUser(email, extId) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
notify();
|
|
48
|
+
update({
|
|
49
|
+
userEmail: email,
|
|
50
|
+
userExtId: extId ?? state.userExtId,
|
|
51
|
+
user: state.user ? { ...state.user, email } : null
|
|
52
|
+
});
|
|
49
53
|
},
|
|
50
|
-
|
|
51
|
-
state
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
isAuth0Authenticated: opts.isAuth0Authenticated ?? state.isAuth0Authenticated,
|
|
55
|
-
auth0AccessToken: opts.auth0AccessToken !== void 0 ? opts.auth0AccessToken : state.auth0AccessToken
|
|
56
|
-
};
|
|
57
|
-
notify();
|
|
54
|
+
updateUser(patch) {
|
|
55
|
+
if (!state.user) return;
|
|
56
|
+
const user = { ...state.user, ...patch };
|
|
57
|
+
update({ user, userEmail: user.email });
|
|
58
58
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
setSelectedWorkspace: (selectedWorkspaceId) => update({ selectedWorkspaceId }),
|
|
60
|
+
setSsoMode: (isSsoMode) => update({ isSsoMode }),
|
|
61
|
+
setServerSessionKey: (serverSessionKey) => update({ serverSessionKey }),
|
|
62
|
+
signIn({ accessToken, user, isSsoMode, serverSessionKey }) {
|
|
63
|
+
update({
|
|
64
|
+
accessToken,
|
|
65
|
+
user,
|
|
66
|
+
userEmail: user.email,
|
|
67
|
+
userExtId: user.external_id,
|
|
68
|
+
isSsoMode,
|
|
69
|
+
serverSessionKey,
|
|
70
|
+
signedIn: true
|
|
71
|
+
});
|
|
62
72
|
},
|
|
73
|
+
// The SSO mode outlives a sign-out: it says how this deployment (or this user) signs in.
|
|
74
|
+
clear: () => update(createInitialState(state.isSsoMode)),
|
|
63
75
|
subscribe(listener) {
|
|
64
76
|
listeners.add(listener);
|
|
65
77
|
return () => {
|
|
@@ -68,23 +80,41 @@ function createSessionManager() {
|
|
|
68
80
|
}
|
|
69
81
|
};
|
|
70
82
|
}
|
|
83
|
+
var ACCESS_TOKEN_STORAGE_KEY = "arbi_access_token";
|
|
84
|
+
var SESSION_STORAGE_KEY = "arbi_session";
|
|
85
|
+
function browserSessionStateStorage() {
|
|
86
|
+
return {
|
|
87
|
+
load() {
|
|
88
|
+
try {
|
|
89
|
+
const rest = JSON.parse(window.localStorage.getItem(SESSION_STORAGE_KEY) ?? "null");
|
|
90
|
+
const accessToken = window.sessionStorage.getItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
91
|
+
return { ...rest ?? {}, accessToken };
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
save({ accessToken, ...rest }) {
|
|
97
|
+
try {
|
|
98
|
+
if (accessToken) window.sessionStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, accessToken);
|
|
99
|
+
else window.sessionStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
100
|
+
if (rest.signedIn || rest.isSsoMode)
|
|
101
|
+
window.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(rest));
|
|
102
|
+
else window.localStorage.removeItem(SESSION_STORAGE_KEY);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
71
108
|
function createTokenProvider(session) {
|
|
72
109
|
return {
|
|
73
110
|
getAccessToken: () => session.getState().accessToken
|
|
74
111
|
};
|
|
75
112
|
}
|
|
76
|
-
function createAuthStateProvider(session) {
|
|
113
|
+
function createAuthStateProvider(session, selectedWorkspace = () => session.getState().selectedWorkspaceId) {
|
|
77
114
|
return {
|
|
78
115
|
getUserEmail: () => session.getState().userEmail,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
isSsoMode: s.isSsoMode,
|
|
83
|
-
isAuth0Authenticated: s.isAuth0Authenticated,
|
|
84
|
-
auth0AccessToken: s.auth0AccessToken
|
|
85
|
-
};
|
|
86
|
-
},
|
|
87
|
-
getSelectedWorkspaceId: () => session.getState().selectedWorkspaceId,
|
|
116
|
+
isSsoMode: () => session.getState().isSsoMode,
|
|
117
|
+
getSelectedWorkspaceId: selectedWorkspace,
|
|
88
118
|
setAccessToken: (token) => session.setAccessToken(token)
|
|
89
119
|
};
|
|
90
120
|
}
|
|
@@ -363,6 +393,25 @@ function parseCallFragment(hash) {
|
|
|
363
393
|
const v = Number(params.get("v"));
|
|
364
394
|
return { key, linkVersion: Number.isInteger(v) && v > 0 ? v : null };
|
|
365
395
|
}
|
|
396
|
+
function buildCallLink(origin, callExtId, key, linkVersion) {
|
|
397
|
+
return `${origin}/call/${callExtId}#${buildCallFragment(key, linkVersion)}`;
|
|
398
|
+
}
|
|
399
|
+
async function sealCallKeyForPeers(key, peers, exclude) {
|
|
400
|
+
const out = {};
|
|
401
|
+
for (const peer of peers) {
|
|
402
|
+
if (!peer.encryption_public_key || peer.external_id === exclude) continue;
|
|
403
|
+
out[peer.external_id] = await sealCallKey(key, peer.encryption_public_key);
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
async function openCallKeyOf(call, encryptionSecretKey) {
|
|
408
|
+
if (!call.my_sealed_key) return null;
|
|
409
|
+
try {
|
|
410
|
+
return await openSealedCallKey(call.my_sealed_key, encryptionSecretKey);
|
|
411
|
+
} catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
366
415
|
var CHAT_KEY_BYTES = 32;
|
|
367
416
|
async function deriveDirectChatKey(peerPublicKeyBase64, mySecretKey) {
|
|
368
417
|
await initSodium();
|
|
@@ -1067,8 +1116,126 @@ function createBearerAuthMiddleware(config) {
|
|
|
1067
1116
|
};
|
|
1068
1117
|
}
|
|
1069
1118
|
|
|
1119
|
+
// src/workspace-keys.ts
|
|
1120
|
+
function createWorkspaceKeys(deps) {
|
|
1121
|
+
const wrapped = /* @__PURE__ */ new Map();
|
|
1122
|
+
const keyless = /* @__PURE__ */ new Set();
|
|
1123
|
+
let listing = null;
|
|
1124
|
+
const primed = /* @__PURE__ */ new Map();
|
|
1125
|
+
let primedFor = null;
|
|
1126
|
+
function remember(workspaces) {
|
|
1127
|
+
for (const ws of workspaces) {
|
|
1128
|
+
if (ws.wrapped_key) {
|
|
1129
|
+
wrapped.set(ws.external_id, ws.wrapped_key);
|
|
1130
|
+
keyless.delete(ws.external_id);
|
|
1131
|
+
} else if (ws.is_public || ws.is_member) keyless.add(ws.external_id);
|
|
1132
|
+
}
|
|
1133
|
+
return workspaces;
|
|
1134
|
+
}
|
|
1135
|
+
function readList() {
|
|
1136
|
+
listing ??= (async () => {
|
|
1137
|
+
try {
|
|
1138
|
+
const token = deps.getAccessToken();
|
|
1139
|
+
if (!token) throw new Error("Not signed in");
|
|
1140
|
+
const list = await deps.listWorkspaces(token);
|
|
1141
|
+
if (!list) throw new Error("Failed to fetch workspaces");
|
|
1142
|
+
remember(list);
|
|
1143
|
+
} finally {
|
|
1144
|
+
listing = null;
|
|
1145
|
+
}
|
|
1146
|
+
})();
|
|
1147
|
+
return listing;
|
|
1148
|
+
}
|
|
1149
|
+
async function wrappedKey(workspaceId, { refresh = false } = {}) {
|
|
1150
|
+
if (!refresh) {
|
|
1151
|
+
const cached = wrapped.get(workspaceId);
|
|
1152
|
+
if (cached) return { wrappedKey: cached, fromCache: true };
|
|
1153
|
+
if (keyless.has(workspaceId)) return { wrappedKey: null, fromCache: true };
|
|
1154
|
+
}
|
|
1155
|
+
wrapped.delete(workspaceId);
|
|
1156
|
+
keyless.delete(workspaceId);
|
|
1157
|
+
await readList();
|
|
1158
|
+
const fresh = wrapped.get(workspaceId);
|
|
1159
|
+
if (fresh) return { wrappedKey: fresh, fromCache: false };
|
|
1160
|
+
if (keyless.has(workspaceId)) return { wrappedKey: null, fromCache: false };
|
|
1161
|
+
throw new Error(`Workspace ${workspaceId} not found or has no encryption key`);
|
|
1162
|
+
}
|
|
1163
|
+
async function sealedFor(workspaceId, serverSessionKey2, refresh) {
|
|
1164
|
+
const stored2 = await deps.persistence.getSession();
|
|
1165
|
+
if (!stored2?.signingPrivateKey) throw new Error("No stored identity to unlock the workspace");
|
|
1166
|
+
const { wrappedKey: key, fromCache } = await wrappedKey(workspaceId, { refresh });
|
|
1167
|
+
if (!key) throw new Error(`Workspace ${workspaceId} is public: it has no key to deposit`);
|
|
1168
|
+
try {
|
|
1169
|
+
await deps.crypto.ensureReady();
|
|
1170
|
+
return await deps.crypto.sealKeyForSession(
|
|
1171
|
+
deps.unwrapWorkspaceKey(key, stored2.signingPrivateKey),
|
|
1172
|
+
serverSessionKey2
|
|
1173
|
+
);
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
if (!fromCache) throw error;
|
|
1176
|
+
return sealedFor(workspaceId, serverSessionKey2, true);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
async function serverSessionKey() {
|
|
1180
|
+
const key = deps.getServerSessionKey() ?? (await deps.persistence.getSession())?.serverSessionKey;
|
|
1181
|
+
if (!key) throw new Error("No server session to unlock the workspace for");
|
|
1182
|
+
return key;
|
|
1183
|
+
}
|
|
1184
|
+
function prime(workspaceId, { force = false } = {}) {
|
|
1185
|
+
const sessionKey = deps.getServerSessionKey();
|
|
1186
|
+
if (sessionKey !== primedFor) {
|
|
1187
|
+
primed.clear();
|
|
1188
|
+
primedFor = sessionKey;
|
|
1189
|
+
}
|
|
1190
|
+
const pending = primed.get(workspaceId);
|
|
1191
|
+
if (pending && !force) return pending;
|
|
1192
|
+
const run = (async () => {
|
|
1193
|
+
const token = deps.getAccessToken();
|
|
1194
|
+
if (!token) throw new Error("Not signed in");
|
|
1195
|
+
const sealed = await sealedFor(workspaceId, await serverSessionKey(), false);
|
|
1196
|
+
await deps.primeKey(token, workspaceId, sealed);
|
|
1197
|
+
})();
|
|
1198
|
+
primed.set(workspaceId, run);
|
|
1199
|
+
run.catch(() => {
|
|
1200
|
+
if (primed.get(workspaceId) === run) primed.delete(workspaceId);
|
|
1201
|
+
});
|
|
1202
|
+
return run;
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
remember,
|
|
1206
|
+
wrappedKey,
|
|
1207
|
+
prime,
|
|
1208
|
+
forget(workspaceIds) {
|
|
1209
|
+
for (const id of workspaceIds) {
|
|
1210
|
+
wrapped.delete(id);
|
|
1211
|
+
keyless.delete(id);
|
|
1212
|
+
primed.delete(id);
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
clear() {
|
|
1216
|
+
wrapped.clear();
|
|
1217
|
+
keyless.clear();
|
|
1218
|
+
primed.clear();
|
|
1219
|
+
primedFor = null;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function workspaceKeyRequiredOf(body) {
|
|
1224
|
+
const detail = body?.detail;
|
|
1225
|
+
if (!detail || typeof detail !== "object") return null;
|
|
1226
|
+
const d = detail;
|
|
1227
|
+
return d.code === "workspace_key_required" && typeof d.workspace_ext_id === "string" ? d.workspace_ext_id : null;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1070
1230
|
// src/middleware/auto-relogin.ts
|
|
1071
1231
|
var NO_ACTIVE_WORKSPACE = /no active workspace/i;
|
|
1232
|
+
async function keyRequiredWorkspace(response) {
|
|
1233
|
+
try {
|
|
1234
|
+
return workspaceKeyRequiredOf(await response.clone().json());
|
|
1235
|
+
} catch {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1072
1239
|
async function isNoActiveWorkspace(response) {
|
|
1073
1240
|
try {
|
|
1074
1241
|
return NO_ACTIVE_WORKSPACE.test(await response.clone().text());
|
|
@@ -1093,6 +1260,16 @@ function createAutoReloginMiddleware(config) {
|
|
|
1093
1260
|
}
|
|
1094
1261
|
},
|
|
1095
1262
|
async onResponse({ response, request }) {
|
|
1263
|
+
if (response.status === 409 && config.primeWorkspaceHandler) {
|
|
1264
|
+
const workspaceId = await keyRequiredWorkspace(response);
|
|
1265
|
+
if (workspaceId) {
|
|
1266
|
+
if (!await config.primeWorkspaceHandler(workspaceId)) return response;
|
|
1267
|
+
const retry = cloneCache.get(request) ?? safeClone(request);
|
|
1268
|
+
if (retry) return fetch(retry);
|
|
1269
|
+
config.onRetryCloneFailed?.();
|
|
1270
|
+
return response;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1096
1273
|
if (response.status === 400 && await isNoActiveWorkspace(response)) {
|
|
1097
1274
|
let token = null;
|
|
1098
1275
|
let recovered = await config.reopenWorkspaceHandler?.() ?? false;
|
|
@@ -1100,10 +1277,7 @@ function createAutoReloginMiddleware(config) {
|
|
|
1100
1277
|
token = await config.reloginHandler();
|
|
1101
1278
|
recovered = token !== null;
|
|
1102
1279
|
}
|
|
1103
|
-
if (!recovered)
|
|
1104
|
-
config.onReloginFailed?.();
|
|
1105
|
-
return response;
|
|
1106
|
-
}
|
|
1280
|
+
if (!recovered) return response;
|
|
1107
1281
|
const retry = cloneCache.get(request) ?? safeClone(request);
|
|
1108
1282
|
if (retry) {
|
|
1109
1283
|
if (token) retry.headers.set("Authorization", `Bearer ${token}`);
|
|
@@ -1142,8 +1316,6 @@ function createAutoReloginMiddleware(config) {
|
|
|
1142
1316
|
console.warn("[API] Could not retry request after re-login (body already consumed)");
|
|
1143
1317
|
config.onRetryCloneFailed?.();
|
|
1144
1318
|
}
|
|
1145
|
-
} else {
|
|
1146
|
-
config.onReloginFailed?.();
|
|
1147
1319
|
}
|
|
1148
1320
|
}
|
|
1149
1321
|
}
|
|
@@ -1169,15 +1341,20 @@ function createReloginHandler(deps) {
|
|
|
1169
1341
|
const userEmail = deps.authState.getUserEmail();
|
|
1170
1342
|
if (!userEmail) {
|
|
1171
1343
|
console.warn("[API] Re-login aborted: no user email");
|
|
1344
|
+
deps.onReloginFailed?.("not-ready");
|
|
1172
1345
|
return Promise.resolve(null);
|
|
1173
1346
|
}
|
|
1347
|
+
const fail = (reason) => {
|
|
1348
|
+
deps.onReloginFailed?.(reason);
|
|
1349
|
+
return null;
|
|
1350
|
+
};
|
|
1174
1351
|
reloginPromise = (async () => {
|
|
1175
1352
|
try {
|
|
1176
1353
|
await deps.crypto.ensureReady();
|
|
1177
|
-
const session = await deps.
|
|
1354
|
+
const session = await deps.persistence.getSession();
|
|
1178
1355
|
if (!session) {
|
|
1179
1356
|
console.warn("[API] Re-login aborted: no session");
|
|
1180
|
-
return
|
|
1357
|
+
return fail("not-ready");
|
|
1181
1358
|
}
|
|
1182
1359
|
const ed25519PublicKey = session.signingPrivateKey.slice(32, 64);
|
|
1183
1360
|
const encryptionKeyPair = deps.crypto.deriveEncryptionKeypair({
|
|
@@ -1187,22 +1364,17 @@ function createReloginHandler(deps) {
|
|
|
1187
1364
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1188
1365
|
const message = `${userEmail}|${timestamp}`;
|
|
1189
1366
|
const signature = deps.crypto.signMessage(message, session.signingPrivateKey);
|
|
1190
|
-
const ssoState = deps.authState.getSsoState();
|
|
1191
|
-
const isSsoAccount = ssoState.isSsoMode || ssoState.isAuth0Authenticated || !!ssoState.auth0AccessToken;
|
|
1192
1367
|
let ssoToken = null;
|
|
1193
|
-
if (deps.
|
|
1368
|
+
if (deps.authState.isSsoMode()) {
|
|
1194
1369
|
try {
|
|
1195
|
-
ssoToken = await deps.
|
|
1370
|
+
ssoToken = await deps.sso?.getToken() ?? null;
|
|
1196
1371
|
} catch (error) {
|
|
1197
|
-
console.error("[API] Failed to get SSO token
|
|
1372
|
+
console.error("[API] Failed to get SSO token:", error);
|
|
1373
|
+
}
|
|
1374
|
+
if (!ssoToken) {
|
|
1375
|
+
console.warn("[API] Re-login aborted: SSO account but no token available");
|
|
1376
|
+
return fail("refused");
|
|
1198
1377
|
}
|
|
1199
|
-
}
|
|
1200
|
-
if (!ssoToken && ssoState.auth0AccessToken) {
|
|
1201
|
-
ssoToken = ssoState.auth0AccessToken;
|
|
1202
|
-
}
|
|
1203
|
-
if (!ssoToken && isSsoAccount) {
|
|
1204
|
-
console.warn("[API] Re-login aborted: SSO account but no token available");
|
|
1205
|
-
return null;
|
|
1206
1378
|
}
|
|
1207
1379
|
const loginResult = await deps.loginProvider.login({
|
|
1208
1380
|
email: userEmail,
|
|
@@ -1210,11 +1382,15 @@ function createReloginHandler(deps) {
|
|
|
1210
1382
|
timestamp,
|
|
1211
1383
|
ssoToken: ssoToken ?? void 0
|
|
1212
1384
|
});
|
|
1385
|
+
if (loginResult === "unreachable") {
|
|
1386
|
+
console.warn("[API] Re-login failed: server unreachable");
|
|
1387
|
+
return fail("unreachable");
|
|
1388
|
+
}
|
|
1213
1389
|
if (!loginResult) {
|
|
1214
1390
|
console.warn("[API] Re-login failed");
|
|
1215
|
-
return
|
|
1391
|
+
return fail("refused");
|
|
1216
1392
|
}
|
|
1217
|
-
await deps.
|
|
1393
|
+
await deps.persistence.saveSession({
|
|
1218
1394
|
signingPrivateKey: session.signingPrivateKey,
|
|
1219
1395
|
serverSessionKey: loginResult.sessionKey,
|
|
1220
1396
|
userExtId: loginResult.userExtId,
|
|
@@ -1264,7 +1440,7 @@ function createReloginHandler(deps) {
|
|
|
1264
1440
|
console.error("[API] Re-login error:", error);
|
|
1265
1441
|
lastSuccessToken = null;
|
|
1266
1442
|
lastSuccessTime = 0;
|
|
1267
|
-
return
|
|
1443
|
+
return fail("refused");
|
|
1268
1444
|
} finally {
|
|
1269
1445
|
reloginPromise = null;
|
|
1270
1446
|
}
|
|
@@ -1283,7 +1459,7 @@ function createWorkspaceReopener(deps) {
|
|
|
1283
1459
|
const workspaceId = deps.authState.getSelectedWorkspaceId();
|
|
1284
1460
|
const accessToken = deps.getAccessToken();
|
|
1285
1461
|
if (!workspaceId || !accessToken) return false;
|
|
1286
|
-
const session = await deps.
|
|
1462
|
+
const session = await deps.persistence.getSession();
|
|
1287
1463
|
if (!session?.signingPrivateKey || !session.serverSessionKey) return false;
|
|
1288
1464
|
await deps.crypto.ensureReady();
|
|
1289
1465
|
const wrappedKey = await deps.workspaceKeyRefreshProvider.getWrappedKey(
|
|
@@ -1313,29 +1489,56 @@ function createWorkspaceReopener(deps) {
|
|
|
1313
1489
|
return inFlight;
|
|
1314
1490
|
};
|
|
1315
1491
|
}
|
|
1492
|
+
function createAllWorkspacesOpener(deps) {
|
|
1493
|
+
return async function openAllWorkspaces() {
|
|
1494
|
+
const accessToken = deps.getAccessToken();
|
|
1495
|
+
if (!accessToken) return;
|
|
1496
|
+
await deps.crypto.ensureReady();
|
|
1497
|
+
const session = await deps.persistence.getSession();
|
|
1498
|
+
if (!session?.signingPrivateKey) return;
|
|
1499
|
+
const sessionKey = deps.getSessionKey(accessToken);
|
|
1500
|
+
if (!sessionKey) return;
|
|
1501
|
+
const sessionKeyBytes = deps.crypto.fromBase64(sessionKey);
|
|
1502
|
+
let workspaces;
|
|
1503
|
+
try {
|
|
1504
|
+
workspaces = await deps.listWorkspaces(accessToken);
|
|
1505
|
+
} catch {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
for (const ws of workspaces ?? []) {
|
|
1509
|
+
if (!ws.wrapped_key || !ws.external_id) continue;
|
|
1510
|
+
try {
|
|
1511
|
+
const workspaceKey = deps.unwrapWorkspaceKey(ws.wrapped_key, session.signingPrivateKey);
|
|
1512
|
+
const sealed = await deps.crypto.sealKeyForSession(workspaceKey, sessionKeyBytes);
|
|
1513
|
+
await deps.workspaceOpenProvider.openWorkspace(accessToken, ws.external_id, sealed);
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
console.warn(`[API] Failed to open workspace ${ws.external_id}:`, error);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1316
1520
|
|
|
1317
|
-
// src/auth/
|
|
1318
|
-
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
},
|
|
1327
|
-
params.password,
|
|
1328
|
-
deps.deploymentDomain
|
|
1329
|
-
);
|
|
1330
|
-
const response = await deps.fetchClient.POST("/v1/user/register", {
|
|
1331
|
-
body: credentials.request
|
|
1332
|
-
});
|
|
1333
|
-
if (response.error) {
|
|
1334
|
-
throw new Error(
|
|
1335
|
-
`Registration failed: ${typeof response.error === "object" && response.error !== null && "detail" in response.error ? response.error.detail : "Unknown error"}`
|
|
1521
|
+
// src/auth/session-claims.ts
|
|
1522
|
+
function readSessionClaims(token) {
|
|
1523
|
+
const payload = token?.split(".")[1];
|
|
1524
|
+
if (!payload) return null;
|
|
1525
|
+
let claims;
|
|
1526
|
+
try {
|
|
1527
|
+
const binary = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1528
|
+
claims = JSON.parse(
|
|
1529
|
+
new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)))
|
|
1336
1530
|
);
|
|
1531
|
+
} catch {
|
|
1532
|
+
return null;
|
|
1337
1533
|
}
|
|
1338
|
-
|
|
1534
|
+
if (typeof claims !== "object" || claims === null) return null;
|
|
1535
|
+
const { sk, exp } = claims;
|
|
1536
|
+
const sessionKey = typeof sk === "string" && sk ? sk : null;
|
|
1537
|
+
return {
|
|
1538
|
+
sessionKey,
|
|
1539
|
+
sessionId: sessionKey ? sessionKey.slice(0, 12) : null,
|
|
1540
|
+
expiresAt: typeof exp === "number" && Number.isFinite(exp) ? exp * 1e3 : null
|
|
1541
|
+
};
|
|
1339
1542
|
}
|
|
1340
1543
|
|
|
1341
1544
|
// src/auth/identity.ts
|
|
@@ -1362,6 +1565,11 @@ async function unlockIdentityWithPassword(email, password, deploymentDomain) {
|
|
|
1362
1565
|
}
|
|
1363
1566
|
|
|
1364
1567
|
// src/auth/login.ts
|
|
1568
|
+
function authRequestError(what, error, status) {
|
|
1569
|
+
const detail = typeof error === "object" && error !== null && "detail" in error ? error.detail : void 0;
|
|
1570
|
+
const text = typeof detail === "string" ? detail : "Unknown error";
|
|
1571
|
+
return Object.assign(new Error(`${what} failed: ${text}`), { detail, status });
|
|
1572
|
+
}
|
|
1365
1573
|
async function login(params, deps) {
|
|
1366
1574
|
const credentials = await generateLoginCredentials(
|
|
1367
1575
|
{
|
|
@@ -1371,7 +1579,12 @@ async function login(params, deps) {
|
|
|
1371
1579
|
params.password,
|
|
1372
1580
|
deps.deploymentDomain
|
|
1373
1581
|
);
|
|
1374
|
-
return performLogin(
|
|
1582
|
+
return performLogin(
|
|
1583
|
+
credentials.request,
|
|
1584
|
+
{ password: credentials.passwordPrivateKey },
|
|
1585
|
+
deps,
|
|
1586
|
+
params.profile
|
|
1587
|
+
);
|
|
1375
1588
|
}
|
|
1376
1589
|
async function loginWithKey(params, deps) {
|
|
1377
1590
|
const credentials = generateLoginCredentialsFromKey(
|
|
@@ -1383,27 +1596,42 @@ async function loginWithKey(params, deps) {
|
|
|
1383
1596
|
);
|
|
1384
1597
|
return performLogin(credentials.request, { identity: credentials.signingPrivateKey }, deps);
|
|
1385
1598
|
}
|
|
1386
|
-
async function performLogin(request, signer, deps) {
|
|
1599
|
+
async function performLogin(request, signer, deps, profile) {
|
|
1387
1600
|
const response = await deps.fetchClient.POST("/v1/user/login", {
|
|
1388
1601
|
body: request
|
|
1389
1602
|
});
|
|
1390
1603
|
if (response.error || !response.data) {
|
|
1391
|
-
throw
|
|
1392
|
-
`Login failed: ${typeof response.error === "object" && response.error !== null && "detail" in response.error ? response.error.detail : "Unknown error"}`
|
|
1393
|
-
);
|
|
1604
|
+
throw authRequestError("Login", response.error, response.response?.status);
|
|
1394
1605
|
}
|
|
1395
1606
|
const data = response.data;
|
|
1396
1607
|
const serverSessionKey = base64ToBytes(data.session_key);
|
|
1397
1608
|
const signingPrivateKey = "password" in signer ? identityFromLoginResponse(signer.password, data) : signer.identity;
|
|
1398
|
-
await
|
|
1609
|
+
await deps.signIn?.before?.({ user: data.user });
|
|
1610
|
+
await (deps.persistence?.saveSession ?? saveSession)({
|
|
1399
1611
|
signingPrivateKey,
|
|
1400
1612
|
serverSessionKey,
|
|
1401
1613
|
userExtId: data.user.external_id ?? void 0,
|
|
1402
1614
|
wrappedIdentityKey: data.wrapped_identity_key ?? null
|
|
1403
1615
|
});
|
|
1404
|
-
|
|
1405
|
-
deps.session.
|
|
1616
|
+
const email = request.email;
|
|
1617
|
+
deps.session.signIn({
|
|
1618
|
+
accessToken: data.access_token,
|
|
1619
|
+
isSsoMode: !!request.sso_token,
|
|
1620
|
+
serverSessionKey,
|
|
1621
|
+
user: {
|
|
1622
|
+
...data.user,
|
|
1623
|
+
email,
|
|
1624
|
+
given_name: profile?.given_name ?? data.user.given_name ?? email.split("@")[0],
|
|
1625
|
+
family_name: profile?.family_name ?? data.user.family_name,
|
|
1626
|
+
picture: profile?.picture ?? data.user.picture ?? null,
|
|
1627
|
+
parent_ext_id: data.user.parent_ext_id ?? null,
|
|
1628
|
+
is_sso: data.user.is_sso ?? false,
|
|
1629
|
+
is_identity_verified: data.user.is_identity_verified ?? false
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
deps.signIn?.after?.({ response: data, serverSessionKey });
|
|
1406
1633
|
return {
|
|
1634
|
+
response: data,
|
|
1407
1635
|
accessToken: data.access_token,
|
|
1408
1636
|
userExtId: data.user.external_id ?? void 0,
|
|
1409
1637
|
parentExtId: data.user.parent_ext_id ?? null,
|
|
@@ -1420,10 +1648,26 @@ async function performLogin(request, signer, deps) {
|
|
|
1420
1648
|
};
|
|
1421
1649
|
}
|
|
1422
1650
|
|
|
1423
|
-
// src/auth/
|
|
1424
|
-
async function
|
|
1425
|
-
|
|
1426
|
-
|
|
1651
|
+
// src/auth/register.ts
|
|
1652
|
+
async function register(params, deps) {
|
|
1653
|
+
const credentials = await generateRegistrationCredentials(
|
|
1654
|
+
{
|
|
1655
|
+
email: params.email,
|
|
1656
|
+
verification_credential: params.verificationCode,
|
|
1657
|
+
given_name: params.firstName,
|
|
1658
|
+
family_name: params.lastName,
|
|
1659
|
+
picture: params.picture
|
|
1660
|
+
},
|
|
1661
|
+
params.password,
|
|
1662
|
+
deps.deploymentDomain
|
|
1663
|
+
);
|
|
1664
|
+
const response = await deps.fetchClient.POST("/v1/user/register", {
|
|
1665
|
+
body: credentials.request
|
|
1666
|
+
});
|
|
1667
|
+
if (response.error) {
|
|
1668
|
+
throw authRequestError("Registration", response.error, response.response?.status);
|
|
1669
|
+
}
|
|
1670
|
+
return { signingPrivateKey: credentials.signingPrivateKey };
|
|
1427
1671
|
}
|
|
1428
1672
|
|
|
1429
1673
|
// src/auth/change-password.ts
|
|
@@ -1462,29 +1706,6 @@ async function changePassword(params, deps) {
|
|
|
1462
1706
|
return { wrappedIdentityKey: request.wrapped_identity_key };
|
|
1463
1707
|
}
|
|
1464
1708
|
|
|
1465
|
-
// src/auth/session-claims.ts
|
|
1466
|
-
function readSessionClaims(token) {
|
|
1467
|
-
const payload = token?.split(".")[1];
|
|
1468
|
-
if (!payload) return null;
|
|
1469
|
-
let claims;
|
|
1470
|
-
try {
|
|
1471
|
-
const binary = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1472
|
-
claims = JSON.parse(
|
|
1473
|
-
new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)))
|
|
1474
|
-
);
|
|
1475
|
-
} catch {
|
|
1476
|
-
return null;
|
|
1477
|
-
}
|
|
1478
|
-
if (typeof claims !== "object" || claims === null) return null;
|
|
1479
|
-
const { sk, exp } = claims;
|
|
1480
|
-
const sessionKey = typeof sk === "string" && sk ? sk : null;
|
|
1481
|
-
return {
|
|
1482
|
-
sessionKey,
|
|
1483
|
-
sessionId: sessionKey ? sessionKey.slice(0, 12) : null,
|
|
1484
|
-
expiresAt: typeof exp === "number" && Number.isFinite(exp) ? exp * 1e3 : null
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
1709
|
// src/client.ts
|
|
1489
1710
|
var API_PREFIX = "/v1";
|
|
1490
1711
|
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1521,24 +1742,42 @@ function createRetryFetch() {
|
|
|
1521
1742
|
throw lastError;
|
|
1522
1743
|
};
|
|
1523
1744
|
}
|
|
1745
|
+
var HTTP_METHODS = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
1746
|
+
function readBaseUrlPerRequest(fetchClient, base) {
|
|
1747
|
+
const methods = fetchClient;
|
|
1748
|
+
for (const method of HTTP_METHODS) {
|
|
1749
|
+
const call = methods[method].bind(fetchClient);
|
|
1750
|
+
methods[method] = (url, init) => call(url, { ...init, baseUrl: base() });
|
|
1751
|
+
}
|
|
1752
|
+
const request = methods.request.bind(fetchClient);
|
|
1753
|
+
methods.request = (method, url, init) => request(method, url, { ...init, baseUrl: base() });
|
|
1754
|
+
}
|
|
1755
|
+
var deploymentDomainOf = ({ deploymentDomain }) => typeof deploymentDomain === "function" ? deploymentDomain() : deploymentDomain;
|
|
1524
1756
|
function createArbiClient(options) {
|
|
1525
|
-
const { fetch: fetch2, session,
|
|
1526
|
-
const authDeps = {
|
|
1757
|
+
const { fetch: fetch2, session, persistence, workspaceKeys, auth } = createTransport(options);
|
|
1758
|
+
const authDeps = {
|
|
1759
|
+
fetchClient: fetch2,
|
|
1760
|
+
session,
|
|
1761
|
+
get deploymentDomain() {
|
|
1762
|
+
return deploymentDomainOf(options);
|
|
1763
|
+
},
|
|
1764
|
+
persistence,
|
|
1765
|
+
signIn: options.signIn
|
|
1766
|
+
};
|
|
1527
1767
|
return {
|
|
1528
1768
|
fetch: fetch2,
|
|
1769
|
+
workspaceKeys,
|
|
1529
1770
|
session,
|
|
1530
1771
|
auth: {
|
|
1531
1772
|
register: (params) => register(params, authDeps),
|
|
1532
1773
|
login: (params) => login(params, authDeps),
|
|
1533
1774
|
loginWithKey: (params) => loginWithKey(params, authDeps),
|
|
1534
|
-
logout: () => logout(session),
|
|
1535
1775
|
changePassword: (params) => changePassword(params, authDeps),
|
|
1536
|
-
|
|
1537
|
-
reopenWorkspace
|
|
1776
|
+
...auth
|
|
1538
1777
|
},
|
|
1539
1778
|
crypto: {
|
|
1540
1779
|
initSodium,
|
|
1541
|
-
generateUserKeypairs: (email, password) => generateUserKeypairs(email, password, options
|
|
1780
|
+
generateUserKeypairs: (email, password) => generateUserKeypairs(email, password, deploymentDomainOf(options)),
|
|
1542
1781
|
signMessage,
|
|
1543
1782
|
sealedBoxDecrypt,
|
|
1544
1783
|
sealedBoxEncrypt,
|
|
@@ -1564,24 +1803,22 @@ function createArbiClient(options) {
|
|
|
1564
1803
|
}
|
|
1565
1804
|
function createTransport(options) {
|
|
1566
1805
|
const {
|
|
1567
|
-
|
|
1568
|
-
reloginExcludePatterns = [`${API_PREFIX}/user/login`],
|
|
1806
|
+
reloginExcludePatterns = [`${API_PREFIX}/user/login`, `${API_PREFIX}/user/logout`],
|
|
1569
1807
|
credentials = "include",
|
|
1570
|
-
|
|
1808
|
+
sso = null,
|
|
1809
|
+
middleware = [],
|
|
1571
1810
|
onReloginSuccess,
|
|
1572
|
-
|
|
1573
|
-
sessionStorage: sessionStorageOverride,
|
|
1811
|
+
onRetryCloneFailed,
|
|
1574
1812
|
userEmail
|
|
1575
1813
|
} = options;
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
};
|
|
1814
|
+
const persistence = options.persistence ?? { getSession, saveSession };
|
|
1815
|
+
const baseUrlOption = options.baseUrl;
|
|
1816
|
+
const base = typeof baseUrlOption === "function" ? baseUrlOption : () => baseUrlOption;
|
|
1580
1817
|
const retryFetch = createRetryFetch();
|
|
1581
|
-
const session = createSessionManager();
|
|
1818
|
+
const session = options.session ?? createSessionManager();
|
|
1582
1819
|
if (userEmail) session.setUser(userEmail);
|
|
1583
1820
|
const tokenProvider = createTokenProvider(session);
|
|
1584
|
-
const authState = createAuthStateProvider(session);
|
|
1821
|
+
const authState = createAuthStateProvider(session, options.selectedWorkspace);
|
|
1585
1822
|
const cryptoProvider = {
|
|
1586
1823
|
ensureReady: initSodium,
|
|
1587
1824
|
signMessage,
|
|
@@ -1590,11 +1827,10 @@ function createTransport(options) {
|
|
|
1590
1827
|
sealKeyForSession,
|
|
1591
1828
|
fromBase64: base64ToBytes
|
|
1592
1829
|
};
|
|
1593
|
-
let reloginUnreachable = false;
|
|
1594
1830
|
const loginProvider = {
|
|
1595
1831
|
async login(payload) {
|
|
1596
1832
|
const rawFetch = createFetchClient__default.default({
|
|
1597
|
-
baseUrl,
|
|
1833
|
+
baseUrl: base(),
|
|
1598
1834
|
credentials,
|
|
1599
1835
|
fetch: retryFetch
|
|
1600
1836
|
});
|
|
@@ -1610,8 +1846,7 @@ function createTransport(options) {
|
|
|
1610
1846
|
});
|
|
1611
1847
|
} catch (error) {
|
|
1612
1848
|
console.warn("[API] Login call unreachable:", error);
|
|
1613
|
-
|
|
1614
|
-
return null;
|
|
1849
|
+
return "unreachable";
|
|
1615
1850
|
}
|
|
1616
1851
|
if (response.error || !response.data) {
|
|
1617
1852
|
console.warn("[API] Login call failed:", response.error);
|
|
@@ -1626,81 +1861,219 @@ function createTransport(options) {
|
|
|
1626
1861
|
};
|
|
1627
1862
|
}
|
|
1628
1863
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1864
|
+
async function listWorkspaces(accessToken) {
|
|
1865
|
+
const response = await retryFetch(`${base()}${API_PREFIX}/user/workspaces`, {
|
|
1866
|
+
method: "GET",
|
|
1867
|
+
headers: {
|
|
1868
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1869
|
+
"Content-Type": "application/json"
|
|
1870
|
+
},
|
|
1871
|
+
credentials
|
|
1872
|
+
});
|
|
1873
|
+
return response.ok ? response.json() : null;
|
|
1874
|
+
}
|
|
1875
|
+
const workspaceKeys = createWorkspaceKeys({
|
|
1876
|
+
crypto: cryptoProvider,
|
|
1877
|
+
unwrapWorkspaceKey,
|
|
1878
|
+
persistence,
|
|
1879
|
+
getAccessToken: () => session.getState().accessToken,
|
|
1880
|
+
getServerSessionKey: () => session.getState().serverSessionKey,
|
|
1881
|
+
listWorkspaces,
|
|
1882
|
+
async primeKey(accessToken, workspaceId, sealedKey) {
|
|
1883
|
+
const response = await retryFetch(
|
|
1884
|
+
`${base()}${API_PREFIX}/workspace/${workspaceId}/prime-key`,
|
|
1885
|
+
{
|
|
1886
|
+
method: "POST",
|
|
1887
|
+
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
1888
|
+
body: JSON.stringify({ workspace_key: sealedKey }),
|
|
1889
|
+
credentials
|
|
1890
|
+
}
|
|
1643
1891
|
);
|
|
1644
|
-
|
|
1892
|
+
if (!response.ok) throw new Error(`Could not unlock the workspace (${response.status})`);
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
let keysUser = session.getState().signedIn ? session.getState().userExtId : null;
|
|
1896
|
+
session.subscribe((state) => {
|
|
1897
|
+
const user = state.signedIn ? state.userExtId : null;
|
|
1898
|
+
if (user !== keysUser) workspaceKeys.clear();
|
|
1899
|
+
keysUser = user;
|
|
1900
|
+
});
|
|
1901
|
+
const workspaceKeyRefreshProvider = {
|
|
1902
|
+
async getWrappedKey(_accessToken, workspaceId) {
|
|
1903
|
+
return (await workspaceKeys.wrappedKey(workspaceId).catch(() => null))?.wrappedKey ?? null;
|
|
1645
1904
|
}
|
|
1646
1905
|
};
|
|
1647
1906
|
const workspaceOpenProvider = {
|
|
1648
1907
|
async openWorkspace(accessToken, workspaceId, encryptedWorkspaceKey) {
|
|
1908
|
+
const query = "?include_documents=false&include_conversations=false&include_tags=false";
|
|
1649
1909
|
try {
|
|
1650
|
-
const response = await retryFetch(
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1910
|
+
const response = await retryFetch(
|
|
1911
|
+
`${base()}${API_PREFIX}/workspace/${workspaceId}/open${query}`,
|
|
1912
|
+
{
|
|
1913
|
+
method: "POST",
|
|
1914
|
+
headers: {
|
|
1915
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1916
|
+
"Content-Type": "application/json"
|
|
1917
|
+
},
|
|
1918
|
+
body: JSON.stringify({ workspace_key: encryptedWorkspaceKey }),
|
|
1919
|
+
credentials
|
|
1920
|
+
}
|
|
1921
|
+
);
|
|
1659
1922
|
return response.ok ? true : null;
|
|
1660
1923
|
} catch {
|
|
1661
1924
|
return null;
|
|
1662
1925
|
}
|
|
1663
1926
|
}
|
|
1664
1927
|
};
|
|
1665
|
-
|
|
1928
|
+
let lastFailure = null;
|
|
1929
|
+
const expiredListeners = /* @__PURE__ */ new Set();
|
|
1930
|
+
const rawRelogin = createReloginHandler({
|
|
1666
1931
|
crypto: cryptoProvider,
|
|
1667
|
-
|
|
1932
|
+
persistence,
|
|
1668
1933
|
authState,
|
|
1669
|
-
|
|
1934
|
+
sso,
|
|
1670
1935
|
loginProvider,
|
|
1671
1936
|
workspaceKeyRefreshProvider,
|
|
1672
1937
|
workspaceOpenProvider,
|
|
1673
|
-
|
|
1938
|
+
// The new server session key goes into the session before the token: whatever re-renders on
|
|
1939
|
+
// the token reads the key that goes with it.
|
|
1940
|
+
async onReloginSuccess(data) {
|
|
1941
|
+
session.setServerSessionKey(data.serverSessionKey);
|
|
1942
|
+
await onReloginSuccess?.(data);
|
|
1943
|
+
},
|
|
1944
|
+
onReloginFailed: (reason) => {
|
|
1945
|
+
lastFailure = reason;
|
|
1946
|
+
}
|
|
1674
1947
|
});
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1948
|
+
async function relogin() {
|
|
1949
|
+
const tokenBefore = session.getState().accessToken;
|
|
1950
|
+
const token = await rawRelogin();
|
|
1951
|
+
if (token) return token;
|
|
1952
|
+
const tokenNow = session.getState().accessToken;
|
|
1953
|
+
if (tokenNow && tokenNow !== tokenBefore) return tokenNow;
|
|
1954
|
+
if (lastFailure === "refused") {
|
|
1955
|
+
lastFailure = null;
|
|
1956
|
+
session.clear();
|
|
1957
|
+
for (const listener of [...expiredListeners]) listener();
|
|
1958
|
+
}
|
|
1959
|
+
return null;
|
|
1960
|
+
}
|
|
1679
1961
|
const reopenWorkspace = createWorkspaceReopener({
|
|
1680
1962
|
crypto: cryptoProvider,
|
|
1681
|
-
|
|
1963
|
+
persistence,
|
|
1682
1964
|
authState,
|
|
1683
1965
|
getAccessToken: () => session.getState().accessToken,
|
|
1684
1966
|
workspaceKeyRefreshProvider,
|
|
1685
1967
|
workspaceOpenProvider
|
|
1686
1968
|
});
|
|
1969
|
+
const openAllWorkspaces = createAllWorkspacesOpener({
|
|
1970
|
+
crypto: cryptoProvider,
|
|
1971
|
+
persistence,
|
|
1972
|
+
getAccessToken: () => session.getState().accessToken,
|
|
1973
|
+
getSessionKey: (accessToken) => readSessionClaims(accessToken)?.sessionKey ?? null,
|
|
1974
|
+
unwrapWorkspaceKey,
|
|
1975
|
+
listWorkspaces: async (accessToken) => {
|
|
1976
|
+
const list = await listWorkspaces(accessToken);
|
|
1977
|
+
return list && workspaceKeys.remember(list);
|
|
1978
|
+
},
|
|
1979
|
+
workspaceOpenProvider
|
|
1980
|
+
});
|
|
1687
1981
|
const fetchClient = createFetchClient__default.default({
|
|
1688
|
-
baseUrl,
|
|
1982
|
+
baseUrl: base(),
|
|
1689
1983
|
credentials,
|
|
1690
1984
|
fetch: retryFetch
|
|
1691
1985
|
});
|
|
1986
|
+
if (typeof baseUrlOption === "function") readBaseUrlPerRequest(fetchClient, base);
|
|
1692
1987
|
fetchClient.use(createBearerAuthMiddleware({ tokenProvider }));
|
|
1988
|
+
for (const m of middleware) fetchClient.use(m);
|
|
1693
1989
|
fetchClient.use(
|
|
1694
1990
|
createAutoReloginMiddleware({
|
|
1695
|
-
reloginHandler,
|
|
1991
|
+
reloginHandler: relogin,
|
|
1696
1992
|
reloginExcludePatterns,
|
|
1697
1993
|
reopenWorkspaceHandler: reopenWorkspace,
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1994
|
+
primeWorkspaceHandler: (workspaceId) => workspaceKeys.prime(workspaceId, { force: true }).then(
|
|
1995
|
+
() => true,
|
|
1996
|
+
(error) => {
|
|
1997
|
+
console.warn("[API] Could not unlock workspace", workspaceId, error);
|
|
1998
|
+
return false;
|
|
1999
|
+
}
|
|
2000
|
+
),
|
|
2001
|
+
onRetryCloneFailed
|
|
1701
2002
|
})
|
|
1702
2003
|
);
|
|
1703
|
-
return {
|
|
2004
|
+
return {
|
|
2005
|
+
fetch: fetchClient,
|
|
2006
|
+
session,
|
|
2007
|
+
persistence,
|
|
2008
|
+
workspaceKeys,
|
|
2009
|
+
auth: {
|
|
2010
|
+
relogin,
|
|
2011
|
+
reopenWorkspace,
|
|
2012
|
+
openAllWorkspaces,
|
|
2013
|
+
async logout({ everywhere = false } = {}) {
|
|
2014
|
+
if (session.getState().accessToken) {
|
|
2015
|
+
try {
|
|
2016
|
+
await fetchClient.POST("/v1/user/logout", {
|
|
2017
|
+
params: { query: { all_sessions: everywhere } }
|
|
2018
|
+
});
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
console.warn("[API] Server logout failed:", error);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
session.clear();
|
|
2024
|
+
if (everywhere) await clearAllData();
|
|
2025
|
+
},
|
|
2026
|
+
async unlock({ email, password }) {
|
|
2027
|
+
const saved = await persistence.getSession();
|
|
2028
|
+
if (!saved?.signingPrivateKey) throw new Error("no-session");
|
|
2029
|
+
let signingPrivateKey;
|
|
2030
|
+
try {
|
|
2031
|
+
signingPrivateKey = await unlockIdentityWithPassword(
|
|
2032
|
+
email,
|
|
2033
|
+
password,
|
|
2034
|
+
deploymentDomainOf(options)
|
|
2035
|
+
);
|
|
2036
|
+
} catch {
|
|
2037
|
+
throw new Error("bad-password");
|
|
2038
|
+
}
|
|
2039
|
+
const { publicKey } = encryptionKeypairFromSigningKey(signingPrivateKey);
|
|
2040
|
+
session.signIn({
|
|
2041
|
+
accessToken: null,
|
|
2042
|
+
isSsoMode: false,
|
|
2043
|
+
serverSessionKey: saved.serverSessionKey ?? null,
|
|
2044
|
+
user: {
|
|
2045
|
+
email,
|
|
2046
|
+
given_name: email.split("@")[0],
|
|
2047
|
+
family_name: null,
|
|
2048
|
+
picture: null,
|
|
2049
|
+
external_id: saved.userExtId ?? "",
|
|
2050
|
+
parent_ext_id: null,
|
|
2051
|
+
encryption_public_key: bytesToBase64(publicKey),
|
|
2052
|
+
is_sso: false,
|
|
2053
|
+
is_identity_verified: false,
|
|
2054
|
+
temporary_access_only: false
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
},
|
|
2058
|
+
async restore() {
|
|
2059
|
+
if (!session.getState().signedIn) return false;
|
|
2060
|
+
const keys = await persistence.getSession().catch(() => null);
|
|
2061
|
+
if (session.getState().serverSessionKey) return true;
|
|
2062
|
+
if (keys?.signingPrivateKey && keys.serverSessionKey) {
|
|
2063
|
+
session.setServerSessionKey(keys.serverSessionKey);
|
|
2064
|
+
return true;
|
|
2065
|
+
}
|
|
2066
|
+
session.clear();
|
|
2067
|
+
return false;
|
|
2068
|
+
},
|
|
2069
|
+
onSessionExpired(listener) {
|
|
2070
|
+
expiredListeners.add(listener);
|
|
2071
|
+
return () => {
|
|
2072
|
+
expiredListeners.delete(listener);
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
1704
2077
|
}
|
|
1705
2078
|
|
|
1706
2079
|
// src/websocket.ts
|
|
@@ -1728,6 +2101,7 @@ function isMessageType(msg, type) {
|
|
|
1728
2101
|
return msg.type === type;
|
|
1729
2102
|
}
|
|
1730
2103
|
|
|
2104
|
+
exports.ACCESS_TOKEN_STORAGE_KEY = ACCESS_TOKEN_STORAGE_KEY;
|
|
1731
2105
|
exports.API_PREFIX = API_PREFIX;
|
|
1732
2106
|
exports.CALL_KEY_BYTES = CALL_KEY_BYTES;
|
|
1733
2107
|
exports.CHAT_KEY_BYTES = CHAT_KEY_BYTES;
|
|
@@ -1735,11 +2109,14 @@ exports.ENCRYPTED_RECOVERY_KEY_SIZE = ENCRYPTED_RECOVERY_KEY_SIZE;
|
|
|
1735
2109
|
exports.RECOVERY_KEY_ERRORS = RECOVERY_KEY_ERRORS;
|
|
1736
2110
|
exports.SECURE_MAIL_KEY_BYTES = SECURE_MAIL_KEY_BYTES;
|
|
1737
2111
|
exports.UNENCRYPTED_RECOVERY_KEY_SIZE = UNENCRYPTED_RECOVERY_KEY_SIZE;
|
|
2112
|
+
exports.authRequestError = authRequestError;
|
|
1738
2113
|
exports.base64Decode = base64Decode;
|
|
1739
2114
|
exports.base64Encode = base64Encode;
|
|
1740
2115
|
exports.base64ToBytes = base64ToBytes;
|
|
1741
2116
|
exports.base64UrlToBytes = base64UrlToBytes;
|
|
2117
|
+
exports.browserSessionStateStorage = browserSessionStateStorage;
|
|
1742
2118
|
exports.buildCallFragment = buildCallFragment;
|
|
2119
|
+
exports.buildCallLink = buildCallLink;
|
|
1743
2120
|
exports.buildWebSocketUrl = buildWebSocketUrl;
|
|
1744
2121
|
exports.bytesToBase64 = bytesToBase64;
|
|
1745
2122
|
exports.bytesToBase64Url = bytesToBase64Url;
|
|
@@ -1748,6 +2125,7 @@ exports.changePassword = changePassword;
|
|
|
1748
2125
|
exports.clearAllData = clearAllData;
|
|
1749
2126
|
exports.clearSession = clearSession;
|
|
1750
2127
|
exports.computeSharedSecret = computeSharedSecret;
|
|
2128
|
+
exports.createAllWorkspacesOpener = createAllWorkspacesOpener;
|
|
1751
2129
|
exports.createArbiClient = createArbiClient;
|
|
1752
2130
|
exports.createAuthMessage = createAuthMessage;
|
|
1753
2131
|
exports.createAutoReloginMiddleware = createAutoReloginMiddleware;
|
|
@@ -1758,6 +2136,7 @@ exports.createRecoveryFile = createRecoveryFile;
|
|
|
1758
2136
|
exports.createRecoveryUrl = createRecoveryUrl;
|
|
1759
2137
|
exports.createReloginHandler = createReloginHandler;
|
|
1760
2138
|
exports.createSessionManager = createSessionManager;
|
|
2139
|
+
exports.createWorkspaceKeys = createWorkspaceKeys;
|
|
1761
2140
|
exports.createWorkspaceReopener = createWorkspaceReopener;
|
|
1762
2141
|
exports.decodeCallKey = decodeCallKey;
|
|
1763
2142
|
exports.decodeSecureMailKey = decodeSecureMailKey;
|
|
@@ -1797,6 +2176,7 @@ exports.initSodium = initSodium;
|
|
|
1797
2176
|
exports.initializeDatabase = initializeDatabase;
|
|
1798
2177
|
exports.isEncryptedRecoveryKey = isEncryptedRecoveryKey;
|
|
1799
2178
|
exports.isMessageType = isMessageType;
|
|
2179
|
+
exports.openCallKeyOf = openCallKeyOf;
|
|
1800
2180
|
exports.openSealedCallKey = openSealedCallKey;
|
|
1801
2181
|
exports.openSealedChatKey = openSealedChatKey;
|
|
1802
2182
|
exports.openSecureMail = openSecureMail;
|
|
@@ -1808,6 +2188,7 @@ exports.prewarmKeyPairs = prewarmKeyPairs;
|
|
|
1808
2188
|
exports.readSessionClaims = readSessionClaims;
|
|
1809
2189
|
exports.saveSession = saveSession;
|
|
1810
2190
|
exports.sealCallKey = sealCallKey;
|
|
2191
|
+
exports.sealCallKeyForPeers = sealCallKeyForPeers;
|
|
1811
2192
|
exports.sealChatKey = sealChatKey;
|
|
1812
2193
|
exports.sealKeyForSession = sealKeyForSession;
|
|
1813
2194
|
exports.sealSecureMail = sealSecureMail;
|
|
@@ -1820,6 +2201,7 @@ exports.unwrapIdentityKey = unwrapIdentityKey;
|
|
|
1820
2201
|
exports.unwrapWorkspaceKey = unwrapWorkspaceKey;
|
|
1821
2202
|
exports.updateWrappedIdentityKey = updateWrappedIdentityKey;
|
|
1822
2203
|
exports.validateRecoveryKeyFile = validateRecoveryKeyFile;
|
|
2204
|
+
exports.workspaceKeyRequiredOf = workspaceKeyRequiredOf;
|
|
1823
2205
|
exports.wrapIdentityKey = wrapIdentityKey;
|
|
1824
2206
|
//# sourceMappingURL=index.cjs.map
|
|
1825
2207
|
//# sourceMappingURL=index.cjs.map
|