@arbidocs/client 0.4.1 → 0.4.3
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/dist/index.cjs +132 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -2
- package/dist/index.d.ts +50 -2
- package/dist/index.js +132 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -392,6 +392,21 @@ function createBearerAuthMiddleware(config) {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
// src/middleware/auto-relogin.ts
|
|
395
|
+
var NO_ACTIVE_WORKSPACE = /no active workspace/i;
|
|
396
|
+
async function isNoActiveWorkspace(response) {
|
|
397
|
+
try {
|
|
398
|
+
return NO_ACTIVE_WORKSPACE.test(await response.clone().text());
|
|
399
|
+
} catch {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function safeClone(request) {
|
|
404
|
+
try {
|
|
405
|
+
return request.clone();
|
|
406
|
+
} catch {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
395
410
|
function createAutoReloginMiddleware(config) {
|
|
396
411
|
const cloneCache = /* @__PURE__ */ new WeakMap();
|
|
397
412
|
return {
|
|
@@ -402,6 +417,25 @@ function createAutoReloginMiddleware(config) {
|
|
|
402
417
|
}
|
|
403
418
|
},
|
|
404
419
|
async onResponse({ response, request }) {
|
|
420
|
+
if (response.status === 400 && await isNoActiveWorkspace(response)) {
|
|
421
|
+
let token = null;
|
|
422
|
+
let recovered = await config.reopenWorkspaceHandler?.() ?? false;
|
|
423
|
+
if (!recovered) {
|
|
424
|
+
token = await config.reloginHandler();
|
|
425
|
+
recovered = token !== null;
|
|
426
|
+
}
|
|
427
|
+
if (!recovered) {
|
|
428
|
+
config.onReloginFailed?.();
|
|
429
|
+
return response;
|
|
430
|
+
}
|
|
431
|
+
const retry = cloneCache.get(request) ?? safeClone(request);
|
|
432
|
+
if (retry) {
|
|
433
|
+
if (token) retry.headers.set("Authorization", `Bearer ${token}`);
|
|
434
|
+
return fetch(retry);
|
|
435
|
+
}
|
|
436
|
+
config.onRetryCloneFailed?.();
|
|
437
|
+
return response;
|
|
438
|
+
}
|
|
405
439
|
if (response.status === 400) {
|
|
406
440
|
const isSafeMethod = ["GET", "HEAD", "DELETE"].includes(request.method);
|
|
407
441
|
if (isSafeMethod) {
|
|
@@ -562,6 +596,47 @@ function createReloginHandler(deps) {
|
|
|
562
596
|
};
|
|
563
597
|
}
|
|
564
598
|
|
|
599
|
+
// src/relogin/reopen.ts
|
|
600
|
+
function createWorkspaceReopener(deps) {
|
|
601
|
+
let inFlight = null;
|
|
602
|
+
return function reopenWorkspace() {
|
|
603
|
+
if (inFlight) return inFlight;
|
|
604
|
+
inFlight = (async () => {
|
|
605
|
+
try {
|
|
606
|
+
const workspaceId = deps.authState.getSelectedWorkspaceId();
|
|
607
|
+
const accessToken = deps.getAccessToken();
|
|
608
|
+
if (!workspaceId || !accessToken) return false;
|
|
609
|
+
const session = await deps.sessionStorage.getSession();
|
|
610
|
+
if (!session?.signingPrivateKey || !session.serverSessionKey) return false;
|
|
611
|
+
await deps.crypto.ensureReady();
|
|
612
|
+
const wrappedKey = await deps.workspaceKeyRefreshProvider.getWrappedKey(
|
|
613
|
+
accessToken,
|
|
614
|
+
workspaceId
|
|
615
|
+
);
|
|
616
|
+
if (!wrappedKey) return false;
|
|
617
|
+
const encryptionKeyPair = deps.crypto.deriveEncryptionKeypair({
|
|
618
|
+
publicKey: session.signingPrivateKey.slice(32, 64),
|
|
619
|
+
secretKey: session.signingPrivateKey
|
|
620
|
+
});
|
|
621
|
+
const workspaceKey = deps.crypto.sealedBoxDecrypt(wrappedKey, encryptionKeyPair.secretKey);
|
|
622
|
+
const sealed = await deps.crypto.sealKeyForSession(workspaceKey, session.serverSessionKey);
|
|
623
|
+
const opened = await deps.workspaceOpenProvider.openWorkspace(
|
|
624
|
+
accessToken,
|
|
625
|
+
workspaceId,
|
|
626
|
+
sealed
|
|
627
|
+
);
|
|
628
|
+
return opened === true;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.error("[API] Failed to re-open workspace:", error);
|
|
631
|
+
return false;
|
|
632
|
+
} finally {
|
|
633
|
+
inFlight = null;
|
|
634
|
+
}
|
|
635
|
+
})();
|
|
636
|
+
return inFlight;
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
565
640
|
// src/storage/indexed-db.ts
|
|
566
641
|
var DB_NAME = "arbi-crypto";
|
|
567
642
|
var DB_VERSION = 2;
|
|
@@ -1009,6 +1084,7 @@ function createArbiClient(options) {
|
|
|
1009
1084
|
credentials = "include",
|
|
1010
1085
|
ssoTokenProvider = null,
|
|
1011
1086
|
onReloginSuccess,
|
|
1087
|
+
onSessionExpired,
|
|
1012
1088
|
sessionStorage: sessionStorageOverride,
|
|
1013
1089
|
userEmail
|
|
1014
1090
|
} = options;
|
|
@@ -1029,6 +1105,7 @@ function createArbiClient(options) {
|
|
|
1029
1105
|
sealKeyForSession,
|
|
1030
1106
|
fromBase64: base64ToBytes
|
|
1031
1107
|
};
|
|
1108
|
+
let reloginUnreachable = false;
|
|
1032
1109
|
const loginProvider = {
|
|
1033
1110
|
async login(payload) {
|
|
1034
1111
|
const rawFetch = createFetchClient__default.default({
|
|
@@ -1036,14 +1113,21 @@ function createArbiClient(options) {
|
|
|
1036
1113
|
credentials,
|
|
1037
1114
|
fetch: retryFetch
|
|
1038
1115
|
});
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1116
|
+
let response;
|
|
1117
|
+
try {
|
|
1118
|
+
response = await rawFetch.POST("/v1/user/login", {
|
|
1119
|
+
body: {
|
|
1120
|
+
email: payload.email,
|
|
1121
|
+
signature: payload.signature,
|
|
1122
|
+
timestamp: payload.timestamp,
|
|
1123
|
+
sso_token: payload.ssoToken
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
console.warn("[API] Login call unreachable:", error);
|
|
1128
|
+
reloginUnreachable = true;
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1047
1131
|
if (response.error || !response.data) {
|
|
1048
1132
|
console.warn("[API] Login call failed:", response.error);
|
|
1049
1133
|
return null;
|
|
@@ -1074,15 +1158,46 @@ function createArbiClient(options) {
|
|
|
1074
1158
|
return workspace?.wrapped_key ?? null;
|
|
1075
1159
|
}
|
|
1076
1160
|
};
|
|
1077
|
-
const
|
|
1161
|
+
const workspaceOpenProvider = {
|
|
1162
|
+
async openWorkspace(accessToken, workspaceId, encryptedWorkspaceKey) {
|
|
1163
|
+
try {
|
|
1164
|
+
const response = await retryFetch(`${baseUrl}${API_PREFIX}/workspace/${workspaceId}/open`, {
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
headers: {
|
|
1167
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1168
|
+
"Content-Type": "application/json"
|
|
1169
|
+
},
|
|
1170
|
+
body: JSON.stringify({ workspace_key: encryptedWorkspaceKey }),
|
|
1171
|
+
credentials
|
|
1172
|
+
});
|
|
1173
|
+
return response.ok ? true : null;
|
|
1174
|
+
} catch {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
const rawReloginHandler = createReloginHandler({
|
|
1078
1180
|
crypto: cryptoProvider,
|
|
1079
1181
|
sessionStorage,
|
|
1080
1182
|
authState,
|
|
1081
1183
|
ssoTokenProvider,
|
|
1082
1184
|
loginProvider,
|
|
1083
1185
|
workspaceKeyRefreshProvider,
|
|
1186
|
+
workspaceOpenProvider,
|
|
1084
1187
|
onReloginSuccess
|
|
1085
1188
|
});
|
|
1189
|
+
const reloginHandler = () => {
|
|
1190
|
+
reloginUnreachable = false;
|
|
1191
|
+
return rawReloginHandler();
|
|
1192
|
+
};
|
|
1193
|
+
const reopenWorkspace = createWorkspaceReopener({
|
|
1194
|
+
crypto: cryptoProvider,
|
|
1195
|
+
sessionStorage,
|
|
1196
|
+
authState,
|
|
1197
|
+
getAccessToken: () => session.getState().accessToken,
|
|
1198
|
+
workspaceKeyRefreshProvider,
|
|
1199
|
+
workspaceOpenProvider
|
|
1200
|
+
});
|
|
1086
1201
|
const fetchClient = createFetchClient__default.default({
|
|
1087
1202
|
baseUrl,
|
|
1088
1203
|
credentials,
|
|
@@ -1092,7 +1207,11 @@ function createArbiClient(options) {
|
|
|
1092
1207
|
fetchClient.use(
|
|
1093
1208
|
createAutoReloginMiddleware({
|
|
1094
1209
|
reloginHandler,
|
|
1095
|
-
reloginExcludePatterns
|
|
1210
|
+
reloginExcludePatterns,
|
|
1211
|
+
reopenWorkspaceHandler: reopenWorkspace,
|
|
1212
|
+
onReloginFailed: () => {
|
|
1213
|
+
if (!reloginUnreachable && session.getState().userEmail) onSessionExpired?.();
|
|
1214
|
+
}
|
|
1096
1215
|
})
|
|
1097
1216
|
);
|
|
1098
1217
|
const authDeps = {
|
|
@@ -1109,7 +1228,8 @@ function createArbiClient(options) {
|
|
|
1109
1228
|
loginWithKey: (params) => loginWithKey(params, authDeps),
|
|
1110
1229
|
logout: () => logout(session),
|
|
1111
1230
|
changePassword: (params) => changePassword(params, authDeps),
|
|
1112
|
-
relogin: reloginHandler
|
|
1231
|
+
relogin: reloginHandler,
|
|
1232
|
+
reopenWorkspace
|
|
1113
1233
|
},
|
|
1114
1234
|
crypto: {
|
|
1115
1235
|
initSodium,
|
|
@@ -1179,6 +1299,7 @@ exports.createBearerAuthMiddleware = createBearerAuthMiddleware;
|
|
|
1179
1299
|
exports.createContentHash = createContentHash;
|
|
1180
1300
|
exports.createReloginHandler = createReloginHandler;
|
|
1181
1301
|
exports.createSessionManager = createSessionManager;
|
|
1302
|
+
exports.createWorkspaceReopener = createWorkspaceReopener;
|
|
1182
1303
|
exports.decryptFile = decryptFile;
|
|
1183
1304
|
exports.decryptMessage = decryptMessage;
|
|
1184
1305
|
exports.decryptMessageWithSharedSecret = decryptMessageWithSharedSecret;
|