@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.js
CHANGED
|
@@ -4,55 +4,67 @@ import sodium from 'libsodium-wrappers-sumo';
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
|
|
6
6
|
// src/session.ts
|
|
7
|
-
function createInitialState() {
|
|
7
|
+
function createInitialState(isSsoMode = false) {
|
|
8
8
|
return {
|
|
9
9
|
accessToken: null,
|
|
10
10
|
userEmail: null,
|
|
11
11
|
userExtId: null,
|
|
12
12
|
selectedWorkspaceId: null,
|
|
13
|
-
isSsoMode
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
isSsoMode,
|
|
14
|
+
user: null,
|
|
15
|
+
signedIn: false,
|
|
16
|
+
serverSessionKey: null
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
var stored = (s) => ({
|
|
20
|
+
accessToken: s.accessToken,
|
|
21
|
+
userEmail: s.userEmail,
|
|
22
|
+
userExtId: s.userExtId,
|
|
23
|
+
isSsoMode: s.isSsoMode,
|
|
24
|
+
user: s.user,
|
|
25
|
+
signedIn: s.signedIn
|
|
26
|
+
});
|
|
27
|
+
function createSessionManager(options = {}) {
|
|
28
|
+
const { storage } = options;
|
|
29
|
+
let state = { ...createInitialState(), ...storage?.load() ?? {} };
|
|
20
30
|
const listeners = /* @__PURE__ */ new Set();
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
function update(patch) {
|
|
32
|
+
state = { ...state, ...patch };
|
|
33
|
+
storage?.save(stored(state));
|
|
34
|
+
const snapshot = state;
|
|
35
|
+
for (const listener of [...listeners]) listener(snapshot);
|
|
26
36
|
}
|
|
27
37
|
return {
|
|
28
|
-
getState()
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
setAccessToken(token) {
|
|
32
|
-
state = { ...state, accessToken: token };
|
|
33
|
-
notify();
|
|
34
|
-
},
|
|
38
|
+
getState: () => state,
|
|
39
|
+
setAccessToken: (accessToken) => update({ accessToken }),
|
|
35
40
|
setUser(email, extId) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
notify();
|
|
41
|
+
update({
|
|
42
|
+
userEmail: email,
|
|
43
|
+
userExtId: extId ?? state.userExtId,
|
|
44
|
+
user: state.user ? { ...state.user, email } : null
|
|
45
|
+
});
|
|
42
46
|
},
|
|
43
|
-
|
|
44
|
-
state
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
isAuth0Authenticated: opts.isAuth0Authenticated ?? state.isAuth0Authenticated,
|
|
48
|
-
auth0AccessToken: opts.auth0AccessToken !== void 0 ? opts.auth0AccessToken : state.auth0AccessToken
|
|
49
|
-
};
|
|
50
|
-
notify();
|
|
47
|
+
updateUser(patch) {
|
|
48
|
+
if (!state.user) return;
|
|
49
|
+
const user = { ...state.user, ...patch };
|
|
50
|
+
update({ user, userEmail: user.email });
|
|
51
51
|
},
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
setSelectedWorkspace: (selectedWorkspaceId) => update({ selectedWorkspaceId }),
|
|
53
|
+
setSsoMode: (isSsoMode) => update({ isSsoMode }),
|
|
54
|
+
setServerSessionKey: (serverSessionKey) => update({ serverSessionKey }),
|
|
55
|
+
signIn({ accessToken, user, isSsoMode, serverSessionKey }) {
|
|
56
|
+
update({
|
|
57
|
+
accessToken,
|
|
58
|
+
user,
|
|
59
|
+
userEmail: user.email,
|
|
60
|
+
userExtId: user.external_id,
|
|
61
|
+
isSsoMode,
|
|
62
|
+
serverSessionKey,
|
|
63
|
+
signedIn: true
|
|
64
|
+
});
|
|
55
65
|
},
|
|
66
|
+
// The SSO mode outlives a sign-out: it says how this deployment (or this user) signs in.
|
|
67
|
+
clear: () => update(createInitialState(state.isSsoMode)),
|
|
56
68
|
subscribe(listener) {
|
|
57
69
|
listeners.add(listener);
|
|
58
70
|
return () => {
|
|
@@ -61,23 +73,41 @@ function createSessionManager() {
|
|
|
61
73
|
}
|
|
62
74
|
};
|
|
63
75
|
}
|
|
76
|
+
var ACCESS_TOKEN_STORAGE_KEY = "arbi_access_token";
|
|
77
|
+
var SESSION_STORAGE_KEY = "arbi_session";
|
|
78
|
+
function browserSessionStateStorage() {
|
|
79
|
+
return {
|
|
80
|
+
load() {
|
|
81
|
+
try {
|
|
82
|
+
const rest = JSON.parse(window.localStorage.getItem(SESSION_STORAGE_KEY) ?? "null");
|
|
83
|
+
const accessToken = window.sessionStorage.getItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
84
|
+
return { ...rest ?? {}, accessToken };
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
save({ accessToken, ...rest }) {
|
|
90
|
+
try {
|
|
91
|
+
if (accessToken) window.sessionStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, accessToken);
|
|
92
|
+
else window.sessionStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
93
|
+
if (rest.signedIn || rest.isSsoMode)
|
|
94
|
+
window.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(rest));
|
|
95
|
+
else window.localStorage.removeItem(SESSION_STORAGE_KEY);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
64
101
|
function createTokenProvider(session) {
|
|
65
102
|
return {
|
|
66
103
|
getAccessToken: () => session.getState().accessToken
|
|
67
104
|
};
|
|
68
105
|
}
|
|
69
|
-
function createAuthStateProvider(session) {
|
|
106
|
+
function createAuthStateProvider(session, selectedWorkspace = () => session.getState().selectedWorkspaceId) {
|
|
70
107
|
return {
|
|
71
108
|
getUserEmail: () => session.getState().userEmail,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
isSsoMode: s.isSsoMode,
|
|
76
|
-
isAuth0Authenticated: s.isAuth0Authenticated,
|
|
77
|
-
auth0AccessToken: s.auth0AccessToken
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
getSelectedWorkspaceId: () => session.getState().selectedWorkspaceId,
|
|
109
|
+
isSsoMode: () => session.getState().isSsoMode,
|
|
110
|
+
getSelectedWorkspaceId: selectedWorkspace,
|
|
81
111
|
setAccessToken: (token) => session.setAccessToken(token)
|
|
82
112
|
};
|
|
83
113
|
}
|
|
@@ -356,6 +386,25 @@ function parseCallFragment(hash) {
|
|
|
356
386
|
const v = Number(params.get("v"));
|
|
357
387
|
return { key, linkVersion: Number.isInteger(v) && v > 0 ? v : null };
|
|
358
388
|
}
|
|
389
|
+
function buildCallLink(origin, callExtId, key, linkVersion) {
|
|
390
|
+
return `${origin}/call/${callExtId}#${buildCallFragment(key, linkVersion)}`;
|
|
391
|
+
}
|
|
392
|
+
async function sealCallKeyForPeers(key, peers, exclude) {
|
|
393
|
+
const out = {};
|
|
394
|
+
for (const peer of peers) {
|
|
395
|
+
if (!peer.encryption_public_key || peer.external_id === exclude) continue;
|
|
396
|
+
out[peer.external_id] = await sealCallKey(key, peer.encryption_public_key);
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
async function openCallKeyOf(call, encryptionSecretKey) {
|
|
401
|
+
if (!call.my_sealed_key) return null;
|
|
402
|
+
try {
|
|
403
|
+
return await openSealedCallKey(call.my_sealed_key, encryptionSecretKey);
|
|
404
|
+
} catch {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
359
408
|
var CHAT_KEY_BYTES = 32;
|
|
360
409
|
async function deriveDirectChatKey(peerPublicKeyBase64, mySecretKey) {
|
|
361
410
|
await initSodium();
|
|
@@ -1060,8 +1109,126 @@ function createBearerAuthMiddleware(config) {
|
|
|
1060
1109
|
};
|
|
1061
1110
|
}
|
|
1062
1111
|
|
|
1112
|
+
// src/workspace-keys.ts
|
|
1113
|
+
function createWorkspaceKeys(deps) {
|
|
1114
|
+
const wrapped = /* @__PURE__ */ new Map();
|
|
1115
|
+
const keyless = /* @__PURE__ */ new Set();
|
|
1116
|
+
let listing = null;
|
|
1117
|
+
const primed = /* @__PURE__ */ new Map();
|
|
1118
|
+
let primedFor = null;
|
|
1119
|
+
function remember(workspaces) {
|
|
1120
|
+
for (const ws of workspaces) {
|
|
1121
|
+
if (ws.wrapped_key) {
|
|
1122
|
+
wrapped.set(ws.external_id, ws.wrapped_key);
|
|
1123
|
+
keyless.delete(ws.external_id);
|
|
1124
|
+
} else if (ws.is_public || ws.is_member) keyless.add(ws.external_id);
|
|
1125
|
+
}
|
|
1126
|
+
return workspaces;
|
|
1127
|
+
}
|
|
1128
|
+
function readList() {
|
|
1129
|
+
listing ??= (async () => {
|
|
1130
|
+
try {
|
|
1131
|
+
const token = deps.getAccessToken();
|
|
1132
|
+
if (!token) throw new Error("Not signed in");
|
|
1133
|
+
const list = await deps.listWorkspaces(token);
|
|
1134
|
+
if (!list) throw new Error("Failed to fetch workspaces");
|
|
1135
|
+
remember(list);
|
|
1136
|
+
} finally {
|
|
1137
|
+
listing = null;
|
|
1138
|
+
}
|
|
1139
|
+
})();
|
|
1140
|
+
return listing;
|
|
1141
|
+
}
|
|
1142
|
+
async function wrappedKey(workspaceId, { refresh = false } = {}) {
|
|
1143
|
+
if (!refresh) {
|
|
1144
|
+
const cached = wrapped.get(workspaceId);
|
|
1145
|
+
if (cached) return { wrappedKey: cached, fromCache: true };
|
|
1146
|
+
if (keyless.has(workspaceId)) return { wrappedKey: null, fromCache: true };
|
|
1147
|
+
}
|
|
1148
|
+
wrapped.delete(workspaceId);
|
|
1149
|
+
keyless.delete(workspaceId);
|
|
1150
|
+
await readList();
|
|
1151
|
+
const fresh = wrapped.get(workspaceId);
|
|
1152
|
+
if (fresh) return { wrappedKey: fresh, fromCache: false };
|
|
1153
|
+
if (keyless.has(workspaceId)) return { wrappedKey: null, fromCache: false };
|
|
1154
|
+
throw new Error(`Workspace ${workspaceId} not found or has no encryption key`);
|
|
1155
|
+
}
|
|
1156
|
+
async function sealedFor(workspaceId, serverSessionKey2, refresh) {
|
|
1157
|
+
const stored2 = await deps.persistence.getSession();
|
|
1158
|
+
if (!stored2?.signingPrivateKey) throw new Error("No stored identity to unlock the workspace");
|
|
1159
|
+
const { wrappedKey: key, fromCache } = await wrappedKey(workspaceId, { refresh });
|
|
1160
|
+
if (!key) throw new Error(`Workspace ${workspaceId} is public: it has no key to deposit`);
|
|
1161
|
+
try {
|
|
1162
|
+
await deps.crypto.ensureReady();
|
|
1163
|
+
return await deps.crypto.sealKeyForSession(
|
|
1164
|
+
deps.unwrapWorkspaceKey(key, stored2.signingPrivateKey),
|
|
1165
|
+
serverSessionKey2
|
|
1166
|
+
);
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
if (!fromCache) throw error;
|
|
1169
|
+
return sealedFor(workspaceId, serverSessionKey2, true);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async function serverSessionKey() {
|
|
1173
|
+
const key = deps.getServerSessionKey() ?? (await deps.persistence.getSession())?.serverSessionKey;
|
|
1174
|
+
if (!key) throw new Error("No server session to unlock the workspace for");
|
|
1175
|
+
return key;
|
|
1176
|
+
}
|
|
1177
|
+
function prime(workspaceId, { force = false } = {}) {
|
|
1178
|
+
const sessionKey = deps.getServerSessionKey();
|
|
1179
|
+
if (sessionKey !== primedFor) {
|
|
1180
|
+
primed.clear();
|
|
1181
|
+
primedFor = sessionKey;
|
|
1182
|
+
}
|
|
1183
|
+
const pending = primed.get(workspaceId);
|
|
1184
|
+
if (pending && !force) return pending;
|
|
1185
|
+
const run = (async () => {
|
|
1186
|
+
const token = deps.getAccessToken();
|
|
1187
|
+
if (!token) throw new Error("Not signed in");
|
|
1188
|
+
const sealed = await sealedFor(workspaceId, await serverSessionKey(), false);
|
|
1189
|
+
await deps.primeKey(token, workspaceId, sealed);
|
|
1190
|
+
})();
|
|
1191
|
+
primed.set(workspaceId, run);
|
|
1192
|
+
run.catch(() => {
|
|
1193
|
+
if (primed.get(workspaceId) === run) primed.delete(workspaceId);
|
|
1194
|
+
});
|
|
1195
|
+
return run;
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
remember,
|
|
1199
|
+
wrappedKey,
|
|
1200
|
+
prime,
|
|
1201
|
+
forget(workspaceIds) {
|
|
1202
|
+
for (const id of workspaceIds) {
|
|
1203
|
+
wrapped.delete(id);
|
|
1204
|
+
keyless.delete(id);
|
|
1205
|
+
primed.delete(id);
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
clear() {
|
|
1209
|
+
wrapped.clear();
|
|
1210
|
+
keyless.clear();
|
|
1211
|
+
primed.clear();
|
|
1212
|
+
primedFor = null;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function workspaceKeyRequiredOf(body) {
|
|
1217
|
+
const detail = body?.detail;
|
|
1218
|
+
if (!detail || typeof detail !== "object") return null;
|
|
1219
|
+
const d = detail;
|
|
1220
|
+
return d.code === "workspace_key_required" && typeof d.workspace_ext_id === "string" ? d.workspace_ext_id : null;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1063
1223
|
// src/middleware/auto-relogin.ts
|
|
1064
1224
|
var NO_ACTIVE_WORKSPACE = /no active workspace/i;
|
|
1225
|
+
async function keyRequiredWorkspace(response) {
|
|
1226
|
+
try {
|
|
1227
|
+
return workspaceKeyRequiredOf(await response.clone().json());
|
|
1228
|
+
} catch {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1065
1232
|
async function isNoActiveWorkspace(response) {
|
|
1066
1233
|
try {
|
|
1067
1234
|
return NO_ACTIVE_WORKSPACE.test(await response.clone().text());
|
|
@@ -1086,6 +1253,16 @@ function createAutoReloginMiddleware(config) {
|
|
|
1086
1253
|
}
|
|
1087
1254
|
},
|
|
1088
1255
|
async onResponse({ response, request }) {
|
|
1256
|
+
if (response.status === 409 && config.primeWorkspaceHandler) {
|
|
1257
|
+
const workspaceId = await keyRequiredWorkspace(response);
|
|
1258
|
+
if (workspaceId) {
|
|
1259
|
+
if (!await config.primeWorkspaceHandler(workspaceId)) return response;
|
|
1260
|
+
const retry = cloneCache.get(request) ?? safeClone(request);
|
|
1261
|
+
if (retry) return fetch(retry);
|
|
1262
|
+
config.onRetryCloneFailed?.();
|
|
1263
|
+
return response;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1089
1266
|
if (response.status === 400 && await isNoActiveWorkspace(response)) {
|
|
1090
1267
|
let token = null;
|
|
1091
1268
|
let recovered = await config.reopenWorkspaceHandler?.() ?? false;
|
|
@@ -1093,10 +1270,7 @@ function createAutoReloginMiddleware(config) {
|
|
|
1093
1270
|
token = await config.reloginHandler();
|
|
1094
1271
|
recovered = token !== null;
|
|
1095
1272
|
}
|
|
1096
|
-
if (!recovered)
|
|
1097
|
-
config.onReloginFailed?.();
|
|
1098
|
-
return response;
|
|
1099
|
-
}
|
|
1273
|
+
if (!recovered) return response;
|
|
1100
1274
|
const retry = cloneCache.get(request) ?? safeClone(request);
|
|
1101
1275
|
if (retry) {
|
|
1102
1276
|
if (token) retry.headers.set("Authorization", `Bearer ${token}`);
|
|
@@ -1135,8 +1309,6 @@ function createAutoReloginMiddleware(config) {
|
|
|
1135
1309
|
console.warn("[API] Could not retry request after re-login (body already consumed)");
|
|
1136
1310
|
config.onRetryCloneFailed?.();
|
|
1137
1311
|
}
|
|
1138
|
-
} else {
|
|
1139
|
-
config.onReloginFailed?.();
|
|
1140
1312
|
}
|
|
1141
1313
|
}
|
|
1142
1314
|
}
|
|
@@ -1162,15 +1334,20 @@ function createReloginHandler(deps) {
|
|
|
1162
1334
|
const userEmail = deps.authState.getUserEmail();
|
|
1163
1335
|
if (!userEmail) {
|
|
1164
1336
|
console.warn("[API] Re-login aborted: no user email");
|
|
1337
|
+
deps.onReloginFailed?.("not-ready");
|
|
1165
1338
|
return Promise.resolve(null);
|
|
1166
1339
|
}
|
|
1340
|
+
const fail = (reason) => {
|
|
1341
|
+
deps.onReloginFailed?.(reason);
|
|
1342
|
+
return null;
|
|
1343
|
+
};
|
|
1167
1344
|
reloginPromise = (async () => {
|
|
1168
1345
|
try {
|
|
1169
1346
|
await deps.crypto.ensureReady();
|
|
1170
|
-
const session = await deps.
|
|
1347
|
+
const session = await deps.persistence.getSession();
|
|
1171
1348
|
if (!session) {
|
|
1172
1349
|
console.warn("[API] Re-login aborted: no session");
|
|
1173
|
-
return
|
|
1350
|
+
return fail("not-ready");
|
|
1174
1351
|
}
|
|
1175
1352
|
const ed25519PublicKey = session.signingPrivateKey.slice(32, 64);
|
|
1176
1353
|
const encryptionKeyPair = deps.crypto.deriveEncryptionKeypair({
|
|
@@ -1180,22 +1357,17 @@ function createReloginHandler(deps) {
|
|
|
1180
1357
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1181
1358
|
const message = `${userEmail}|${timestamp}`;
|
|
1182
1359
|
const signature = deps.crypto.signMessage(message, session.signingPrivateKey);
|
|
1183
|
-
const ssoState = deps.authState.getSsoState();
|
|
1184
|
-
const isSsoAccount = ssoState.isSsoMode || ssoState.isAuth0Authenticated || !!ssoState.auth0AccessToken;
|
|
1185
1360
|
let ssoToken = null;
|
|
1186
|
-
if (deps.
|
|
1361
|
+
if (deps.authState.isSsoMode()) {
|
|
1187
1362
|
try {
|
|
1188
|
-
ssoToken = await deps.
|
|
1363
|
+
ssoToken = await deps.sso?.getToken() ?? null;
|
|
1189
1364
|
} catch (error) {
|
|
1190
|
-
console.error("[API] Failed to get SSO token
|
|
1365
|
+
console.error("[API] Failed to get SSO token:", error);
|
|
1366
|
+
}
|
|
1367
|
+
if (!ssoToken) {
|
|
1368
|
+
console.warn("[API] Re-login aborted: SSO account but no token available");
|
|
1369
|
+
return fail("refused");
|
|
1191
1370
|
}
|
|
1192
|
-
}
|
|
1193
|
-
if (!ssoToken && ssoState.auth0AccessToken) {
|
|
1194
|
-
ssoToken = ssoState.auth0AccessToken;
|
|
1195
|
-
}
|
|
1196
|
-
if (!ssoToken && isSsoAccount) {
|
|
1197
|
-
console.warn("[API] Re-login aborted: SSO account but no token available");
|
|
1198
|
-
return null;
|
|
1199
1371
|
}
|
|
1200
1372
|
const loginResult = await deps.loginProvider.login({
|
|
1201
1373
|
email: userEmail,
|
|
@@ -1203,11 +1375,15 @@ function createReloginHandler(deps) {
|
|
|
1203
1375
|
timestamp,
|
|
1204
1376
|
ssoToken: ssoToken ?? void 0
|
|
1205
1377
|
});
|
|
1378
|
+
if (loginResult === "unreachable") {
|
|
1379
|
+
console.warn("[API] Re-login failed: server unreachable");
|
|
1380
|
+
return fail("unreachable");
|
|
1381
|
+
}
|
|
1206
1382
|
if (!loginResult) {
|
|
1207
1383
|
console.warn("[API] Re-login failed");
|
|
1208
|
-
return
|
|
1384
|
+
return fail("refused");
|
|
1209
1385
|
}
|
|
1210
|
-
await deps.
|
|
1386
|
+
await deps.persistence.saveSession({
|
|
1211
1387
|
signingPrivateKey: session.signingPrivateKey,
|
|
1212
1388
|
serverSessionKey: loginResult.sessionKey,
|
|
1213
1389
|
userExtId: loginResult.userExtId,
|
|
@@ -1257,7 +1433,7 @@ function createReloginHandler(deps) {
|
|
|
1257
1433
|
console.error("[API] Re-login error:", error);
|
|
1258
1434
|
lastSuccessToken = null;
|
|
1259
1435
|
lastSuccessTime = 0;
|
|
1260
|
-
return
|
|
1436
|
+
return fail("refused");
|
|
1261
1437
|
} finally {
|
|
1262
1438
|
reloginPromise = null;
|
|
1263
1439
|
}
|
|
@@ -1276,7 +1452,7 @@ function createWorkspaceReopener(deps) {
|
|
|
1276
1452
|
const workspaceId = deps.authState.getSelectedWorkspaceId();
|
|
1277
1453
|
const accessToken = deps.getAccessToken();
|
|
1278
1454
|
if (!workspaceId || !accessToken) return false;
|
|
1279
|
-
const session = await deps.
|
|
1455
|
+
const session = await deps.persistence.getSession();
|
|
1280
1456
|
if (!session?.signingPrivateKey || !session.serverSessionKey) return false;
|
|
1281
1457
|
await deps.crypto.ensureReady();
|
|
1282
1458
|
const wrappedKey = await deps.workspaceKeyRefreshProvider.getWrappedKey(
|
|
@@ -1306,29 +1482,56 @@ function createWorkspaceReopener(deps) {
|
|
|
1306
1482
|
return inFlight;
|
|
1307
1483
|
};
|
|
1308
1484
|
}
|
|
1485
|
+
function createAllWorkspacesOpener(deps) {
|
|
1486
|
+
return async function openAllWorkspaces() {
|
|
1487
|
+
const accessToken = deps.getAccessToken();
|
|
1488
|
+
if (!accessToken) return;
|
|
1489
|
+
await deps.crypto.ensureReady();
|
|
1490
|
+
const session = await deps.persistence.getSession();
|
|
1491
|
+
if (!session?.signingPrivateKey) return;
|
|
1492
|
+
const sessionKey = deps.getSessionKey(accessToken);
|
|
1493
|
+
if (!sessionKey) return;
|
|
1494
|
+
const sessionKeyBytes = deps.crypto.fromBase64(sessionKey);
|
|
1495
|
+
let workspaces;
|
|
1496
|
+
try {
|
|
1497
|
+
workspaces = await deps.listWorkspaces(accessToken);
|
|
1498
|
+
} catch {
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
for (const ws of workspaces ?? []) {
|
|
1502
|
+
if (!ws.wrapped_key || !ws.external_id) continue;
|
|
1503
|
+
try {
|
|
1504
|
+
const workspaceKey = deps.unwrapWorkspaceKey(ws.wrapped_key, session.signingPrivateKey);
|
|
1505
|
+
const sealed = await deps.crypto.sealKeyForSession(workspaceKey, sessionKeyBytes);
|
|
1506
|
+
await deps.workspaceOpenProvider.openWorkspace(accessToken, ws.external_id, sealed);
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
console.warn(`[API] Failed to open workspace ${ws.external_id}:`, error);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1309
1513
|
|
|
1310
|
-
// src/auth/
|
|
1311
|
-
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
},
|
|
1320
|
-
params.password,
|
|
1321
|
-
deps.deploymentDomain
|
|
1322
|
-
);
|
|
1323
|
-
const response = await deps.fetchClient.POST("/v1/user/register", {
|
|
1324
|
-
body: credentials.request
|
|
1325
|
-
});
|
|
1326
|
-
if (response.error) {
|
|
1327
|
-
throw new Error(
|
|
1328
|
-
`Registration failed: ${typeof response.error === "object" && response.error !== null && "detail" in response.error ? response.error.detail : "Unknown error"}`
|
|
1514
|
+
// src/auth/session-claims.ts
|
|
1515
|
+
function readSessionClaims(token) {
|
|
1516
|
+
const payload = token?.split(".")[1];
|
|
1517
|
+
if (!payload) return null;
|
|
1518
|
+
let claims;
|
|
1519
|
+
try {
|
|
1520
|
+
const binary = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1521
|
+
claims = JSON.parse(
|
|
1522
|
+
new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)))
|
|
1329
1523
|
);
|
|
1524
|
+
} catch {
|
|
1525
|
+
return null;
|
|
1330
1526
|
}
|
|
1331
|
-
|
|
1527
|
+
if (typeof claims !== "object" || claims === null) return null;
|
|
1528
|
+
const { sk, exp } = claims;
|
|
1529
|
+
const sessionKey = typeof sk === "string" && sk ? sk : null;
|
|
1530
|
+
return {
|
|
1531
|
+
sessionKey,
|
|
1532
|
+
sessionId: sessionKey ? sessionKey.slice(0, 12) : null,
|
|
1533
|
+
expiresAt: typeof exp === "number" && Number.isFinite(exp) ? exp * 1e3 : null
|
|
1534
|
+
};
|
|
1332
1535
|
}
|
|
1333
1536
|
|
|
1334
1537
|
// src/auth/identity.ts
|
|
@@ -1355,6 +1558,11 @@ async function unlockIdentityWithPassword(email, password, deploymentDomain) {
|
|
|
1355
1558
|
}
|
|
1356
1559
|
|
|
1357
1560
|
// src/auth/login.ts
|
|
1561
|
+
function authRequestError(what, error, status) {
|
|
1562
|
+
const detail = typeof error === "object" && error !== null && "detail" in error ? error.detail : void 0;
|
|
1563
|
+
const text = typeof detail === "string" ? detail : "Unknown error";
|
|
1564
|
+
return Object.assign(new Error(`${what} failed: ${text}`), { detail, status });
|
|
1565
|
+
}
|
|
1358
1566
|
async function login(params, deps) {
|
|
1359
1567
|
const credentials = await generateLoginCredentials(
|
|
1360
1568
|
{
|
|
@@ -1364,7 +1572,12 @@ async function login(params, deps) {
|
|
|
1364
1572
|
params.password,
|
|
1365
1573
|
deps.deploymentDomain
|
|
1366
1574
|
);
|
|
1367
|
-
return performLogin(
|
|
1575
|
+
return performLogin(
|
|
1576
|
+
credentials.request,
|
|
1577
|
+
{ password: credentials.passwordPrivateKey },
|
|
1578
|
+
deps,
|
|
1579
|
+
params.profile
|
|
1580
|
+
);
|
|
1368
1581
|
}
|
|
1369
1582
|
async function loginWithKey(params, deps) {
|
|
1370
1583
|
const credentials = generateLoginCredentialsFromKey(
|
|
@@ -1376,27 +1589,42 @@ async function loginWithKey(params, deps) {
|
|
|
1376
1589
|
);
|
|
1377
1590
|
return performLogin(credentials.request, { identity: credentials.signingPrivateKey }, deps);
|
|
1378
1591
|
}
|
|
1379
|
-
async function performLogin(request, signer, deps) {
|
|
1592
|
+
async function performLogin(request, signer, deps, profile) {
|
|
1380
1593
|
const response = await deps.fetchClient.POST("/v1/user/login", {
|
|
1381
1594
|
body: request
|
|
1382
1595
|
});
|
|
1383
1596
|
if (response.error || !response.data) {
|
|
1384
|
-
throw
|
|
1385
|
-
`Login failed: ${typeof response.error === "object" && response.error !== null && "detail" in response.error ? response.error.detail : "Unknown error"}`
|
|
1386
|
-
);
|
|
1597
|
+
throw authRequestError("Login", response.error, response.response?.status);
|
|
1387
1598
|
}
|
|
1388
1599
|
const data = response.data;
|
|
1389
1600
|
const serverSessionKey = base64ToBytes(data.session_key);
|
|
1390
1601
|
const signingPrivateKey = "password" in signer ? identityFromLoginResponse(signer.password, data) : signer.identity;
|
|
1391
|
-
await
|
|
1602
|
+
await deps.signIn?.before?.({ user: data.user });
|
|
1603
|
+
await (deps.persistence?.saveSession ?? saveSession)({
|
|
1392
1604
|
signingPrivateKey,
|
|
1393
1605
|
serverSessionKey,
|
|
1394
1606
|
userExtId: data.user.external_id ?? void 0,
|
|
1395
1607
|
wrappedIdentityKey: data.wrapped_identity_key ?? null
|
|
1396
1608
|
});
|
|
1397
|
-
|
|
1398
|
-
deps.session.
|
|
1609
|
+
const email = request.email;
|
|
1610
|
+
deps.session.signIn({
|
|
1611
|
+
accessToken: data.access_token,
|
|
1612
|
+
isSsoMode: !!request.sso_token,
|
|
1613
|
+
serverSessionKey,
|
|
1614
|
+
user: {
|
|
1615
|
+
...data.user,
|
|
1616
|
+
email,
|
|
1617
|
+
given_name: profile?.given_name ?? data.user.given_name ?? email.split("@")[0],
|
|
1618
|
+
family_name: profile?.family_name ?? data.user.family_name,
|
|
1619
|
+
picture: profile?.picture ?? data.user.picture ?? null,
|
|
1620
|
+
parent_ext_id: data.user.parent_ext_id ?? null,
|
|
1621
|
+
is_sso: data.user.is_sso ?? false,
|
|
1622
|
+
is_identity_verified: data.user.is_identity_verified ?? false
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
deps.signIn?.after?.({ response: data, serverSessionKey });
|
|
1399
1626
|
return {
|
|
1627
|
+
response: data,
|
|
1400
1628
|
accessToken: data.access_token,
|
|
1401
1629
|
userExtId: data.user.external_id ?? void 0,
|
|
1402
1630
|
parentExtId: data.user.parent_ext_id ?? null,
|
|
@@ -1413,10 +1641,26 @@ async function performLogin(request, signer, deps) {
|
|
|
1413
1641
|
};
|
|
1414
1642
|
}
|
|
1415
1643
|
|
|
1416
|
-
// src/auth/
|
|
1417
|
-
async function
|
|
1418
|
-
|
|
1419
|
-
|
|
1644
|
+
// src/auth/register.ts
|
|
1645
|
+
async function register(params, deps) {
|
|
1646
|
+
const credentials = await generateRegistrationCredentials(
|
|
1647
|
+
{
|
|
1648
|
+
email: params.email,
|
|
1649
|
+
verification_credential: params.verificationCode,
|
|
1650
|
+
given_name: params.firstName,
|
|
1651
|
+
family_name: params.lastName,
|
|
1652
|
+
picture: params.picture
|
|
1653
|
+
},
|
|
1654
|
+
params.password,
|
|
1655
|
+
deps.deploymentDomain
|
|
1656
|
+
);
|
|
1657
|
+
const response = await deps.fetchClient.POST("/v1/user/register", {
|
|
1658
|
+
body: credentials.request
|
|
1659
|
+
});
|
|
1660
|
+
if (response.error) {
|
|
1661
|
+
throw authRequestError("Registration", response.error, response.response?.status);
|
|
1662
|
+
}
|
|
1663
|
+
return { signingPrivateKey: credentials.signingPrivateKey };
|
|
1420
1664
|
}
|
|
1421
1665
|
|
|
1422
1666
|
// src/auth/change-password.ts
|
|
@@ -1455,29 +1699,6 @@ async function changePassword(params, deps) {
|
|
|
1455
1699
|
return { wrappedIdentityKey: request.wrapped_identity_key };
|
|
1456
1700
|
}
|
|
1457
1701
|
|
|
1458
|
-
// src/auth/session-claims.ts
|
|
1459
|
-
function readSessionClaims(token) {
|
|
1460
|
-
const payload = token?.split(".")[1];
|
|
1461
|
-
if (!payload) return null;
|
|
1462
|
-
let claims;
|
|
1463
|
-
try {
|
|
1464
|
-
const binary = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1465
|
-
claims = JSON.parse(
|
|
1466
|
-
new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)))
|
|
1467
|
-
);
|
|
1468
|
-
} catch {
|
|
1469
|
-
return null;
|
|
1470
|
-
}
|
|
1471
|
-
if (typeof claims !== "object" || claims === null) return null;
|
|
1472
|
-
const { sk, exp } = claims;
|
|
1473
|
-
const sessionKey = typeof sk === "string" && sk ? sk : null;
|
|
1474
|
-
return {
|
|
1475
|
-
sessionKey,
|
|
1476
|
-
sessionId: sessionKey ? sessionKey.slice(0, 12) : null,
|
|
1477
|
-
expiresAt: typeof exp === "number" && Number.isFinite(exp) ? exp * 1e3 : null
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
1702
|
// src/client.ts
|
|
1482
1703
|
var API_PREFIX = "/v1";
|
|
1483
1704
|
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1514,24 +1735,42 @@ function createRetryFetch() {
|
|
|
1514
1735
|
throw lastError;
|
|
1515
1736
|
};
|
|
1516
1737
|
}
|
|
1738
|
+
var HTTP_METHODS = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
|
|
1739
|
+
function readBaseUrlPerRequest(fetchClient, base) {
|
|
1740
|
+
const methods = fetchClient;
|
|
1741
|
+
for (const method of HTTP_METHODS) {
|
|
1742
|
+
const call = methods[method].bind(fetchClient);
|
|
1743
|
+
methods[method] = (url, init) => call(url, { ...init, baseUrl: base() });
|
|
1744
|
+
}
|
|
1745
|
+
const request = methods.request.bind(fetchClient);
|
|
1746
|
+
methods.request = (method, url, init) => request(method, url, { ...init, baseUrl: base() });
|
|
1747
|
+
}
|
|
1748
|
+
var deploymentDomainOf = ({ deploymentDomain }) => typeof deploymentDomain === "function" ? deploymentDomain() : deploymentDomain;
|
|
1517
1749
|
function createArbiClient(options) {
|
|
1518
|
-
const { fetch: fetch2, session,
|
|
1519
|
-
const authDeps = {
|
|
1750
|
+
const { fetch: fetch2, session, persistence, workspaceKeys, auth } = createTransport(options);
|
|
1751
|
+
const authDeps = {
|
|
1752
|
+
fetchClient: fetch2,
|
|
1753
|
+
session,
|
|
1754
|
+
get deploymentDomain() {
|
|
1755
|
+
return deploymentDomainOf(options);
|
|
1756
|
+
},
|
|
1757
|
+
persistence,
|
|
1758
|
+
signIn: options.signIn
|
|
1759
|
+
};
|
|
1520
1760
|
return {
|
|
1521
1761
|
fetch: fetch2,
|
|
1762
|
+
workspaceKeys,
|
|
1522
1763
|
session,
|
|
1523
1764
|
auth: {
|
|
1524
1765
|
register: (params) => register(params, authDeps),
|
|
1525
1766
|
login: (params) => login(params, authDeps),
|
|
1526
1767
|
loginWithKey: (params) => loginWithKey(params, authDeps),
|
|
1527
|
-
logout: () => logout(session),
|
|
1528
1768
|
changePassword: (params) => changePassword(params, authDeps),
|
|
1529
|
-
|
|
1530
|
-
reopenWorkspace
|
|
1769
|
+
...auth
|
|
1531
1770
|
},
|
|
1532
1771
|
crypto: {
|
|
1533
1772
|
initSodium,
|
|
1534
|
-
generateUserKeypairs: (email, password) => generateUserKeypairs(email, password, options
|
|
1773
|
+
generateUserKeypairs: (email, password) => generateUserKeypairs(email, password, deploymentDomainOf(options)),
|
|
1535
1774
|
signMessage,
|
|
1536
1775
|
sealedBoxDecrypt,
|
|
1537
1776
|
sealedBoxEncrypt,
|
|
@@ -1557,24 +1796,22 @@ function createArbiClient(options) {
|
|
|
1557
1796
|
}
|
|
1558
1797
|
function createTransport(options) {
|
|
1559
1798
|
const {
|
|
1560
|
-
|
|
1561
|
-
reloginExcludePatterns = [`${API_PREFIX}/user/login`],
|
|
1799
|
+
reloginExcludePatterns = [`${API_PREFIX}/user/login`, `${API_PREFIX}/user/logout`],
|
|
1562
1800
|
credentials = "include",
|
|
1563
|
-
|
|
1801
|
+
sso = null,
|
|
1802
|
+
middleware = [],
|
|
1564
1803
|
onReloginSuccess,
|
|
1565
|
-
|
|
1566
|
-
sessionStorage: sessionStorageOverride,
|
|
1804
|
+
onRetryCloneFailed,
|
|
1567
1805
|
userEmail
|
|
1568
1806
|
} = options;
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
};
|
|
1807
|
+
const persistence = options.persistence ?? { getSession, saveSession };
|
|
1808
|
+
const baseUrlOption = options.baseUrl;
|
|
1809
|
+
const base = typeof baseUrlOption === "function" ? baseUrlOption : () => baseUrlOption;
|
|
1573
1810
|
const retryFetch = createRetryFetch();
|
|
1574
|
-
const session = createSessionManager();
|
|
1811
|
+
const session = options.session ?? createSessionManager();
|
|
1575
1812
|
if (userEmail) session.setUser(userEmail);
|
|
1576
1813
|
const tokenProvider = createTokenProvider(session);
|
|
1577
|
-
const authState = createAuthStateProvider(session);
|
|
1814
|
+
const authState = createAuthStateProvider(session, options.selectedWorkspace);
|
|
1578
1815
|
const cryptoProvider = {
|
|
1579
1816
|
ensureReady: initSodium,
|
|
1580
1817
|
signMessage,
|
|
@@ -1583,11 +1820,10 @@ function createTransport(options) {
|
|
|
1583
1820
|
sealKeyForSession,
|
|
1584
1821
|
fromBase64: base64ToBytes
|
|
1585
1822
|
};
|
|
1586
|
-
let reloginUnreachable = false;
|
|
1587
1823
|
const loginProvider = {
|
|
1588
1824
|
async login(payload) {
|
|
1589
1825
|
const rawFetch = createFetchClient({
|
|
1590
|
-
baseUrl,
|
|
1826
|
+
baseUrl: base(),
|
|
1591
1827
|
credentials,
|
|
1592
1828
|
fetch: retryFetch
|
|
1593
1829
|
});
|
|
@@ -1603,8 +1839,7 @@ function createTransport(options) {
|
|
|
1603
1839
|
});
|
|
1604
1840
|
} catch (error) {
|
|
1605
1841
|
console.warn("[API] Login call unreachable:", error);
|
|
1606
|
-
|
|
1607
|
-
return null;
|
|
1842
|
+
return "unreachable";
|
|
1608
1843
|
}
|
|
1609
1844
|
if (response.error || !response.data) {
|
|
1610
1845
|
console.warn("[API] Login call failed:", response.error);
|
|
@@ -1619,81 +1854,219 @@ function createTransport(options) {
|
|
|
1619
1854
|
};
|
|
1620
1855
|
}
|
|
1621
1856
|
};
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1857
|
+
async function listWorkspaces(accessToken) {
|
|
1858
|
+
const response = await retryFetch(`${base()}${API_PREFIX}/user/workspaces`, {
|
|
1859
|
+
method: "GET",
|
|
1860
|
+
headers: {
|
|
1861
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1862
|
+
"Content-Type": "application/json"
|
|
1863
|
+
},
|
|
1864
|
+
credentials
|
|
1865
|
+
});
|
|
1866
|
+
return response.ok ? response.json() : null;
|
|
1867
|
+
}
|
|
1868
|
+
const workspaceKeys = createWorkspaceKeys({
|
|
1869
|
+
crypto: cryptoProvider,
|
|
1870
|
+
unwrapWorkspaceKey,
|
|
1871
|
+
persistence,
|
|
1872
|
+
getAccessToken: () => session.getState().accessToken,
|
|
1873
|
+
getServerSessionKey: () => session.getState().serverSessionKey,
|
|
1874
|
+
listWorkspaces,
|
|
1875
|
+
async primeKey(accessToken, workspaceId, sealedKey) {
|
|
1876
|
+
const response = await retryFetch(
|
|
1877
|
+
`${base()}${API_PREFIX}/workspace/${workspaceId}/prime-key`,
|
|
1878
|
+
{
|
|
1879
|
+
method: "POST",
|
|
1880
|
+
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
1881
|
+
body: JSON.stringify({ workspace_key: sealedKey }),
|
|
1882
|
+
credentials
|
|
1883
|
+
}
|
|
1636
1884
|
);
|
|
1637
|
-
|
|
1885
|
+
if (!response.ok) throw new Error(`Could not unlock the workspace (${response.status})`);
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
let keysUser = session.getState().signedIn ? session.getState().userExtId : null;
|
|
1889
|
+
session.subscribe((state) => {
|
|
1890
|
+
const user = state.signedIn ? state.userExtId : null;
|
|
1891
|
+
if (user !== keysUser) workspaceKeys.clear();
|
|
1892
|
+
keysUser = user;
|
|
1893
|
+
});
|
|
1894
|
+
const workspaceKeyRefreshProvider = {
|
|
1895
|
+
async getWrappedKey(_accessToken, workspaceId) {
|
|
1896
|
+
return (await workspaceKeys.wrappedKey(workspaceId).catch(() => null))?.wrappedKey ?? null;
|
|
1638
1897
|
}
|
|
1639
1898
|
};
|
|
1640
1899
|
const workspaceOpenProvider = {
|
|
1641
1900
|
async openWorkspace(accessToken, workspaceId, encryptedWorkspaceKey) {
|
|
1901
|
+
const query = "?include_documents=false&include_conversations=false&include_tags=false";
|
|
1642
1902
|
try {
|
|
1643
|
-
const response = await retryFetch(
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1903
|
+
const response = await retryFetch(
|
|
1904
|
+
`${base()}${API_PREFIX}/workspace/${workspaceId}/open${query}`,
|
|
1905
|
+
{
|
|
1906
|
+
method: "POST",
|
|
1907
|
+
headers: {
|
|
1908
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1909
|
+
"Content-Type": "application/json"
|
|
1910
|
+
},
|
|
1911
|
+
body: JSON.stringify({ workspace_key: encryptedWorkspaceKey }),
|
|
1912
|
+
credentials
|
|
1913
|
+
}
|
|
1914
|
+
);
|
|
1652
1915
|
return response.ok ? true : null;
|
|
1653
1916
|
} catch {
|
|
1654
1917
|
return null;
|
|
1655
1918
|
}
|
|
1656
1919
|
}
|
|
1657
1920
|
};
|
|
1658
|
-
|
|
1921
|
+
let lastFailure = null;
|
|
1922
|
+
const expiredListeners = /* @__PURE__ */ new Set();
|
|
1923
|
+
const rawRelogin = createReloginHandler({
|
|
1659
1924
|
crypto: cryptoProvider,
|
|
1660
|
-
|
|
1925
|
+
persistence,
|
|
1661
1926
|
authState,
|
|
1662
|
-
|
|
1927
|
+
sso,
|
|
1663
1928
|
loginProvider,
|
|
1664
1929
|
workspaceKeyRefreshProvider,
|
|
1665
1930
|
workspaceOpenProvider,
|
|
1666
|
-
|
|
1931
|
+
// The new server session key goes into the session before the token: whatever re-renders on
|
|
1932
|
+
// the token reads the key that goes with it.
|
|
1933
|
+
async onReloginSuccess(data) {
|
|
1934
|
+
session.setServerSessionKey(data.serverSessionKey);
|
|
1935
|
+
await onReloginSuccess?.(data);
|
|
1936
|
+
},
|
|
1937
|
+
onReloginFailed: (reason) => {
|
|
1938
|
+
lastFailure = reason;
|
|
1939
|
+
}
|
|
1667
1940
|
});
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1941
|
+
async function relogin() {
|
|
1942
|
+
const tokenBefore = session.getState().accessToken;
|
|
1943
|
+
const token = await rawRelogin();
|
|
1944
|
+
if (token) return token;
|
|
1945
|
+
const tokenNow = session.getState().accessToken;
|
|
1946
|
+
if (tokenNow && tokenNow !== tokenBefore) return tokenNow;
|
|
1947
|
+
if (lastFailure === "refused") {
|
|
1948
|
+
lastFailure = null;
|
|
1949
|
+
session.clear();
|
|
1950
|
+
for (const listener of [...expiredListeners]) listener();
|
|
1951
|
+
}
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1672
1954
|
const reopenWorkspace = createWorkspaceReopener({
|
|
1673
1955
|
crypto: cryptoProvider,
|
|
1674
|
-
|
|
1956
|
+
persistence,
|
|
1675
1957
|
authState,
|
|
1676
1958
|
getAccessToken: () => session.getState().accessToken,
|
|
1677
1959
|
workspaceKeyRefreshProvider,
|
|
1678
1960
|
workspaceOpenProvider
|
|
1679
1961
|
});
|
|
1962
|
+
const openAllWorkspaces = createAllWorkspacesOpener({
|
|
1963
|
+
crypto: cryptoProvider,
|
|
1964
|
+
persistence,
|
|
1965
|
+
getAccessToken: () => session.getState().accessToken,
|
|
1966
|
+
getSessionKey: (accessToken) => readSessionClaims(accessToken)?.sessionKey ?? null,
|
|
1967
|
+
unwrapWorkspaceKey,
|
|
1968
|
+
listWorkspaces: async (accessToken) => {
|
|
1969
|
+
const list = await listWorkspaces(accessToken);
|
|
1970
|
+
return list && workspaceKeys.remember(list);
|
|
1971
|
+
},
|
|
1972
|
+
workspaceOpenProvider
|
|
1973
|
+
});
|
|
1680
1974
|
const fetchClient = createFetchClient({
|
|
1681
|
-
baseUrl,
|
|
1975
|
+
baseUrl: base(),
|
|
1682
1976
|
credentials,
|
|
1683
1977
|
fetch: retryFetch
|
|
1684
1978
|
});
|
|
1979
|
+
if (typeof baseUrlOption === "function") readBaseUrlPerRequest(fetchClient, base);
|
|
1685
1980
|
fetchClient.use(createBearerAuthMiddleware({ tokenProvider }));
|
|
1981
|
+
for (const m of middleware) fetchClient.use(m);
|
|
1686
1982
|
fetchClient.use(
|
|
1687
1983
|
createAutoReloginMiddleware({
|
|
1688
|
-
reloginHandler,
|
|
1984
|
+
reloginHandler: relogin,
|
|
1689
1985
|
reloginExcludePatterns,
|
|
1690
1986
|
reopenWorkspaceHandler: reopenWorkspace,
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1987
|
+
primeWorkspaceHandler: (workspaceId) => workspaceKeys.prime(workspaceId, { force: true }).then(
|
|
1988
|
+
() => true,
|
|
1989
|
+
(error) => {
|
|
1990
|
+
console.warn("[API] Could not unlock workspace", workspaceId, error);
|
|
1991
|
+
return false;
|
|
1992
|
+
}
|
|
1993
|
+
),
|
|
1994
|
+
onRetryCloneFailed
|
|
1694
1995
|
})
|
|
1695
1996
|
);
|
|
1696
|
-
return {
|
|
1997
|
+
return {
|
|
1998
|
+
fetch: fetchClient,
|
|
1999
|
+
session,
|
|
2000
|
+
persistence,
|
|
2001
|
+
workspaceKeys,
|
|
2002
|
+
auth: {
|
|
2003
|
+
relogin,
|
|
2004
|
+
reopenWorkspace,
|
|
2005
|
+
openAllWorkspaces,
|
|
2006
|
+
async logout({ everywhere = false } = {}) {
|
|
2007
|
+
if (session.getState().accessToken) {
|
|
2008
|
+
try {
|
|
2009
|
+
await fetchClient.POST("/v1/user/logout", {
|
|
2010
|
+
params: { query: { all_sessions: everywhere } }
|
|
2011
|
+
});
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
console.warn("[API] Server logout failed:", error);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
session.clear();
|
|
2017
|
+
if (everywhere) await clearAllData();
|
|
2018
|
+
},
|
|
2019
|
+
async unlock({ email, password }) {
|
|
2020
|
+
const saved = await persistence.getSession();
|
|
2021
|
+
if (!saved?.signingPrivateKey) throw new Error("no-session");
|
|
2022
|
+
let signingPrivateKey;
|
|
2023
|
+
try {
|
|
2024
|
+
signingPrivateKey = await unlockIdentityWithPassword(
|
|
2025
|
+
email,
|
|
2026
|
+
password,
|
|
2027
|
+
deploymentDomainOf(options)
|
|
2028
|
+
);
|
|
2029
|
+
} catch {
|
|
2030
|
+
throw new Error("bad-password");
|
|
2031
|
+
}
|
|
2032
|
+
const { publicKey } = encryptionKeypairFromSigningKey(signingPrivateKey);
|
|
2033
|
+
session.signIn({
|
|
2034
|
+
accessToken: null,
|
|
2035
|
+
isSsoMode: false,
|
|
2036
|
+
serverSessionKey: saved.serverSessionKey ?? null,
|
|
2037
|
+
user: {
|
|
2038
|
+
email,
|
|
2039
|
+
given_name: email.split("@")[0],
|
|
2040
|
+
family_name: null,
|
|
2041
|
+
picture: null,
|
|
2042
|
+
external_id: saved.userExtId ?? "",
|
|
2043
|
+
parent_ext_id: null,
|
|
2044
|
+
encryption_public_key: bytesToBase64(publicKey),
|
|
2045
|
+
is_sso: false,
|
|
2046
|
+
is_identity_verified: false,
|
|
2047
|
+
temporary_access_only: false
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
},
|
|
2051
|
+
async restore() {
|
|
2052
|
+
if (!session.getState().signedIn) return false;
|
|
2053
|
+
const keys = await persistence.getSession().catch(() => null);
|
|
2054
|
+
if (session.getState().serverSessionKey) return true;
|
|
2055
|
+
if (keys?.signingPrivateKey && keys.serverSessionKey) {
|
|
2056
|
+
session.setServerSessionKey(keys.serverSessionKey);
|
|
2057
|
+
return true;
|
|
2058
|
+
}
|
|
2059
|
+
session.clear();
|
|
2060
|
+
return false;
|
|
2061
|
+
},
|
|
2062
|
+
onSessionExpired(listener) {
|
|
2063
|
+
expiredListeners.add(listener);
|
|
2064
|
+
return () => {
|
|
2065
|
+
expiredListeners.delete(listener);
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
1697
2070
|
}
|
|
1698
2071
|
|
|
1699
2072
|
// src/websocket.ts
|
|
@@ -1721,6 +2094,6 @@ function isMessageType(msg, type) {
|
|
|
1721
2094
|
return msg.type === type;
|
|
1722
2095
|
}
|
|
1723
2096
|
|
|
1724
|
-
export { API_PREFIX, CALL_KEY_BYTES, CHAT_KEY_BYTES, ENCRYPTED_RECOVERY_KEY_SIZE, RECOVERY_KEY_ERRORS, SECURE_MAIL_KEY_BYTES, UNENCRYPTED_RECOVERY_KEY_SIZE, base64Decode, base64Encode, base64ToBytes, base64UrlToBytes, buildCallFragment, buildWebSocketUrl, bytesToBase64, bytesToBase64Url, canPersistSession, changePassword, clearAllData, clearSession, computeSharedSecret, createArbiClient, createAuthMessage, createAutoReloginMiddleware, createBearerAuthMiddleware, createContentHash, createOfflineSealer, createRecoveryFile, createRecoveryUrl, createReloginHandler, createSessionManager, createWorkspaceReopener, decodeCallKey, decodeSecureMailKey, decryptCallTitle, decryptFile, decryptMessage, decryptMessageWithSharedSecret, decryptRecoveryKey, decryptWithChatKey, deriveDirectChatKey, deriveEncryptionKeypairFromSigning, derivePublicKey, encodeCallKey, encryptCallTitle, encryptFile, encryptMessage, encryptMessageWithSharedSecret, encryptRecoveryKey, encryptWithChatKey, encryptionKeypairFromSigningKey, generateCallKey, generateChatKey, generateIdentityKeypair, generateKeyPairs, generateLoginCredentials, generateLoginCredentialsFromKey, generatePasswordChangeCredentials, generateRandomSigningKeypair, generateRegistrationCredentials, generateUserKeypairs, getRecoveryKeyFilename, getSession, getUserEncryptionKeypair, hasSession, identityFromLoginResponse, initSodium, initializeDatabase, isEncryptedRecoveryKey, isMessageType, openSealedCallKey, openSealedChatKey, openSecureMail, parseCallFragment, parseRecoveryUrl, parseServerMessage, passwordChangeErrorMessage, prewarmKeyPairs, readSessionClaims, saveSession, sealCallKey, sealChatKey, sealKeyForSession, sealSecureMail, sealedBoxDecrypt, sealedBoxEncrypt, secureMailClaim, signMessage, unlockIdentityWithPassword, unwrapIdentityKey, unwrapWorkspaceKey, updateWrappedIdentityKey, validateRecoveryKeyFile, wrapIdentityKey };
|
|
2097
|
+
export { ACCESS_TOKEN_STORAGE_KEY, API_PREFIX, CALL_KEY_BYTES, CHAT_KEY_BYTES, ENCRYPTED_RECOVERY_KEY_SIZE, RECOVERY_KEY_ERRORS, SECURE_MAIL_KEY_BYTES, UNENCRYPTED_RECOVERY_KEY_SIZE, authRequestError, base64Decode, base64Encode, base64ToBytes, base64UrlToBytes, browserSessionStateStorage, buildCallFragment, buildCallLink, buildWebSocketUrl, bytesToBase64, bytesToBase64Url, canPersistSession, changePassword, clearAllData, clearSession, computeSharedSecret, createAllWorkspacesOpener, createArbiClient, createAuthMessage, createAutoReloginMiddleware, createBearerAuthMiddleware, createContentHash, createOfflineSealer, createRecoveryFile, createRecoveryUrl, createReloginHandler, createSessionManager, createWorkspaceKeys, createWorkspaceReopener, decodeCallKey, decodeSecureMailKey, decryptCallTitle, decryptFile, decryptMessage, decryptMessageWithSharedSecret, decryptRecoveryKey, decryptWithChatKey, deriveDirectChatKey, deriveEncryptionKeypairFromSigning, derivePublicKey, encodeCallKey, encryptCallTitle, encryptFile, encryptMessage, encryptMessageWithSharedSecret, encryptRecoveryKey, encryptWithChatKey, encryptionKeypairFromSigningKey, generateCallKey, generateChatKey, generateIdentityKeypair, generateKeyPairs, generateLoginCredentials, generateLoginCredentialsFromKey, generatePasswordChangeCredentials, generateRandomSigningKeypair, generateRegistrationCredentials, generateUserKeypairs, getRecoveryKeyFilename, getSession, getUserEncryptionKeypair, hasSession, identityFromLoginResponse, initSodium, initializeDatabase, isEncryptedRecoveryKey, isMessageType, openCallKeyOf, openSealedCallKey, openSealedChatKey, openSecureMail, parseCallFragment, parseRecoveryUrl, parseServerMessage, passwordChangeErrorMessage, prewarmKeyPairs, readSessionClaims, saveSession, sealCallKey, sealCallKeyForPeers, sealChatKey, sealKeyForSession, sealSecureMail, sealedBoxDecrypt, sealedBoxEncrypt, secureMailClaim, signMessage, unlockIdentityWithPassword, unwrapIdentityKey, unwrapWorkspaceKey, updateWrappedIdentityKey, validateRecoveryKeyFile, workspaceKeyRequiredOf, wrapIdentityKey };
|
|
1725
2098
|
//# sourceMappingURL=index.js.map
|
|
1726
2099
|
//# sourceMappingURL=index.js.map
|