@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.js
CHANGED
|
@@ -26,6 +26,8 @@ __export(index_exports, {
|
|
|
26
26
|
ERROR_RETRYABLE: () => ERROR_RETRYABLE,
|
|
27
27
|
FREE_PLAN_SEAT_HELP: () => FREE_PLAN_SEAT_HELP,
|
|
28
28
|
ORGANISATION_SEAT_HELP: () => ORGANISATION_SEAT_HELP,
|
|
29
|
+
SUITE_APP_QUERY_CACHES: () => SUITE_APP_QUERY_CACHES,
|
|
30
|
+
SUITE_QUERY_CACHE_MAX_AGE_MS: () => SUITE_QUERY_CACHE_MAX_AGE_MS,
|
|
29
31
|
TIDES_ACCESS_HELP: () => TIDES_ACCESS_HELP,
|
|
30
32
|
TURTLETIME_ACCESS_HELP: () => TURTLETIME_ACCESS_HELP,
|
|
31
33
|
WEBHOOK_ACTIVITY_ACTIONS: () => WEBHOOK_ACTIVITY_ACTIONS,
|
|
@@ -53,9 +55,12 @@ __export(index_exports, {
|
|
|
53
55
|
formatAppError: () => formatAppError,
|
|
54
56
|
hasAppAccess: () => hasAppAccess,
|
|
55
57
|
isErrorCode: () => isErrorCode,
|
|
58
|
+
nextRevisionTick: () => nextRevisionTick,
|
|
56
59
|
normalizeError: () => normalizeError,
|
|
57
60
|
normalizeThrown: () => normalizeThrown,
|
|
61
|
+
persistSuiteQueries: () => persistSuiteQueries,
|
|
58
62
|
planSeatSummary: () => planSeatSummary,
|
|
63
|
+
readJsonSnapshot: () => readJsonSnapshot,
|
|
59
64
|
resolveSubscriptionTier: () => resolveSubscriptionTier,
|
|
60
65
|
seatTypeDescription: () => seatTypeDescription,
|
|
61
66
|
seatTypeFromFlags: () => seatTypeFromFlags,
|
|
@@ -63,11 +68,15 @@ __export(index_exports, {
|
|
|
63
68
|
selectedToWebhookActions: () => selectedToWebhookActions,
|
|
64
69
|
sortedSuiteAiEvents: () => sortedSuiteAiEvents,
|
|
65
70
|
suiteAiEventSummary: () => suiteAiEventSummary,
|
|
71
|
+
suiteCacheBuster: () => suiteCacheBuster,
|
|
66
72
|
tierAllowsTidesOnlySeats: () => tierAllowsTidesOnlySeats,
|
|
67
73
|
tierAllowsTurtletime: () => tierAllowsTurtletime,
|
|
68
74
|
tierLabel: () => tierLabel,
|
|
75
|
+
useEmailNotificationPreference: () => useEmailNotificationPreference,
|
|
69
76
|
useIsNight: () => useIsNight,
|
|
70
|
-
|
|
77
|
+
useSuiteRevisions: () => useSuiteRevisions,
|
|
78
|
+
webhookActionsToSelected: () => webhookActionsToSelected,
|
|
79
|
+
writeJsonSnapshot: () => writeJsonSnapshot
|
|
71
80
|
});
|
|
72
81
|
module.exports = __toCommonJS(index_exports);
|
|
73
82
|
|
|
@@ -9490,8 +9499,12 @@ function createAnalyticsClient(config) {
|
|
|
9490
9499
|
defaults,
|
|
9491
9500
|
person_profiles: "identified_only",
|
|
9492
9501
|
autocapture: true,
|
|
9493
|
-
|
|
9494
|
-
|
|
9502
|
+
// Pageviews are captured explicitly via `pageview()` (mount + route
|
|
9503
|
+
// change) so every `$pageview` carries the app/super-properties and the
|
|
9504
|
+
// behaviour is identical across all apps. The SDK's own pageview capture
|
|
9505
|
+
// is disabled to avoid double-counting and `defaults`-dependent behaviour.
|
|
9506
|
+
capture_pageview: false,
|
|
9507
|
+
capture_pageleave: false,
|
|
9495
9508
|
persistence: "localStorage+cookie",
|
|
9496
9509
|
cross_subdomain_cookie: true
|
|
9497
9510
|
});
|
|
@@ -9549,6 +9562,7 @@ function createAnalyticsClient(config) {
|
|
|
9549
9562
|
if (sessionId) headers["X-POSTHOG-SESSION-ID"] = sessionId;
|
|
9550
9563
|
return headers;
|
|
9551
9564
|
}
|
|
9565
|
+
if (isBrowser()) init();
|
|
9552
9566
|
return {
|
|
9553
9567
|
init,
|
|
9554
9568
|
identify,
|
|
@@ -9575,6 +9589,8 @@ function createApiClient(config) {
|
|
|
9575
9589
|
let accessToken = null;
|
|
9576
9590
|
let refreshToken = null;
|
|
9577
9591
|
let activeWorkspaceId = null;
|
|
9592
|
+
const etagBodies = /* @__PURE__ */ new Map();
|
|
9593
|
+
const pendingGets = /* @__PURE__ */ new Map();
|
|
9578
9594
|
let authFailureHandler = null;
|
|
9579
9595
|
let authFailureFired = false;
|
|
9580
9596
|
let refreshPromise = null;
|
|
@@ -9666,6 +9682,7 @@ function createApiClient(config) {
|
|
|
9666
9682
|
window.localStorage.removeItem(WORKSPACE_KEY);
|
|
9667
9683
|
}
|
|
9668
9684
|
if (changed) {
|
|
9685
|
+
etagBodies.clear();
|
|
9669
9686
|
config.onWorkspaceChange?.(id2);
|
|
9670
9687
|
if (id2 && previousWorkspaceId) {
|
|
9671
9688
|
track("Workspace Switched", {
|
|
@@ -9699,6 +9716,8 @@ function createApiClient(config) {
|
|
|
9699
9716
|
accessToken = null;
|
|
9700
9717
|
refreshToken = null;
|
|
9701
9718
|
activeWorkspaceId = null;
|
|
9719
|
+
etagBodies.clear();
|
|
9720
|
+
pendingGets.clear();
|
|
9702
9721
|
if (typeof window !== "undefined") {
|
|
9703
9722
|
window.localStorage.removeItem(ACCESS_KEY);
|
|
9704
9723
|
window.localStorage.removeItem(REFRESH_KEY);
|
|
@@ -9767,6 +9786,30 @@ function createApiClient(config) {
|
|
|
9767
9786
|
}
|
|
9768
9787
|
}
|
|
9769
9788
|
async function apiFetch(path, init = {}) {
|
|
9789
|
+
if ((init.method ?? "GET").toUpperCase() !== "GET") {
|
|
9790
|
+
pendingGets.clear();
|
|
9791
|
+
try {
|
|
9792
|
+
return await apiFetchInternal(path, init);
|
|
9793
|
+
} finally {
|
|
9794
|
+
pendingGets.clear();
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
if (Object.keys(init).length > 0) return apiFetchInternal(path, init);
|
|
9798
|
+
const key = JSON.stringify([
|
|
9799
|
+
getAccessToken(),
|
|
9800
|
+
getActiveWorkspaceId(),
|
|
9801
|
+
path
|
|
9802
|
+
]);
|
|
9803
|
+
let pending = pendingGets.get(key);
|
|
9804
|
+
if (!pending) {
|
|
9805
|
+
pending = apiFetchInternal(path, init).finally(() => {
|
|
9806
|
+
if (pendingGets.get(key) === pending) pendingGets.delete(key);
|
|
9807
|
+
});
|
|
9808
|
+
pendingGets.set(key, pending);
|
|
9809
|
+
}
|
|
9810
|
+
return (await pending).clone();
|
|
9811
|
+
}
|
|
9812
|
+
async function apiFetchInternal(path, init = {}) {
|
|
9770
9813
|
const { skipAuth, ...requestInit } = init;
|
|
9771
9814
|
const token = getAccessToken();
|
|
9772
9815
|
const headers = {
|
|
@@ -9786,6 +9829,12 @@ function createApiClient(config) {
|
|
|
9786
9829
|
headers["X-Active-Workspace-Id"] = wsId;
|
|
9787
9830
|
}
|
|
9788
9831
|
applyContextHeaders(headers);
|
|
9832
|
+
const method = (requestInit.method ?? "GET").toUpperCase();
|
|
9833
|
+
const etagKey = `${method}:${wsId ?? ""}:${path}`;
|
|
9834
|
+
if (method === "GET") {
|
|
9835
|
+
const hit = etagBodies.get(etagKey);
|
|
9836
|
+
if (hit) headers["If-None-Match"] = hit.etag;
|
|
9837
|
+
}
|
|
9789
9838
|
const url = path.startsWith("http") ? path : `${API_URL}${path}`;
|
|
9790
9839
|
if (config.enforceSameOrigin && !skipAuth && path.startsWith("http") && !url.startsWith(API_URL)) {
|
|
9791
9840
|
throw new Error("Refusing to send the session to an unexpected origin.");
|
|
@@ -9817,6 +9866,26 @@ function createApiClient(config) {
|
|
|
9817
9866
|
triggerAuthFailure();
|
|
9818
9867
|
}
|
|
9819
9868
|
}
|
|
9869
|
+
if (method === "GET" && res.status === 304) {
|
|
9870
|
+
const hit = etagBodies.get(etagKey);
|
|
9871
|
+
if (hit) {
|
|
9872
|
+
return new Response(hit.body, {
|
|
9873
|
+
status: 200,
|
|
9874
|
+
headers: { "Content-Type": "application/json", ETag: hit.etag }
|
|
9875
|
+
});
|
|
9876
|
+
}
|
|
9877
|
+
}
|
|
9878
|
+
if (method === "GET" && res.ok) {
|
|
9879
|
+
const etag = res.headers.get("ETag");
|
|
9880
|
+
if (etag) {
|
|
9881
|
+
const cachedBody = await res.clone().text();
|
|
9882
|
+
if (etagBodies.size >= 40) {
|
|
9883
|
+
const oldest = etagBodies.keys().next().value;
|
|
9884
|
+
if (oldest) etagBodies.delete(oldest);
|
|
9885
|
+
}
|
|
9886
|
+
etagBodies.set(etagKey, { etag, body: cachedBody });
|
|
9887
|
+
}
|
|
9888
|
+
}
|
|
9820
9889
|
return res;
|
|
9821
9890
|
}
|
|
9822
9891
|
async function parseApiError(res) {
|
|
@@ -9911,8 +9980,6 @@ function createAuthReturnPath(config) {
|
|
|
9911
9980
|
}
|
|
9912
9981
|
}
|
|
9913
9982
|
function readPortalHandoff(fallbackNext = DEFAULT_NEXT) {
|
|
9914
|
-
const cached = cachedHandoff(fallbackNext);
|
|
9915
|
-
if (cached) return cached;
|
|
9916
9983
|
const hash = new URLSearchParams(window.location.hash.replace(/^#/, ""));
|
|
9917
9984
|
const query = new URLSearchParams(window.location.search);
|
|
9918
9985
|
const nextRaw = hash.get("next") || query.get("next");
|
|
@@ -9925,19 +9992,18 @@ function createAuthReturnPath(config) {
|
|
|
9925
9992
|
if (result.code || result.token) {
|
|
9926
9993
|
sessionStorage.setItem(HANDOFF_KEY, JSON.stringify(result));
|
|
9927
9994
|
}
|
|
9928
|
-
return result;
|
|
9995
|
+
return result.code || result.token ? result : cachedHandoff(fallbackNext) ?? result;
|
|
9929
9996
|
}
|
|
9930
9997
|
let redeemInflight = null;
|
|
9998
|
+
let redeemKey = "";
|
|
9931
9999
|
function track(event, properties) {
|
|
9932
10000
|
try {
|
|
9933
10001
|
config.onAnalyticsEvent?.(event, properties);
|
|
9934
10002
|
} catch {
|
|
9935
10003
|
}
|
|
9936
10004
|
}
|
|
9937
|
-
async function
|
|
9938
|
-
|
|
9939
|
-
const { code, token, refresh, next } = readPortalHandoff(fallbackNext);
|
|
9940
|
-
if (!code) return { code: null, token, refresh, next };
|
|
10005
|
+
async function requestSwitchCode(apiUrl, code) {
|
|
10006
|
+
try {
|
|
9941
10007
|
const response = await fetch(`${apiUrl.replace(/\/$/, "")}${REDEEM_PATH}`, {
|
|
9942
10008
|
method: "POST",
|
|
9943
10009
|
headers: {
|
|
@@ -9946,21 +10012,57 @@ function createAuthReturnPath(config) {
|
|
|
9946
10012
|
},
|
|
9947
10013
|
body: JSON.stringify({ code })
|
|
9948
10014
|
});
|
|
9949
|
-
if (!response.ok)
|
|
10015
|
+
if (!response.ok) return "dead";
|
|
10016
|
+
return await response.json();
|
|
10017
|
+
} catch {
|
|
10018
|
+
return "retry";
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
async function redeemPortalHandoff(apiUrl, fallbackNext = DEFAULT_NEXT) {
|
|
10022
|
+
const handoff = readPortalHandoff(fallbackNext);
|
|
10023
|
+
const key = JSON.stringify([apiUrl, handoff]);
|
|
10024
|
+
if (redeemInflight && redeemKey === key) {
|
|
10025
|
+
return redeemInflight.then((result) => {
|
|
10026
|
+
if (result.token) sessionStorage.removeItem(HANDOFF_KEY);
|
|
10027
|
+
return result;
|
|
10028
|
+
});
|
|
10029
|
+
}
|
|
10030
|
+
redeemKey = key;
|
|
10031
|
+
const { code, token, refresh, next } = handoff;
|
|
10032
|
+
redeemInflight = (async () => {
|
|
10033
|
+
if (!code) {
|
|
10034
|
+
sessionStorage.removeItem(HANDOFF_KEY);
|
|
10035
|
+
return { code: null, token, refresh, next };
|
|
10036
|
+
}
|
|
10037
|
+
let outcome = await requestSwitchCode(apiUrl, code);
|
|
10038
|
+
if (outcome === "retry") outcome = await requestSwitchCode(apiUrl, code);
|
|
10039
|
+
if (outcome === "retry") {
|
|
10040
|
+
track("Cross-App Switch", { success: false });
|
|
10041
|
+
return { code: null, token: null, refresh: null, next };
|
|
10042
|
+
}
|
|
10043
|
+
if (outcome === "dead") {
|
|
10044
|
+
sessionStorage.removeItem(HANDOFF_KEY);
|
|
10045
|
+
track("Cross-App Switch", { success: false });
|
|
10046
|
+
return { code: null, token: null, refresh: null, next };
|
|
10047
|
+
}
|
|
10048
|
+
const redeemedToken = outcome.token || outcome.access || null;
|
|
10049
|
+
if (!redeemedToken) {
|
|
10050
|
+
sessionStorage.removeItem(HANDOFF_KEY);
|
|
9950
10051
|
track("Cross-App Switch", { success: false });
|
|
9951
10052
|
return { code: null, token: null, refresh: null, next };
|
|
9952
10053
|
}
|
|
9953
|
-
const data = await response.json();
|
|
9954
10054
|
sessionStorage.removeItem(HANDOFF_KEY);
|
|
9955
|
-
|
|
9956
|
-
track("Cross-App Switch", { success: Boolean(redeemedToken) });
|
|
10055
|
+
track("Cross-App Switch", { success: true });
|
|
9957
10056
|
return {
|
|
9958
10057
|
code: null,
|
|
9959
10058
|
token: redeemedToken,
|
|
9960
|
-
refresh:
|
|
10059
|
+
refresh: outcome.refresh || null,
|
|
9961
10060
|
next
|
|
9962
10061
|
};
|
|
9963
|
-
})())
|
|
10062
|
+
})().then((result) => {
|
|
10063
|
+
if (!result.token && redeemKey === key) redeemInflight = null;
|
|
10064
|
+
return result;
|
|
10065
|
+
});
|
|
9964
10066
|
return redeemInflight;
|
|
9965
10067
|
}
|
|
9966
10068
|
function readAuthReturnPath() {
|
|
@@ -10272,6 +10374,165 @@ function createWorkspaceAppearanceApi(config) {
|
|
|
10272
10374
|
};
|
|
10273
10375
|
}
|
|
10274
10376
|
|
|
10377
|
+
// src/cache/persist-query-cache.ts
|
|
10378
|
+
var SUITE_QUERY_CACHE_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
10379
|
+
var MAX_SNAPSHOT_CHARS = 15e5;
|
|
10380
|
+
function suiteCacheBuster(tokenKey, workspaceKey) {
|
|
10381
|
+
if (typeof window === "undefined") return "";
|
|
10382
|
+
let token = null;
|
|
10383
|
+
let workspace = null;
|
|
10384
|
+
try {
|
|
10385
|
+
token = window.localStorage.getItem(tokenKey);
|
|
10386
|
+
workspace = window.localStorage.getItem(workspaceKey) || readCookie("xenide_active_workspace_id");
|
|
10387
|
+
} catch {
|
|
10388
|
+
return "";
|
|
10389
|
+
}
|
|
10390
|
+
const subject = subjectFromAccessToken(token);
|
|
10391
|
+
if (!subject || !workspace) return "";
|
|
10392
|
+
return `${subject}:${workspace}`;
|
|
10393
|
+
}
|
|
10394
|
+
function readJsonSnapshot(key, buster, maxAgeMs) {
|
|
10395
|
+
if (typeof window === "undefined" || !buster) return null;
|
|
10396
|
+
try {
|
|
10397
|
+
const raw = window.localStorage.getItem(key);
|
|
10398
|
+
if (!raw) return null;
|
|
10399
|
+
const parsed = JSON.parse(raw);
|
|
10400
|
+
if (parsed.buster !== buster || typeof parsed.savedAt !== "number") return null;
|
|
10401
|
+
if (Date.now() - parsed.savedAt > maxAgeMs) return null;
|
|
10402
|
+
return parsed.value ?? null;
|
|
10403
|
+
} catch {
|
|
10404
|
+
return null;
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
function writeJsonSnapshot(key, buster, value) {
|
|
10408
|
+
if (typeof window === "undefined" || !buster) return;
|
|
10409
|
+
try {
|
|
10410
|
+
const raw = JSON.stringify({ buster, savedAt: Date.now(), value });
|
|
10411
|
+
if (raw.length > MAX_SNAPSHOT_CHARS) return;
|
|
10412
|
+
window.localStorage.setItem(key, raw);
|
|
10413
|
+
} catch {
|
|
10414
|
+
}
|
|
10415
|
+
}
|
|
10416
|
+
function persistSuiteQueries(queryClient, options) {
|
|
10417
|
+
const maxAgeMs = options.maxAgeMs ?? SUITE_QUERY_CACHE_MAX_AGE_MS;
|
|
10418
|
+
restore(queryClient, options, maxAgeMs);
|
|
10419
|
+
let timer = 0;
|
|
10420
|
+
const unsubscribe = queryClient.getQueryCache().subscribe(() => {
|
|
10421
|
+
if (typeof window === "undefined") return;
|
|
10422
|
+
window.clearTimeout(timer);
|
|
10423
|
+
timer = window.setTimeout(() => save(queryClient, options), 400);
|
|
10424
|
+
});
|
|
10425
|
+
return () => {
|
|
10426
|
+
if (typeof window !== "undefined") window.clearTimeout(timer);
|
|
10427
|
+
unsubscribe();
|
|
10428
|
+
};
|
|
10429
|
+
}
|
|
10430
|
+
function restore(queryClient, options, maxAgeMs) {
|
|
10431
|
+
const buster = suiteCacheBuster(options.tokenKey, options.workspaceKey);
|
|
10432
|
+
const stored = readJsonSnapshot(
|
|
10433
|
+
options.storageKey,
|
|
10434
|
+
buster,
|
|
10435
|
+
maxAgeMs
|
|
10436
|
+
);
|
|
10437
|
+
if (!Array.isArray(stored)) return;
|
|
10438
|
+
for (const entry of stored) {
|
|
10439
|
+
if (!entry || !Array.isArray(entry.queryKey) || !allowed(entry.queryKey, options.allow)) {
|
|
10440
|
+
continue;
|
|
10441
|
+
}
|
|
10442
|
+
queryClient.setQueryData(entry.queryKey, entry.data);
|
|
10443
|
+
void queryClient.invalidateQueries({
|
|
10444
|
+
queryKey: entry.queryKey,
|
|
10445
|
+
refetchType: "none"
|
|
10446
|
+
});
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
function save(queryClient, options) {
|
|
10450
|
+
const buster = suiteCacheBuster(options.tokenKey, options.workspaceKey);
|
|
10451
|
+
if (!buster) return;
|
|
10452
|
+
const queries = queryClient.getQueryCache().getAll().filter(
|
|
10453
|
+
(query) => query.state.status === "success" && query.state.data !== void 0 && allowed(query.queryKey, options.allow)
|
|
10454
|
+
).map((query) => ({ queryKey: query.queryKey, data: query.state.data }));
|
|
10455
|
+
writeJsonSnapshot(options.storageKey, buster, queries);
|
|
10456
|
+
}
|
|
10457
|
+
function allowed(queryKey, allow) {
|
|
10458
|
+
return allow.some((rule) => {
|
|
10459
|
+
if (typeof rule === "string") return queryKey[0] === rule;
|
|
10460
|
+
return rule.every((part, index) => queryKey[index] === part);
|
|
10461
|
+
});
|
|
10462
|
+
}
|
|
10463
|
+
function readCookie(name) {
|
|
10464
|
+
if (typeof document === "undefined") return null;
|
|
10465
|
+
const prefix = `${name}=`;
|
|
10466
|
+
const entry = document.cookie.split("; ").find((item) => item.startsWith(prefix));
|
|
10467
|
+
if (!entry) return null;
|
|
10468
|
+
try {
|
|
10469
|
+
return decodeURIComponent(entry.slice(prefix.length)) || null;
|
|
10470
|
+
} catch {
|
|
10471
|
+
return entry.slice(prefix.length) || null;
|
|
10472
|
+
}
|
|
10473
|
+
}
|
|
10474
|
+
function subjectFromAccessToken(token) {
|
|
10475
|
+
if (!token) return "";
|
|
10476
|
+
const segment = token.split(".")[1];
|
|
10477
|
+
if (!segment) return "";
|
|
10478
|
+
try {
|
|
10479
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
10480
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
10481
|
+
const json = JSON.parse(atob(padded));
|
|
10482
|
+
return typeof json.sub === "string" ? json.sub : "";
|
|
10483
|
+
} catch {
|
|
10484
|
+
return "";
|
|
10485
|
+
}
|
|
10486
|
+
}
|
|
10487
|
+
var SUITE_APP_QUERY_CACHES = {
|
|
10488
|
+
kraken: {
|
|
10489
|
+
storageKey: "kraken-query-cache:v1",
|
|
10490
|
+
tokenKey: "kraken_access_token",
|
|
10491
|
+
workspaceKey: "kraken_active_workspace_id",
|
|
10492
|
+
allow: ["kraken-sidebar"]
|
|
10493
|
+
},
|
|
10494
|
+
shellstack: {
|
|
10495
|
+
storageKey: "shellstack-query-cache:v1",
|
|
10496
|
+
tokenKey: "portal_access_token",
|
|
10497
|
+
workspaceKey: "portal_active_workspace_id",
|
|
10498
|
+
allow: ["calendar-events", "org-workspaces", "organisation", "org-team", "directory"]
|
|
10499
|
+
},
|
|
10500
|
+
tides: {
|
|
10501
|
+
storageKey: "tides-query-cache:v1",
|
|
10502
|
+
tokenKey: "tides_access_token",
|
|
10503
|
+
workspaceKey: "tides_active_workspace_id",
|
|
10504
|
+
allow: [
|
|
10505
|
+
["tides", "projects"],
|
|
10506
|
+
["tides", "clients"],
|
|
10507
|
+
["tides", "workspace-members"],
|
|
10508
|
+
["tides", "workspace-tags"],
|
|
10509
|
+
["tides", "project-statuses"]
|
|
10510
|
+
]
|
|
10511
|
+
},
|
|
10512
|
+
turtletime: {
|
|
10513
|
+
storageKey: "tt-query-cache:v1",
|
|
10514
|
+
tokenKey: "tt_access_token",
|
|
10515
|
+
workspaceKey: "tt_active_workspace_id",
|
|
10516
|
+
allow: [
|
|
10517
|
+
["tt", "tracker-catalog"],
|
|
10518
|
+
["tt", "projects"],
|
|
10519
|
+
["tt", "clients"],
|
|
10520
|
+
["tt", "categories"]
|
|
10521
|
+
]
|
|
10522
|
+
},
|
|
10523
|
+
shelly: {
|
|
10524
|
+
storageKey: "shelly-query-cache:v1",
|
|
10525
|
+
tokenKey: "shelly_access_token",
|
|
10526
|
+
workspaceKey: "shelly_active_workspace_id",
|
|
10527
|
+
allow: [
|
|
10528
|
+
["shelly", "skills"],
|
|
10529
|
+
["shelly", "crews"],
|
|
10530
|
+
["shelly", "mcp-servers"],
|
|
10531
|
+
["shelly", "models"]
|
|
10532
|
+
]
|
|
10533
|
+
}
|
|
10534
|
+
};
|
|
10535
|
+
|
|
10275
10536
|
// src/sync/create-cross-app-sync.ts
|
|
10276
10537
|
function createCrossAppSync(config) {
|
|
10277
10538
|
const pollMs = config.pollMs ?? 5e3;
|
|
@@ -10283,40 +10544,49 @@ function createCrossAppSync(config) {
|
|
|
10283
10544
|
const subscribers = [];
|
|
10284
10545
|
let pollInterval = null;
|
|
10285
10546
|
let pollListeners = 0;
|
|
10547
|
+
let inFlight = false;
|
|
10548
|
+
let primed = false;
|
|
10549
|
+
let scope;
|
|
10550
|
+
let lastRequestedScope;
|
|
10551
|
+
let lastRequestedAt;
|
|
10286
10552
|
async function fetchCheckpoint() {
|
|
10553
|
+
if (inFlight || document.visibilityState !== "visible") return null;
|
|
10554
|
+
const requestScope = config.getScope?.();
|
|
10555
|
+
if (requestScope === lastRequestedScope && lastRequestedAt !== void 0 && Date.now() - lastRequestedAt < pollMs)
|
|
10556
|
+
return null;
|
|
10557
|
+
inFlight = true;
|
|
10558
|
+
lastRequestedScope = requestScope;
|
|
10559
|
+
lastRequestedAt = Date.now();
|
|
10287
10560
|
try {
|
|
10288
10561
|
const res = await config.api.get(config.checkpointPath);
|
|
10289
10562
|
if (!res.ok) return null;
|
|
10290
|
-
|
|
10563
|
+
const data = await res.json();
|
|
10564
|
+
if (requestScope !== config.getScope?.()) return null;
|
|
10565
|
+
if (scope !== requestScope) {
|
|
10566
|
+
primed = false;
|
|
10567
|
+
scope = requestScope;
|
|
10568
|
+
}
|
|
10569
|
+
return data;
|
|
10291
10570
|
} catch {
|
|
10292
10571
|
return null;
|
|
10293
|
-
}
|
|
10294
|
-
|
|
10295
|
-
function notify(topic) {
|
|
10296
|
-
for (const sub of subscribers) {
|
|
10297
|
-
if (!sub.topic || sub.topic === topic) {
|
|
10298
|
-
if (lastKnown[topic] > sub.lastSync) {
|
|
10299
|
-
sub.lastSync = lastKnown[topic];
|
|
10300
|
-
sub.callback();
|
|
10301
|
-
}
|
|
10302
|
-
}
|
|
10572
|
+
} finally {
|
|
10573
|
+
inFlight = false;
|
|
10303
10574
|
}
|
|
10304
10575
|
}
|
|
10305
10576
|
function checkAndNotify(data) {
|
|
10306
|
-
const
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
notify("integration");
|
|
10577
|
+
const next = {
|
|
10578
|
+
tasks: data.tasks_synced_at || 0,
|
|
10579
|
+
time: data.time_synced_at || 0,
|
|
10580
|
+
integration: data.integration_synced_at || 0
|
|
10581
|
+
};
|
|
10582
|
+
const changed = ["tasks", "time", "integration"].filter(
|
|
10583
|
+
(topic) => primed && next[topic] > lastKnown[topic]
|
|
10584
|
+
);
|
|
10585
|
+
Object.assign(lastKnown, next);
|
|
10586
|
+
primed = true;
|
|
10587
|
+
for (const sub of subscribers) {
|
|
10588
|
+
if (changed.some((topic) => !sub.topic || sub.topic === topic))
|
|
10589
|
+
sub.callback();
|
|
10320
10590
|
}
|
|
10321
10591
|
}
|
|
10322
10592
|
function startPolling() {
|
|
@@ -10343,8 +10613,7 @@ function createCrossAppSync(config) {
|
|
|
10343
10613
|
};
|
|
10344
10614
|
const sub = {
|
|
10345
10615
|
topic,
|
|
10346
|
-
callback: onSync
|
|
10347
|
-
lastSync: lastKnown[topic || "tasks"]
|
|
10616
|
+
callback: onSync
|
|
10348
10617
|
};
|
|
10349
10618
|
subscribers.push(sub);
|
|
10350
10619
|
pollListeners += 1;
|
|
@@ -10415,6 +10684,80 @@ function createLiveRefresh(sync) {
|
|
|
10415
10684
|
return { subscribeLiveRefresh, hasPendingCrossAppSync };
|
|
10416
10685
|
}
|
|
10417
10686
|
|
|
10687
|
+
// src/sync/use-suite-revisions.ts
|
|
10688
|
+
var import_react = require("react");
|
|
10689
|
+
function nextRevisionTick(seen, ready, data) {
|
|
10690
|
+
if (!data) return { seen, ready, changed: [] };
|
|
10691
|
+
const next = { ...seen };
|
|
10692
|
+
if (!ready) {
|
|
10693
|
+
for (const [topic, value] of Object.entries(data)) {
|
|
10694
|
+
if (typeof value === "number") next[topic] = value;
|
|
10695
|
+
}
|
|
10696
|
+
return { seen: next, ready: true, changed: [] };
|
|
10697
|
+
}
|
|
10698
|
+
const changed = [];
|
|
10699
|
+
for (const [topic, value] of Object.entries(data)) {
|
|
10700
|
+
if (typeof value !== "number" || next[topic] === value) continue;
|
|
10701
|
+
next[topic] = value;
|
|
10702
|
+
changed.push(topic);
|
|
10703
|
+
}
|
|
10704
|
+
return { seen: next, ready: true, changed };
|
|
10705
|
+
}
|
|
10706
|
+
function useSuiteRevisions(options) {
|
|
10707
|
+
const loadRef = (0, import_react.useRef)(options.load);
|
|
10708
|
+
const onTopicRef = (0, import_react.useRef)(options.onTopic);
|
|
10709
|
+
loadRef.current = options.load;
|
|
10710
|
+
onTopicRef.current = options.onTopic;
|
|
10711
|
+
const pollMs = options.pollMs ?? 15e3;
|
|
10712
|
+
const scope = options.scope ?? "";
|
|
10713
|
+
const seenScope = (0, import_react.useRef)(null);
|
|
10714
|
+
(0, import_react.useEffect)(() => {
|
|
10715
|
+
if (!options.enabled || typeof window === "undefined") return;
|
|
10716
|
+
const switching = seenScope.current !== null && seenScope.current !== scope;
|
|
10717
|
+
seenScope.current = scope;
|
|
10718
|
+
let seen = {};
|
|
10719
|
+
let ready = switching;
|
|
10720
|
+
let timer;
|
|
10721
|
+
let inFlight = false;
|
|
10722
|
+
let disposed = false;
|
|
10723
|
+
const tick = async () => {
|
|
10724
|
+
if (inFlight || disposed) return;
|
|
10725
|
+
inFlight = true;
|
|
10726
|
+
try {
|
|
10727
|
+
const data = await loadRef.current();
|
|
10728
|
+
if (disposed) return;
|
|
10729
|
+
const step = nextRevisionTick(seen, ready, data);
|
|
10730
|
+
seen = step.seen;
|
|
10731
|
+
ready = step.ready;
|
|
10732
|
+
for (const topic of step.changed) onTopicRef.current(topic);
|
|
10733
|
+
} catch {
|
|
10734
|
+
} finally {
|
|
10735
|
+
inFlight = false;
|
|
10736
|
+
}
|
|
10737
|
+
};
|
|
10738
|
+
const stop = () => {
|
|
10739
|
+
if (timer !== void 0) window.clearInterval(timer);
|
|
10740
|
+
timer = void 0;
|
|
10741
|
+
};
|
|
10742
|
+
const start = () => {
|
|
10743
|
+
if (timer !== void 0) return;
|
|
10744
|
+
void tick();
|
|
10745
|
+
timer = window.setInterval(() => void tick(), pollMs);
|
|
10746
|
+
};
|
|
10747
|
+
const onVisibility = () => {
|
|
10748
|
+
if (document.visibilityState === "visible") start();
|
|
10749
|
+
else stop();
|
|
10750
|
+
};
|
|
10751
|
+
if (document.visibilityState === "visible") start();
|
|
10752
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
10753
|
+
return () => {
|
|
10754
|
+
disposed = true;
|
|
10755
|
+
stop();
|
|
10756
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
10757
|
+
};
|
|
10758
|
+
}, [options.enabled, pollMs, scope]);
|
|
10759
|
+
}
|
|
10760
|
+
|
|
10418
10761
|
// src/notifications/create-notifications-client.ts
|
|
10419
10762
|
function createNotificationsClient(config) {
|
|
10420
10763
|
const { api, parseApiError } = config;
|
|
@@ -10560,6 +10903,47 @@ function createWebPushClient(config) {
|
|
|
10560
10903
|
return { supported, getState, enable, disable };
|
|
10561
10904
|
}
|
|
10562
10905
|
|
|
10906
|
+
// src/notifications/use-email-notification-preference.ts
|
|
10907
|
+
var import_react2 = require("react");
|
|
10908
|
+
var EMAIL_PREFERENCE_PATH = "/api/notifications/email/";
|
|
10909
|
+
function useEmailNotificationPreference(api, parseApiError) {
|
|
10910
|
+
const [enabled, setEnabled] = (0, import_react2.useState)(false);
|
|
10911
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
10912
|
+
const [error, setError] = (0, import_react2.useState)("");
|
|
10913
|
+
(0, import_react2.useEffect)(() => {
|
|
10914
|
+
let cancelled = false;
|
|
10915
|
+
api.get(EMAIL_PREFERENCE_PATH).then(async (res) => {
|
|
10916
|
+
if (!res.ok || cancelled) return;
|
|
10917
|
+
const data = await res.json();
|
|
10918
|
+
if (!cancelled) setEnabled(Boolean(data.enabled));
|
|
10919
|
+
}).catch(() => {
|
|
10920
|
+
});
|
|
10921
|
+
return () => {
|
|
10922
|
+
cancelled = true;
|
|
10923
|
+
};
|
|
10924
|
+
}, [api]);
|
|
10925
|
+
const onChange = (0, import_react2.useCallback)(
|
|
10926
|
+
(next) => {
|
|
10927
|
+
setBusy(true);
|
|
10928
|
+
setError("");
|
|
10929
|
+
setEnabled(next);
|
|
10930
|
+
api.patch(EMAIL_PREFERENCE_PATH, { enabled: next }).then(async (res) => {
|
|
10931
|
+
if (!res.ok) {
|
|
10932
|
+
setEnabled(!next);
|
|
10933
|
+
setError(await parseApiError(res));
|
|
10934
|
+
}
|
|
10935
|
+
}).catch((err) => {
|
|
10936
|
+
setEnabled(!next);
|
|
10937
|
+
setError(
|
|
10938
|
+
err instanceof Error ? err.message : "Could not save email notifications."
|
|
10939
|
+
);
|
|
10940
|
+
}).finally(() => setBusy(false));
|
|
10941
|
+
},
|
|
10942
|
+
[api, parseApiError]
|
|
10943
|
+
);
|
|
10944
|
+
return { enabled, busy, error, onChange };
|
|
10945
|
+
}
|
|
10946
|
+
|
|
10563
10947
|
// src/ai/suite-ai-job.ts
|
|
10564
10948
|
function activeSuiteAiJobs(jobs) {
|
|
10565
10949
|
return jobs.filter(
|
|
@@ -10641,12 +11025,12 @@ function createJsonLd(config) {
|
|
|
10641
11025
|
}
|
|
10642
11026
|
|
|
10643
11027
|
// src/hooks/use-is-night.ts
|
|
10644
|
-
var
|
|
11028
|
+
var import_react3 = require("react");
|
|
10645
11029
|
var NIGHT_START_HOUR = 18;
|
|
10646
11030
|
var NIGHT_END_HOUR = 6;
|
|
10647
11031
|
function useIsNight() {
|
|
10648
|
-
const [isNight, setIsNight] = (0,
|
|
10649
|
-
(0,
|
|
11032
|
+
const [isNight, setIsNight] = (0, import_react3.useState)(false);
|
|
11033
|
+
(0, import_react3.useEffect)(() => {
|
|
10650
11034
|
const check = () => {
|
|
10651
11035
|
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
10652
11036
|
return hour >= NIGHT_START_HOUR || hour < NIGHT_END_HOUR;
|
|
@@ -10671,6 +11055,8 @@ function useIsNight() {
|
|
|
10671
11055
|
ERROR_RETRYABLE,
|
|
10672
11056
|
FREE_PLAN_SEAT_HELP,
|
|
10673
11057
|
ORGANISATION_SEAT_HELP,
|
|
11058
|
+
SUITE_APP_QUERY_CACHES,
|
|
11059
|
+
SUITE_QUERY_CACHE_MAX_AGE_MS,
|
|
10674
11060
|
TIDES_ACCESS_HELP,
|
|
10675
11061
|
TURTLETIME_ACCESS_HELP,
|
|
10676
11062
|
WEBHOOK_ACTIVITY_ACTIONS,
|
|
@@ -10698,9 +11084,12 @@ function useIsNight() {
|
|
|
10698
11084
|
formatAppError,
|
|
10699
11085
|
hasAppAccess,
|
|
10700
11086
|
isErrorCode,
|
|
11087
|
+
nextRevisionTick,
|
|
10701
11088
|
normalizeError,
|
|
10702
11089
|
normalizeThrown,
|
|
11090
|
+
persistSuiteQueries,
|
|
10703
11091
|
planSeatSummary,
|
|
11092
|
+
readJsonSnapshot,
|
|
10704
11093
|
resolveSubscriptionTier,
|
|
10705
11094
|
seatTypeDescription,
|
|
10706
11095
|
seatTypeFromFlags,
|
|
@@ -10708,10 +11097,14 @@ function useIsNight() {
|
|
|
10708
11097
|
selectedToWebhookActions,
|
|
10709
11098
|
sortedSuiteAiEvents,
|
|
10710
11099
|
suiteAiEventSummary,
|
|
11100
|
+
suiteCacheBuster,
|
|
10711
11101
|
tierAllowsTidesOnlySeats,
|
|
10712
11102
|
tierAllowsTurtletime,
|
|
10713
11103
|
tierLabel,
|
|
11104
|
+
useEmailNotificationPreference,
|
|
10714
11105
|
useIsNight,
|
|
10715
|
-
|
|
11106
|
+
useSuiteRevisions,
|
|
11107
|
+
webhookActionsToSelected,
|
|
11108
|
+
writeJsonSnapshot
|
|
10716
11109
|
});
|
|
10717
11110
|
//# sourceMappingURL=index.js.map
|