@xenide-io/suite-client 0.5.0 → 0.6.1
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 +128 -1
- package/dist/index.d.ts +128 -1
- package/dist/index.js +440 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +430 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9417,8 +9417,12 @@ function createAnalyticsClient(config) {
|
|
|
9417
9417
|
defaults,
|
|
9418
9418
|
person_profiles: "identified_only",
|
|
9419
9419
|
autocapture: true,
|
|
9420
|
-
|
|
9421
|
-
|
|
9420
|
+
// Pageviews are captured explicitly via `pageview()` (mount + route
|
|
9421
|
+
// change) so every `$pageview` carries the app/super-properties and the
|
|
9422
|
+
// behaviour is identical across all apps. The SDK's own pageview capture
|
|
9423
|
+
// is disabled to avoid double-counting and `defaults`-dependent behaviour.
|
|
9424
|
+
capture_pageview: false,
|
|
9425
|
+
capture_pageleave: false,
|
|
9422
9426
|
persistence: "localStorage+cookie",
|
|
9423
9427
|
cross_subdomain_cookie: true
|
|
9424
9428
|
});
|
|
@@ -9476,6 +9480,7 @@ function createAnalyticsClient(config) {
|
|
|
9476
9480
|
if (sessionId) headers["X-POSTHOG-SESSION-ID"] = sessionId;
|
|
9477
9481
|
return headers;
|
|
9478
9482
|
}
|
|
9483
|
+
if (isBrowser()) init();
|
|
9479
9484
|
return {
|
|
9480
9485
|
init,
|
|
9481
9486
|
identify,
|
|
@@ -9502,6 +9507,8 @@ function createApiClient(config) {
|
|
|
9502
9507
|
let accessToken = null;
|
|
9503
9508
|
let refreshToken = null;
|
|
9504
9509
|
let activeWorkspaceId = null;
|
|
9510
|
+
const etagBodies = /* @__PURE__ */ new Map();
|
|
9511
|
+
const pendingGets = /* @__PURE__ */ new Map();
|
|
9505
9512
|
let authFailureHandler = null;
|
|
9506
9513
|
let authFailureFired = false;
|
|
9507
9514
|
let refreshPromise = null;
|
|
@@ -9593,6 +9600,7 @@ function createApiClient(config) {
|
|
|
9593
9600
|
window.localStorage.removeItem(WORKSPACE_KEY);
|
|
9594
9601
|
}
|
|
9595
9602
|
if (changed) {
|
|
9603
|
+
etagBodies.clear();
|
|
9596
9604
|
config.onWorkspaceChange?.(id2);
|
|
9597
9605
|
if (id2 && previousWorkspaceId) {
|
|
9598
9606
|
track("Workspace Switched", {
|
|
@@ -9626,6 +9634,8 @@ function createApiClient(config) {
|
|
|
9626
9634
|
accessToken = null;
|
|
9627
9635
|
refreshToken = null;
|
|
9628
9636
|
activeWorkspaceId = null;
|
|
9637
|
+
etagBodies.clear();
|
|
9638
|
+
pendingGets.clear();
|
|
9629
9639
|
if (typeof window !== "undefined") {
|
|
9630
9640
|
window.localStorage.removeItem(ACCESS_KEY);
|
|
9631
9641
|
window.localStorage.removeItem(REFRESH_KEY);
|
|
@@ -9694,6 +9704,30 @@ function createApiClient(config) {
|
|
|
9694
9704
|
}
|
|
9695
9705
|
}
|
|
9696
9706
|
async function apiFetch(path, init = {}) {
|
|
9707
|
+
if ((init.method ?? "GET").toUpperCase() !== "GET") {
|
|
9708
|
+
pendingGets.clear();
|
|
9709
|
+
try {
|
|
9710
|
+
return await apiFetchInternal(path, init);
|
|
9711
|
+
} finally {
|
|
9712
|
+
pendingGets.clear();
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
if (Object.keys(init).length > 0) return apiFetchInternal(path, init);
|
|
9716
|
+
const key = JSON.stringify([
|
|
9717
|
+
getAccessToken(),
|
|
9718
|
+
getActiveWorkspaceId(),
|
|
9719
|
+
path
|
|
9720
|
+
]);
|
|
9721
|
+
let pending = pendingGets.get(key);
|
|
9722
|
+
if (!pending) {
|
|
9723
|
+
pending = apiFetchInternal(path, init).finally(() => {
|
|
9724
|
+
if (pendingGets.get(key) === pending) pendingGets.delete(key);
|
|
9725
|
+
});
|
|
9726
|
+
pendingGets.set(key, pending);
|
|
9727
|
+
}
|
|
9728
|
+
return (await pending).clone();
|
|
9729
|
+
}
|
|
9730
|
+
async function apiFetchInternal(path, init = {}) {
|
|
9697
9731
|
const { skipAuth, ...requestInit } = init;
|
|
9698
9732
|
const token = getAccessToken();
|
|
9699
9733
|
const headers = {
|
|
@@ -9713,6 +9747,12 @@ function createApiClient(config) {
|
|
|
9713
9747
|
headers["X-Active-Workspace-Id"] = wsId;
|
|
9714
9748
|
}
|
|
9715
9749
|
applyContextHeaders(headers);
|
|
9750
|
+
const method = (requestInit.method ?? "GET").toUpperCase();
|
|
9751
|
+
const etagKey = `${method}:${wsId ?? ""}:${path}`;
|
|
9752
|
+
if (method === "GET") {
|
|
9753
|
+
const hit = etagBodies.get(etagKey);
|
|
9754
|
+
if (hit) headers["If-None-Match"] = hit.etag;
|
|
9755
|
+
}
|
|
9716
9756
|
const url = path.startsWith("http") ? path : `${API_URL}${path}`;
|
|
9717
9757
|
if (config.enforceSameOrigin && !skipAuth && path.startsWith("http") && !url.startsWith(API_URL)) {
|
|
9718
9758
|
throw new Error("Refusing to send the session to an unexpected origin.");
|
|
@@ -9744,6 +9784,26 @@ function createApiClient(config) {
|
|
|
9744
9784
|
triggerAuthFailure();
|
|
9745
9785
|
}
|
|
9746
9786
|
}
|
|
9787
|
+
if (method === "GET" && res.status === 304) {
|
|
9788
|
+
const hit = etagBodies.get(etagKey);
|
|
9789
|
+
if (hit) {
|
|
9790
|
+
return new Response(hit.body, {
|
|
9791
|
+
status: 200,
|
|
9792
|
+
headers: { "Content-Type": "application/json", ETag: hit.etag }
|
|
9793
|
+
});
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
if (method === "GET" && res.ok) {
|
|
9797
|
+
const etag = res.headers.get("ETag");
|
|
9798
|
+
if (etag) {
|
|
9799
|
+
const cachedBody = await res.clone().text();
|
|
9800
|
+
if (etagBodies.size >= 40) {
|
|
9801
|
+
const oldest = etagBodies.keys().next().value;
|
|
9802
|
+
if (oldest) etagBodies.delete(oldest);
|
|
9803
|
+
}
|
|
9804
|
+
etagBodies.set(etagKey, { etag, body: cachedBody });
|
|
9805
|
+
}
|
|
9806
|
+
}
|
|
9747
9807
|
return res;
|
|
9748
9808
|
}
|
|
9749
9809
|
async function parseApiError(res) {
|
|
@@ -9838,8 +9898,6 @@ function createAuthReturnPath(config) {
|
|
|
9838
9898
|
}
|
|
9839
9899
|
}
|
|
9840
9900
|
function readPortalHandoff(fallbackNext = DEFAULT_NEXT) {
|
|
9841
|
-
const cached = cachedHandoff(fallbackNext);
|
|
9842
|
-
if (cached) return cached;
|
|
9843
9901
|
const hash = new URLSearchParams(window.location.hash.replace(/^#/, ""));
|
|
9844
9902
|
const query = new URLSearchParams(window.location.search);
|
|
9845
9903
|
const nextRaw = hash.get("next") || query.get("next");
|
|
@@ -9852,19 +9910,18 @@ function createAuthReturnPath(config) {
|
|
|
9852
9910
|
if (result.code || result.token) {
|
|
9853
9911
|
sessionStorage.setItem(HANDOFF_KEY, JSON.stringify(result));
|
|
9854
9912
|
}
|
|
9855
|
-
return result;
|
|
9913
|
+
return result.code || result.token ? result : cachedHandoff(fallbackNext) ?? result;
|
|
9856
9914
|
}
|
|
9857
9915
|
let redeemInflight = null;
|
|
9916
|
+
let redeemKey = "";
|
|
9858
9917
|
function track(event, properties) {
|
|
9859
9918
|
try {
|
|
9860
9919
|
config.onAnalyticsEvent?.(event, properties);
|
|
9861
9920
|
} catch {
|
|
9862
9921
|
}
|
|
9863
9922
|
}
|
|
9864
|
-
async function
|
|
9865
|
-
|
|
9866
|
-
const { code, token, refresh, next } = readPortalHandoff(fallbackNext);
|
|
9867
|
-
if (!code) return { code: null, token, refresh, next };
|
|
9923
|
+
async function requestSwitchCode(apiUrl, code) {
|
|
9924
|
+
try {
|
|
9868
9925
|
const response = await fetch(`${apiUrl.replace(/\/$/, "")}${REDEEM_PATH}`, {
|
|
9869
9926
|
method: "POST",
|
|
9870
9927
|
headers: {
|
|
@@ -9873,21 +9930,57 @@ function createAuthReturnPath(config) {
|
|
|
9873
9930
|
},
|
|
9874
9931
|
body: JSON.stringify({ code })
|
|
9875
9932
|
});
|
|
9876
|
-
if (!response.ok)
|
|
9933
|
+
if (!response.ok) return "dead";
|
|
9934
|
+
return await response.json();
|
|
9935
|
+
} catch {
|
|
9936
|
+
return "retry";
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
async function redeemPortalHandoff(apiUrl, fallbackNext = DEFAULT_NEXT) {
|
|
9940
|
+
const handoff = readPortalHandoff(fallbackNext);
|
|
9941
|
+
const key = JSON.stringify([apiUrl, handoff]);
|
|
9942
|
+
if (redeemInflight && redeemKey === key) {
|
|
9943
|
+
return redeemInflight.then((result) => {
|
|
9944
|
+
if (result.token) sessionStorage.removeItem(HANDOFF_KEY);
|
|
9945
|
+
return result;
|
|
9946
|
+
});
|
|
9947
|
+
}
|
|
9948
|
+
redeemKey = key;
|
|
9949
|
+
const { code, token, refresh, next } = handoff;
|
|
9950
|
+
redeemInflight = (async () => {
|
|
9951
|
+
if (!code) {
|
|
9952
|
+
sessionStorage.removeItem(HANDOFF_KEY);
|
|
9953
|
+
return { code: null, token, refresh, next };
|
|
9954
|
+
}
|
|
9955
|
+
let outcome = await requestSwitchCode(apiUrl, code);
|
|
9956
|
+
if (outcome === "retry") outcome = await requestSwitchCode(apiUrl, code);
|
|
9957
|
+
if (outcome === "retry") {
|
|
9958
|
+
track("Cross-App Switch", { success: false });
|
|
9959
|
+
return { code: null, token: null, refresh: null, next };
|
|
9960
|
+
}
|
|
9961
|
+
if (outcome === "dead") {
|
|
9962
|
+
sessionStorage.removeItem(HANDOFF_KEY);
|
|
9963
|
+
track("Cross-App Switch", { success: false });
|
|
9964
|
+
return { code: null, token: null, refresh: null, next };
|
|
9965
|
+
}
|
|
9966
|
+
const redeemedToken = outcome.token || outcome.access || null;
|
|
9967
|
+
if (!redeemedToken) {
|
|
9968
|
+
sessionStorage.removeItem(HANDOFF_KEY);
|
|
9877
9969
|
track("Cross-App Switch", { success: false });
|
|
9878
9970
|
return { code: null, token: null, refresh: null, next };
|
|
9879
9971
|
}
|
|
9880
|
-
const data = await response.json();
|
|
9881
9972
|
sessionStorage.removeItem(HANDOFF_KEY);
|
|
9882
|
-
|
|
9883
|
-
track("Cross-App Switch", { success: Boolean(redeemedToken) });
|
|
9973
|
+
track("Cross-App Switch", { success: true });
|
|
9884
9974
|
return {
|
|
9885
9975
|
code: null,
|
|
9886
9976
|
token: redeemedToken,
|
|
9887
|
-
refresh:
|
|
9977
|
+
refresh: outcome.refresh || null,
|
|
9888
9978
|
next
|
|
9889
9979
|
};
|
|
9890
|
-
})())
|
|
9980
|
+
})().then((result) => {
|
|
9981
|
+
if (!result.token && redeemKey === key) redeemInflight = null;
|
|
9982
|
+
return result;
|
|
9983
|
+
});
|
|
9891
9984
|
return redeemInflight;
|
|
9892
9985
|
}
|
|
9893
9986
|
function readAuthReturnPath() {
|
|
@@ -10199,6 +10292,165 @@ function createWorkspaceAppearanceApi(config) {
|
|
|
10199
10292
|
};
|
|
10200
10293
|
}
|
|
10201
10294
|
|
|
10295
|
+
// src/cache/persist-query-cache.ts
|
|
10296
|
+
var SUITE_QUERY_CACHE_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
10297
|
+
var MAX_SNAPSHOT_CHARS = 15e5;
|
|
10298
|
+
function suiteCacheBuster(tokenKey, workspaceKey) {
|
|
10299
|
+
if (typeof window === "undefined") return "";
|
|
10300
|
+
let token = null;
|
|
10301
|
+
let workspace = null;
|
|
10302
|
+
try {
|
|
10303
|
+
token = window.localStorage.getItem(tokenKey);
|
|
10304
|
+
workspace = window.localStorage.getItem(workspaceKey) || readCookie("xenide_active_workspace_id");
|
|
10305
|
+
} catch {
|
|
10306
|
+
return "";
|
|
10307
|
+
}
|
|
10308
|
+
const subject = subjectFromAccessToken(token);
|
|
10309
|
+
if (!subject || !workspace) return "";
|
|
10310
|
+
return `${subject}:${workspace}`;
|
|
10311
|
+
}
|
|
10312
|
+
function readJsonSnapshot(key, buster, maxAgeMs) {
|
|
10313
|
+
if (typeof window === "undefined" || !buster) return null;
|
|
10314
|
+
try {
|
|
10315
|
+
const raw = window.localStorage.getItem(key);
|
|
10316
|
+
if (!raw) return null;
|
|
10317
|
+
const parsed = JSON.parse(raw);
|
|
10318
|
+
if (parsed.buster !== buster || typeof parsed.savedAt !== "number") return null;
|
|
10319
|
+
if (Date.now() - parsed.savedAt > maxAgeMs) return null;
|
|
10320
|
+
return parsed.value ?? null;
|
|
10321
|
+
} catch {
|
|
10322
|
+
return null;
|
|
10323
|
+
}
|
|
10324
|
+
}
|
|
10325
|
+
function writeJsonSnapshot(key, buster, value) {
|
|
10326
|
+
if (typeof window === "undefined" || !buster) return;
|
|
10327
|
+
try {
|
|
10328
|
+
const raw = JSON.stringify({ buster, savedAt: Date.now(), value });
|
|
10329
|
+
if (raw.length > MAX_SNAPSHOT_CHARS) return;
|
|
10330
|
+
window.localStorage.setItem(key, raw);
|
|
10331
|
+
} catch {
|
|
10332
|
+
}
|
|
10333
|
+
}
|
|
10334
|
+
function persistSuiteQueries(queryClient, options) {
|
|
10335
|
+
const maxAgeMs = options.maxAgeMs ?? SUITE_QUERY_CACHE_MAX_AGE_MS;
|
|
10336
|
+
restore(queryClient, options, maxAgeMs);
|
|
10337
|
+
let timer = 0;
|
|
10338
|
+
const unsubscribe = queryClient.getQueryCache().subscribe(() => {
|
|
10339
|
+
if (typeof window === "undefined") return;
|
|
10340
|
+
window.clearTimeout(timer);
|
|
10341
|
+
timer = window.setTimeout(() => save(queryClient, options), 400);
|
|
10342
|
+
});
|
|
10343
|
+
return () => {
|
|
10344
|
+
if (typeof window !== "undefined") window.clearTimeout(timer);
|
|
10345
|
+
unsubscribe();
|
|
10346
|
+
};
|
|
10347
|
+
}
|
|
10348
|
+
function restore(queryClient, options, maxAgeMs) {
|
|
10349
|
+
const buster = suiteCacheBuster(options.tokenKey, options.workspaceKey);
|
|
10350
|
+
const stored = readJsonSnapshot(
|
|
10351
|
+
options.storageKey,
|
|
10352
|
+
buster,
|
|
10353
|
+
maxAgeMs
|
|
10354
|
+
);
|
|
10355
|
+
if (!Array.isArray(stored)) return;
|
|
10356
|
+
for (const entry of stored) {
|
|
10357
|
+
if (!entry || !Array.isArray(entry.queryKey) || !allowed(entry.queryKey, options.allow)) {
|
|
10358
|
+
continue;
|
|
10359
|
+
}
|
|
10360
|
+
queryClient.setQueryData(entry.queryKey, entry.data);
|
|
10361
|
+
void queryClient.invalidateQueries({
|
|
10362
|
+
queryKey: entry.queryKey,
|
|
10363
|
+
refetchType: "none"
|
|
10364
|
+
});
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
function save(queryClient, options) {
|
|
10368
|
+
const buster = suiteCacheBuster(options.tokenKey, options.workspaceKey);
|
|
10369
|
+
if (!buster) return;
|
|
10370
|
+
const queries = queryClient.getQueryCache().getAll().filter(
|
|
10371
|
+
(query) => query.state.status === "success" && query.state.data !== void 0 && allowed(query.queryKey, options.allow)
|
|
10372
|
+
).map((query) => ({ queryKey: query.queryKey, data: query.state.data }));
|
|
10373
|
+
writeJsonSnapshot(options.storageKey, buster, queries);
|
|
10374
|
+
}
|
|
10375
|
+
function allowed(queryKey, allow) {
|
|
10376
|
+
return allow.some((rule) => {
|
|
10377
|
+
if (typeof rule === "string") return queryKey[0] === rule;
|
|
10378
|
+
return rule.every((part, index) => queryKey[index] === part);
|
|
10379
|
+
});
|
|
10380
|
+
}
|
|
10381
|
+
function readCookie(name) {
|
|
10382
|
+
if (typeof document === "undefined") return null;
|
|
10383
|
+
const prefix = `${name}=`;
|
|
10384
|
+
const entry = document.cookie.split("; ").find((item) => item.startsWith(prefix));
|
|
10385
|
+
if (!entry) return null;
|
|
10386
|
+
try {
|
|
10387
|
+
return decodeURIComponent(entry.slice(prefix.length)) || null;
|
|
10388
|
+
} catch {
|
|
10389
|
+
return entry.slice(prefix.length) || null;
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
function subjectFromAccessToken(token) {
|
|
10393
|
+
if (!token) return "";
|
|
10394
|
+
const segment = token.split(".")[1];
|
|
10395
|
+
if (!segment) return "";
|
|
10396
|
+
try {
|
|
10397
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
10398
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
10399
|
+
const json = JSON.parse(atob(padded));
|
|
10400
|
+
return typeof json.sub === "string" ? json.sub : "";
|
|
10401
|
+
} catch {
|
|
10402
|
+
return "";
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
var SUITE_APP_QUERY_CACHES = {
|
|
10406
|
+
kraken: {
|
|
10407
|
+
storageKey: "kraken-query-cache:v1",
|
|
10408
|
+
tokenKey: "kraken_access_token",
|
|
10409
|
+
workspaceKey: "kraken_active_workspace_id",
|
|
10410
|
+
allow: ["kraken-sidebar"]
|
|
10411
|
+
},
|
|
10412
|
+
shellstack: {
|
|
10413
|
+
storageKey: "shellstack-query-cache:v1",
|
|
10414
|
+
tokenKey: "portal_access_token",
|
|
10415
|
+
workspaceKey: "portal_active_workspace_id",
|
|
10416
|
+
allow: ["calendar-events", "org-workspaces", "organisation", "org-team", "directory"]
|
|
10417
|
+
},
|
|
10418
|
+
tides: {
|
|
10419
|
+
storageKey: "tides-query-cache:v1",
|
|
10420
|
+
tokenKey: "tides_access_token",
|
|
10421
|
+
workspaceKey: "tides_active_workspace_id",
|
|
10422
|
+
allow: [
|
|
10423
|
+
["tides", "projects"],
|
|
10424
|
+
["tides", "clients"],
|
|
10425
|
+
["tides", "workspace-members"],
|
|
10426
|
+
["tides", "workspace-tags"],
|
|
10427
|
+
["tides", "project-statuses"]
|
|
10428
|
+
]
|
|
10429
|
+
},
|
|
10430
|
+
turtletime: {
|
|
10431
|
+
storageKey: "tt-query-cache:v1",
|
|
10432
|
+
tokenKey: "tt_access_token",
|
|
10433
|
+
workspaceKey: "tt_active_workspace_id",
|
|
10434
|
+
allow: [
|
|
10435
|
+
["tt", "tracker-catalog"],
|
|
10436
|
+
["tt", "projects"],
|
|
10437
|
+
["tt", "clients"],
|
|
10438
|
+
["tt", "categories"]
|
|
10439
|
+
]
|
|
10440
|
+
},
|
|
10441
|
+
shelly: {
|
|
10442
|
+
storageKey: "shelly-query-cache:v1",
|
|
10443
|
+
tokenKey: "shelly_access_token",
|
|
10444
|
+
workspaceKey: "shelly_active_workspace_id",
|
|
10445
|
+
allow: [
|
|
10446
|
+
["shelly", "skills"],
|
|
10447
|
+
["shelly", "crews"],
|
|
10448
|
+
["shelly", "mcp-servers"],
|
|
10449
|
+
["shelly", "models"]
|
|
10450
|
+
]
|
|
10451
|
+
}
|
|
10452
|
+
};
|
|
10453
|
+
|
|
10202
10454
|
// src/sync/create-cross-app-sync.ts
|
|
10203
10455
|
function createCrossAppSync(config) {
|
|
10204
10456
|
const pollMs = config.pollMs ?? 5e3;
|
|
@@ -10210,40 +10462,49 @@ function createCrossAppSync(config) {
|
|
|
10210
10462
|
const subscribers = [];
|
|
10211
10463
|
let pollInterval = null;
|
|
10212
10464
|
let pollListeners = 0;
|
|
10465
|
+
let inFlight = false;
|
|
10466
|
+
let primed = false;
|
|
10467
|
+
let scope;
|
|
10468
|
+
let lastRequestedScope;
|
|
10469
|
+
let lastRequestedAt;
|
|
10213
10470
|
async function fetchCheckpoint() {
|
|
10471
|
+
if (inFlight || document.visibilityState !== "visible") return null;
|
|
10472
|
+
const requestScope = config.getScope?.();
|
|
10473
|
+
if (requestScope === lastRequestedScope && lastRequestedAt !== void 0 && Date.now() - lastRequestedAt < pollMs)
|
|
10474
|
+
return null;
|
|
10475
|
+
inFlight = true;
|
|
10476
|
+
lastRequestedScope = requestScope;
|
|
10477
|
+
lastRequestedAt = Date.now();
|
|
10214
10478
|
try {
|
|
10215
10479
|
const res = await config.api.get(config.checkpointPath);
|
|
10216
10480
|
if (!res.ok) return null;
|
|
10217
|
-
|
|
10481
|
+
const data = await res.json();
|
|
10482
|
+
if (requestScope !== config.getScope?.()) return null;
|
|
10483
|
+
if (scope !== requestScope) {
|
|
10484
|
+
primed = false;
|
|
10485
|
+
scope = requestScope;
|
|
10486
|
+
}
|
|
10487
|
+
return data;
|
|
10218
10488
|
} catch {
|
|
10219
10489
|
return null;
|
|
10220
|
-
}
|
|
10221
|
-
|
|
10222
|
-
function notify(topic) {
|
|
10223
|
-
for (const sub of subscribers) {
|
|
10224
|
-
if (!sub.topic || sub.topic === topic) {
|
|
10225
|
-
if (lastKnown[topic] > sub.lastSync) {
|
|
10226
|
-
sub.lastSync = lastKnown[topic];
|
|
10227
|
-
sub.callback();
|
|
10228
|
-
}
|
|
10229
|
-
}
|
|
10490
|
+
} finally {
|
|
10491
|
+
inFlight = false;
|
|
10230
10492
|
}
|
|
10231
10493
|
}
|
|
10232
10494
|
function checkAndNotify(data) {
|
|
10233
|
-
const
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
notify("integration");
|
|
10495
|
+
const next = {
|
|
10496
|
+
tasks: data.tasks_synced_at || 0,
|
|
10497
|
+
time: data.time_synced_at || 0,
|
|
10498
|
+
integration: data.integration_synced_at || 0
|
|
10499
|
+
};
|
|
10500
|
+
const changed = ["tasks", "time", "integration"].filter(
|
|
10501
|
+
(topic) => primed && next[topic] > lastKnown[topic]
|
|
10502
|
+
);
|
|
10503
|
+
Object.assign(lastKnown, next);
|
|
10504
|
+
primed = true;
|
|
10505
|
+
for (const sub of subscribers) {
|
|
10506
|
+
if (changed.some((topic) => !sub.topic || sub.topic === topic))
|
|
10507
|
+
sub.callback();
|
|
10247
10508
|
}
|
|
10248
10509
|
}
|
|
10249
10510
|
function startPolling() {
|
|
@@ -10270,8 +10531,7 @@ function createCrossAppSync(config) {
|
|
|
10270
10531
|
};
|
|
10271
10532
|
const sub = {
|
|
10272
10533
|
topic,
|
|
10273
|
-
callback: onSync
|
|
10274
|
-
lastSync: lastKnown[topic || "tasks"]
|
|
10534
|
+
callback: onSync
|
|
10275
10535
|
};
|
|
10276
10536
|
subscribers.push(sub);
|
|
10277
10537
|
pollListeners += 1;
|
|
@@ -10342,6 +10602,80 @@ function createLiveRefresh(sync) {
|
|
|
10342
10602
|
return { subscribeLiveRefresh, hasPendingCrossAppSync };
|
|
10343
10603
|
}
|
|
10344
10604
|
|
|
10605
|
+
// src/sync/use-suite-revisions.ts
|
|
10606
|
+
import { useEffect, useRef } from "react";
|
|
10607
|
+
function nextRevisionTick(seen, ready, data) {
|
|
10608
|
+
if (!data) return { seen, ready, changed: [] };
|
|
10609
|
+
const next = { ...seen };
|
|
10610
|
+
if (!ready) {
|
|
10611
|
+
for (const [topic, value] of Object.entries(data)) {
|
|
10612
|
+
if (typeof value === "number") next[topic] = value;
|
|
10613
|
+
}
|
|
10614
|
+
return { seen: next, ready: true, changed: [] };
|
|
10615
|
+
}
|
|
10616
|
+
const changed = [];
|
|
10617
|
+
for (const [topic, value] of Object.entries(data)) {
|
|
10618
|
+
if (typeof value !== "number" || next[topic] === value) continue;
|
|
10619
|
+
next[topic] = value;
|
|
10620
|
+
changed.push(topic);
|
|
10621
|
+
}
|
|
10622
|
+
return { seen: next, ready: true, changed };
|
|
10623
|
+
}
|
|
10624
|
+
function useSuiteRevisions(options) {
|
|
10625
|
+
const loadRef = useRef(options.load);
|
|
10626
|
+
const onTopicRef = useRef(options.onTopic);
|
|
10627
|
+
loadRef.current = options.load;
|
|
10628
|
+
onTopicRef.current = options.onTopic;
|
|
10629
|
+
const pollMs = options.pollMs ?? 15e3;
|
|
10630
|
+
const scope = options.scope ?? "";
|
|
10631
|
+
const seenScope = useRef(null);
|
|
10632
|
+
useEffect(() => {
|
|
10633
|
+
if (!options.enabled || typeof window === "undefined") return;
|
|
10634
|
+
const switching = seenScope.current !== null && seenScope.current !== scope;
|
|
10635
|
+
seenScope.current = scope;
|
|
10636
|
+
let seen = {};
|
|
10637
|
+
let ready = switching;
|
|
10638
|
+
let timer;
|
|
10639
|
+
let inFlight = false;
|
|
10640
|
+
let disposed = false;
|
|
10641
|
+
const tick = async () => {
|
|
10642
|
+
if (inFlight || disposed) return;
|
|
10643
|
+
inFlight = true;
|
|
10644
|
+
try {
|
|
10645
|
+
const data = await loadRef.current();
|
|
10646
|
+
if (disposed) return;
|
|
10647
|
+
const step = nextRevisionTick(seen, ready, data);
|
|
10648
|
+
seen = step.seen;
|
|
10649
|
+
ready = step.ready;
|
|
10650
|
+
for (const topic of step.changed) onTopicRef.current(topic);
|
|
10651
|
+
} catch {
|
|
10652
|
+
} finally {
|
|
10653
|
+
inFlight = false;
|
|
10654
|
+
}
|
|
10655
|
+
};
|
|
10656
|
+
const stop = () => {
|
|
10657
|
+
if (timer !== void 0) window.clearInterval(timer);
|
|
10658
|
+
timer = void 0;
|
|
10659
|
+
};
|
|
10660
|
+
const start = () => {
|
|
10661
|
+
if (timer !== void 0) return;
|
|
10662
|
+
void tick();
|
|
10663
|
+
timer = window.setInterval(() => void tick(), pollMs);
|
|
10664
|
+
};
|
|
10665
|
+
const onVisibility = () => {
|
|
10666
|
+
if (document.visibilityState === "visible") start();
|
|
10667
|
+
else stop();
|
|
10668
|
+
};
|
|
10669
|
+
if (document.visibilityState === "visible") start();
|
|
10670
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
10671
|
+
return () => {
|
|
10672
|
+
disposed = true;
|
|
10673
|
+
stop();
|
|
10674
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
10675
|
+
};
|
|
10676
|
+
}, [options.enabled, pollMs, scope]);
|
|
10677
|
+
}
|
|
10678
|
+
|
|
10345
10679
|
// src/notifications/create-notifications-client.ts
|
|
10346
10680
|
function createNotificationsClient(config) {
|
|
10347
10681
|
const { api, parseApiError } = config;
|
|
@@ -10487,6 +10821,47 @@ function createWebPushClient(config) {
|
|
|
10487
10821
|
return { supported, getState, enable, disable };
|
|
10488
10822
|
}
|
|
10489
10823
|
|
|
10824
|
+
// src/notifications/use-email-notification-preference.ts
|
|
10825
|
+
import { useCallback, useEffect as useEffect2, useState } from "react";
|
|
10826
|
+
var EMAIL_PREFERENCE_PATH = "/api/notifications/email/";
|
|
10827
|
+
function useEmailNotificationPreference(api, parseApiError) {
|
|
10828
|
+
const [enabled, setEnabled] = useState(false);
|
|
10829
|
+
const [busy, setBusy] = useState(false);
|
|
10830
|
+
const [error, setError] = useState("");
|
|
10831
|
+
useEffect2(() => {
|
|
10832
|
+
let cancelled = false;
|
|
10833
|
+
api.get(EMAIL_PREFERENCE_PATH).then(async (res) => {
|
|
10834
|
+
if (!res.ok || cancelled) return;
|
|
10835
|
+
const data = await res.json();
|
|
10836
|
+
if (!cancelled) setEnabled(Boolean(data.enabled));
|
|
10837
|
+
}).catch(() => {
|
|
10838
|
+
});
|
|
10839
|
+
return () => {
|
|
10840
|
+
cancelled = true;
|
|
10841
|
+
};
|
|
10842
|
+
}, [api]);
|
|
10843
|
+
const onChange = useCallback(
|
|
10844
|
+
(next) => {
|
|
10845
|
+
setBusy(true);
|
|
10846
|
+
setError("");
|
|
10847
|
+
setEnabled(next);
|
|
10848
|
+
api.patch(EMAIL_PREFERENCE_PATH, { enabled: next }).then(async (res) => {
|
|
10849
|
+
if (!res.ok) {
|
|
10850
|
+
setEnabled(!next);
|
|
10851
|
+
setError(await parseApiError(res));
|
|
10852
|
+
}
|
|
10853
|
+
}).catch((err) => {
|
|
10854
|
+
setEnabled(!next);
|
|
10855
|
+
setError(
|
|
10856
|
+
err instanceof Error ? err.message : "Could not save email notifications."
|
|
10857
|
+
);
|
|
10858
|
+
}).finally(() => setBusy(false));
|
|
10859
|
+
},
|
|
10860
|
+
[api, parseApiError]
|
|
10861
|
+
);
|
|
10862
|
+
return { enabled, busy, error, onChange };
|
|
10863
|
+
}
|
|
10864
|
+
|
|
10490
10865
|
// src/ai/suite-ai-job.ts
|
|
10491
10866
|
function activeSuiteAiJobs(jobs) {
|
|
10492
10867
|
return jobs.filter(
|
|
@@ -10568,12 +10943,12 @@ function createJsonLd(config) {
|
|
|
10568
10943
|
}
|
|
10569
10944
|
|
|
10570
10945
|
// src/hooks/use-is-night.ts
|
|
10571
|
-
import { useEffect, useState } from "react";
|
|
10946
|
+
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
10572
10947
|
var NIGHT_START_HOUR = 18;
|
|
10573
10948
|
var NIGHT_END_HOUR = 6;
|
|
10574
10949
|
function useIsNight() {
|
|
10575
|
-
const [isNight, setIsNight] =
|
|
10576
|
-
|
|
10950
|
+
const [isNight, setIsNight] = useState2(false);
|
|
10951
|
+
useEffect3(() => {
|
|
10577
10952
|
const check = () => {
|
|
10578
10953
|
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
10579
10954
|
return hour >= NIGHT_START_HOUR || hour < NIGHT_END_HOUR;
|
|
@@ -10597,6 +10972,8 @@ export {
|
|
|
10597
10972
|
ERROR_RETRYABLE,
|
|
10598
10973
|
FREE_PLAN_SEAT_HELP,
|
|
10599
10974
|
ORGANISATION_SEAT_HELP,
|
|
10975
|
+
SUITE_APP_QUERY_CACHES,
|
|
10976
|
+
SUITE_QUERY_CACHE_MAX_AGE_MS,
|
|
10600
10977
|
TIDES_ACCESS_HELP,
|
|
10601
10978
|
TURTLETIME_ACCESS_HELP,
|
|
10602
10979
|
WEBHOOK_ACTIVITY_ACTIONS,
|
|
@@ -10624,9 +11001,12 @@ export {
|
|
|
10624
11001
|
formatAppError,
|
|
10625
11002
|
hasAppAccess,
|
|
10626
11003
|
isErrorCode,
|
|
11004
|
+
nextRevisionTick,
|
|
10627
11005
|
normalizeError,
|
|
10628
11006
|
normalizeThrown,
|
|
11007
|
+
persistSuiteQueries,
|
|
10629
11008
|
planSeatSummary,
|
|
11009
|
+
readJsonSnapshot,
|
|
10630
11010
|
resolveSubscriptionTier,
|
|
10631
11011
|
seatTypeDescription,
|
|
10632
11012
|
seatTypeFromFlags,
|
|
@@ -10634,10 +11014,14 @@ export {
|
|
|
10634
11014
|
selectedToWebhookActions,
|
|
10635
11015
|
sortedSuiteAiEvents,
|
|
10636
11016
|
suiteAiEventSummary,
|
|
11017
|
+
suiteCacheBuster,
|
|
10637
11018
|
tierAllowsTidesOnlySeats,
|
|
10638
11019
|
tierAllowsTurtletime,
|
|
10639
11020
|
tierLabel,
|
|
11021
|
+
useEmailNotificationPreference,
|
|
10640
11022
|
useIsNight,
|
|
10641
|
-
|
|
11023
|
+
useSuiteRevisions,
|
|
11024
|
+
webhookActionsToSelected,
|
|
11025
|
+
writeJsonSnapshot
|
|
10642
11026
|
};
|
|
10643
11027
|
//# sourceMappingURL=index.mjs.map
|