@xenide-io/suite-client 0.2.0 → 0.4.0
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.d.mts +51 -8
- package/dist/index.d.ts +51 -8
- package/dist/index.js +146 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +145 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -141,6 +141,7 @@ function createApiClient(config) {
|
|
|
141
141
|
const ACCESS_KEY = config.accessTokenKey ?? `${config.storagePrefix}_access_token`;
|
|
142
142
|
const REFRESH_KEY = config.refreshTokenKey ?? `${config.storagePrefix}_refresh_token`;
|
|
143
143
|
const WORKSPACE_KEY = config.workspaceKey ?? `${config.storagePrefix}_active_workspace_id`;
|
|
144
|
+
const CLEAR_WORKSPACE = config.clearWorkspaceOnSignOut ?? true;
|
|
144
145
|
let accessToken = null;
|
|
145
146
|
let refreshToken = null;
|
|
146
147
|
let activeWorkspaceId = null;
|
|
@@ -178,10 +179,12 @@ function createApiClient(config) {
|
|
|
178
179
|
}
|
|
179
180
|
function setTokens(access, refresh) {
|
|
180
181
|
accessToken = access;
|
|
181
|
-
|
|
182
|
+
authFailureFired = false;
|
|
183
|
+
if (refresh !== void 0) refreshToken = refresh;
|
|
182
184
|
if (typeof window !== "undefined") {
|
|
183
185
|
window.localStorage.setItem(ACCESS_KEY, access);
|
|
184
|
-
window.localStorage.setItem(REFRESH_KEY, refresh);
|
|
186
|
+
if (refresh) window.localStorage.setItem(REFRESH_KEY, refresh);
|
|
187
|
+
else if (refresh !== void 0) window.localStorage.removeItem(REFRESH_KEY);
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
function getAccessToken() {
|
|
@@ -198,6 +201,9 @@ function createApiClient(config) {
|
|
|
198
201
|
}
|
|
199
202
|
return refreshToken;
|
|
200
203
|
}
|
|
204
|
+
function hasAccessToken() {
|
|
205
|
+
return Boolean(getAccessToken());
|
|
206
|
+
}
|
|
201
207
|
function setActiveWorkspaceId(id, options) {
|
|
202
208
|
const changed = activeWorkspaceId !== id;
|
|
203
209
|
activeWorkspaceId = id;
|
|
@@ -224,13 +230,17 @@ function createApiClient(config) {
|
|
|
224
230
|
}
|
|
225
231
|
}
|
|
226
232
|
function clearAuth() {
|
|
233
|
+
config.onSignOut?.();
|
|
227
234
|
accessToken = null;
|
|
228
235
|
refreshToken = null;
|
|
229
236
|
activeWorkspaceId = null;
|
|
230
237
|
if (typeof window !== "undefined") {
|
|
231
238
|
window.localStorage.removeItem(ACCESS_KEY);
|
|
232
239
|
window.localStorage.removeItem(REFRESH_KEY);
|
|
233
|
-
window.localStorage.removeItem(WORKSPACE_KEY);
|
|
240
|
+
if (CLEAR_WORKSPACE) window.localStorage.removeItem(WORKSPACE_KEY);
|
|
241
|
+
for (const key of config.extraStorageKeysOnClear ?? []) {
|
|
242
|
+
window.localStorage.removeItem(key);
|
|
243
|
+
}
|
|
234
244
|
const prefixes = config.sessionStorageClearPrefixes ?? [];
|
|
235
245
|
if (prefixes.length > 0) {
|
|
236
246
|
try {
|
|
@@ -289,36 +299,43 @@ function createApiClient(config) {
|
|
|
289
299
|
return false;
|
|
290
300
|
}
|
|
291
301
|
}
|
|
292
|
-
async function apiFetch(path,
|
|
302
|
+
async function apiFetch(path, init = {}) {
|
|
303
|
+
const { skipAuth, ...requestInit } = init;
|
|
293
304
|
const token = getAccessToken();
|
|
294
305
|
const headers = {
|
|
295
306
|
Accept: "application/json",
|
|
296
|
-
...
|
|
307
|
+
...requestInit.headers
|
|
297
308
|
};
|
|
298
|
-
const body =
|
|
309
|
+
const body = requestInit.body;
|
|
299
310
|
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
300
311
|
if (!isFormData) {
|
|
301
312
|
headers["Content-Type"] = "application/json";
|
|
302
313
|
}
|
|
303
|
-
if (token) {
|
|
314
|
+
if (token && !skipAuth) {
|
|
304
315
|
headers["Authorization"] = `Bearer ${token}`;
|
|
305
316
|
}
|
|
306
317
|
const wsId = getActiveWorkspaceId();
|
|
307
|
-
if (wsId) {
|
|
318
|
+
if (wsId && !skipAuth) {
|
|
308
319
|
headers["X-Active-Workspace-Id"] = wsId;
|
|
309
320
|
}
|
|
310
321
|
const url = path.startsWith("http") ? path : `${API_URL}${path}`;
|
|
322
|
+
if (config.enforceSameOrigin && !skipAuth && path.startsWith("http") && !url.startsWith(API_URL)) {
|
|
323
|
+
throw new Error("Refusing to send the session to an unexpected origin.");
|
|
324
|
+
}
|
|
311
325
|
async function doFetch() {
|
|
312
326
|
try {
|
|
313
|
-
return await fetch(url, { ...
|
|
314
|
-
} catch {
|
|
327
|
+
return await fetch(url, { ...requestInit, headers });
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (init.signal?.aborted || error instanceof DOMException && error.name === "AbortError") {
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
315
332
|
throw new Error(
|
|
316
333
|
`Could not reach the API at ${API_URL}. Is the Django backend running?`
|
|
317
334
|
);
|
|
318
335
|
}
|
|
319
336
|
}
|
|
320
337
|
let res = await doFetch();
|
|
321
|
-
if (res.status === 401 && token) {
|
|
338
|
+
if (res.status === 401 && token && !skipAuth) {
|
|
322
339
|
const refreshed = await refreshAccessToken();
|
|
323
340
|
if (refreshed) {
|
|
324
341
|
const newToken = getAccessToken();
|
|
@@ -326,9 +343,10 @@ function createApiClient(config) {
|
|
|
326
343
|
headers["Authorization"] = `Bearer ${newToken}`;
|
|
327
344
|
}
|
|
328
345
|
res = await doFetch();
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
346
|
+
}
|
|
347
|
+
if (res.status === 401) {
|
|
348
|
+
clearAuth();
|
|
349
|
+
triggerAuthFailure();
|
|
332
350
|
}
|
|
333
351
|
}
|
|
334
352
|
return res;
|
|
@@ -337,21 +355,23 @@ function createApiClient(config) {
|
|
|
337
355
|
return formatAppError(await appErrorFromResponse(res));
|
|
338
356
|
}
|
|
339
357
|
const api = {
|
|
340
|
-
get: (path) => apiFetch(path),
|
|
341
|
-
post: (path, body,
|
|
342
|
-
...
|
|
358
|
+
get: (path, init) => apiFetch(path, init),
|
|
359
|
+
post: (path, body, init = {}) => apiFetch(path, {
|
|
360
|
+
...init,
|
|
343
361
|
method: "POST",
|
|
344
362
|
body: body instanceof FormData ? body : body ? JSON.stringify(body) : void 0
|
|
345
363
|
}),
|
|
346
|
-
put: (path, body) => apiFetch(path, {
|
|
364
|
+
put: (path, body, init = {}) => apiFetch(path, {
|
|
365
|
+
...init,
|
|
347
366
|
method: "PUT",
|
|
348
367
|
body: body instanceof FormData ? body : body ? JSON.stringify(body) : void 0
|
|
349
368
|
}),
|
|
350
|
-
patch: (path, body) => apiFetch(path, {
|
|
369
|
+
patch: (path, body, init = {}) => apiFetch(path, {
|
|
370
|
+
...init,
|
|
351
371
|
method: "PATCH",
|
|
352
372
|
body: body instanceof FormData ? body : body ? JSON.stringify(body) : void 0
|
|
353
373
|
}),
|
|
354
|
-
delete: (path) => apiFetch(path, { method: "DELETE" })
|
|
374
|
+
delete: (path, init = {}) => apiFetch(path, { ...init, method: "DELETE" })
|
|
355
375
|
};
|
|
356
376
|
return {
|
|
357
377
|
API_URL,
|
|
@@ -359,6 +379,7 @@ function createApiClient(config) {
|
|
|
359
379
|
setTokens,
|
|
360
380
|
getAccessToken,
|
|
361
381
|
getRefreshToken,
|
|
382
|
+
hasAccessToken,
|
|
362
383
|
setActiveWorkspaceId,
|
|
363
384
|
getActiveWorkspaceId,
|
|
364
385
|
restoreWorkspace,
|
|
@@ -366,7 +387,10 @@ function createApiClient(config) {
|
|
|
366
387
|
restoreTokens,
|
|
367
388
|
refreshAccessToken,
|
|
368
389
|
parseApiError,
|
|
369
|
-
api
|
|
390
|
+
api,
|
|
391
|
+
// Aliases used by Kraken/Shelly.
|
|
392
|
+
restoreSession: restoreTokens,
|
|
393
|
+
clearSession: clearAuth
|
|
370
394
|
};
|
|
371
395
|
}
|
|
372
396
|
|
|
@@ -958,6 +982,105 @@ function createNotificationsClient(config) {
|
|
|
958
982
|
};
|
|
959
983
|
}
|
|
960
984
|
|
|
985
|
+
// src/notifications/create-web-push-client.ts
|
|
986
|
+
function createWebPushClient(config) {
|
|
987
|
+
const { api, parseApiError } = config;
|
|
988
|
+
const PUBLIC_KEY_PATH = config.publicKeyPath ?? "/api/notifications/push/public-key/";
|
|
989
|
+
const SUBSCRIBE_PATH = config.subscribePath ?? "/api/notifications/push/subscribe/";
|
|
990
|
+
const UNSUBSCRIBE_PATH = config.unsubscribePath ?? "/api/notifications/push/unsubscribe/";
|
|
991
|
+
const SW_PATH = config.serviceWorkerPath ?? "/sw.js";
|
|
992
|
+
const SW_SCOPE = config.serviceWorkerScope ?? "/";
|
|
993
|
+
function supported() {
|
|
994
|
+
return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window && "Notification" in window;
|
|
995
|
+
}
|
|
996
|
+
async function fetchConfig() {
|
|
997
|
+
const res = await api.get(PUBLIC_KEY_PATH);
|
|
998
|
+
if (!res.ok) throw new Error(await parseApiError(res));
|
|
999
|
+
return await res.json();
|
|
1000
|
+
}
|
|
1001
|
+
async function getRegistration() {
|
|
1002
|
+
const existing = await navigator.serviceWorker.getRegistration(SW_SCOPE);
|
|
1003
|
+
return existing ?? null;
|
|
1004
|
+
}
|
|
1005
|
+
function urlBase64ToUint8Array(base64) {
|
|
1006
|
+
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
1007
|
+
const normalized = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
1008
|
+
const raw = atob(normalized);
|
|
1009
|
+
const output = new Uint8Array(raw.length);
|
|
1010
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
1011
|
+
output[i] = raw.charCodeAt(i);
|
|
1012
|
+
}
|
|
1013
|
+
return output;
|
|
1014
|
+
}
|
|
1015
|
+
async function getState() {
|
|
1016
|
+
if (!supported()) return "unsupported";
|
|
1017
|
+
let cfg;
|
|
1018
|
+
try {
|
|
1019
|
+
cfg = await fetchConfig();
|
|
1020
|
+
} catch {
|
|
1021
|
+
return "unconfigured";
|
|
1022
|
+
}
|
|
1023
|
+
if (!cfg.enabled || !cfg.public_key) return "unconfigured";
|
|
1024
|
+
if (Notification.permission === "denied") return "denied";
|
|
1025
|
+
const registration = await getRegistration();
|
|
1026
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
1027
|
+
if (subscription && Notification.permission === "granted") {
|
|
1028
|
+
return "subscribed";
|
|
1029
|
+
}
|
|
1030
|
+
if (Notification.permission === "granted") return "granted";
|
|
1031
|
+
return "default";
|
|
1032
|
+
}
|
|
1033
|
+
async function enable() {
|
|
1034
|
+
if (!supported()) {
|
|
1035
|
+
throw new Error("This browser does not support notifications.");
|
|
1036
|
+
}
|
|
1037
|
+
const permission = await Notification.requestPermission();
|
|
1038
|
+
if (permission !== "granted") {
|
|
1039
|
+
throw new Error("Notification permission was not granted.");
|
|
1040
|
+
}
|
|
1041
|
+
const cfg = await fetchConfig();
|
|
1042
|
+
if (!cfg.enabled || !cfg.public_key) {
|
|
1043
|
+
throw new Error("Push notifications are not configured on the server.");
|
|
1044
|
+
}
|
|
1045
|
+
let registration = await getRegistration();
|
|
1046
|
+
if (!registration) {
|
|
1047
|
+
registration = await navigator.serviceWorker.register(SW_PATH, {
|
|
1048
|
+
scope: SW_SCOPE
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
await navigator.serviceWorker.ready;
|
|
1052
|
+
const existing = await registration.pushManager.getSubscription();
|
|
1053
|
+
const subscription = existing ?? await registration.pushManager.subscribe({
|
|
1054
|
+
userVisibleOnly: true,
|
|
1055
|
+
applicationServerKey: urlBase64ToUint8Array(cfg.public_key)
|
|
1056
|
+
});
|
|
1057
|
+
const json = subscription.toJSON();
|
|
1058
|
+
if (!json.endpoint || !json.keys?.p256dh || !json.keys?.auth) {
|
|
1059
|
+
throw new Error("The browser returned an incomplete push subscription.");
|
|
1060
|
+
}
|
|
1061
|
+
const res = await api.post(SUBSCRIBE_PATH, {
|
|
1062
|
+
endpoint: json.endpoint,
|
|
1063
|
+
keys: { p256dh: json.keys.p256dh, auth: json.keys.auth },
|
|
1064
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : ""
|
|
1065
|
+
});
|
|
1066
|
+
if (!res.ok) throw new Error(await parseApiError(res));
|
|
1067
|
+
}
|
|
1068
|
+
async function disable() {
|
|
1069
|
+
if (!supported()) return;
|
|
1070
|
+
const registration = await getRegistration();
|
|
1071
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
1072
|
+
if (!subscription) return;
|
|
1073
|
+
const endpoint = subscription.endpoint;
|
|
1074
|
+
try {
|
|
1075
|
+
await subscription.unsubscribe();
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
const res = await api.post(UNSUBSCRIBE_PATH, { endpoint });
|
|
1079
|
+
if (!res.ok) throw new Error(await parseApiError(res));
|
|
1080
|
+
}
|
|
1081
|
+
return { supported, getState, enable, disable };
|
|
1082
|
+
}
|
|
1083
|
+
|
|
961
1084
|
// src/ai/suite-ai-job.ts
|
|
962
1085
|
function activeSuiteAiJobs(jobs) {
|
|
963
1086
|
return jobs.filter(
|
|
@@ -1085,6 +1208,7 @@ export {
|
|
|
1085
1208
|
createJsonLd,
|
|
1086
1209
|
createLiveRefresh,
|
|
1087
1210
|
createNotificationsClient,
|
|
1211
|
+
createWebPushClient,
|
|
1088
1212
|
createWorkspaceAppearance,
|
|
1089
1213
|
createWorkspaceAppearanceApi,
|
|
1090
1214
|
defaultInviteSeatType,
|