@hexclave/next 1.0.76 → 1.0.78
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/{admin-app-impl-CsH-gVGz.d.ts → admin-app-impl-DAXv7icw.d.ts} +7 -5
- package/dist/admin-app-impl-DAXv7icw.d.ts.map +1 -0
- package/dist/clickmap/clickmap-core.d.ts +1 -1
- package/dist/clickmap/index.d.ts +1 -1
- package/dist/{client-app-BCT5p_70.d.ts → client-app-g3gQ078Q.d.ts} +5 -2
- package/dist/client-app-g3gQ078Q.d.ts.map +1 -0
- package/dist/common-BK_sydxF.d.ts.map +1 -1
- package/dist/components/api-key-dialogs.d.ts +1 -1
- package/dist/components/elements/sidebar-layout.d.ts.map +1 -1
- package/dist/components/elements/sidebar-layout.js +12 -2
- package/dist/components/elements/sidebar-layout.js.map +1 -1
- package/dist/components/selected-team-switcher.d.ts +1 -1
- package/dist/components/team-icon.d.ts +1 -1
- package/dist/components/team-switcher.d.ts +1 -1
- package/dist/components-page/account-settings/payments/payments-page.d.ts +1 -1
- package/dist/components-page/account-settings/teams/leave-team-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-api-keys-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-display-name-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-member-invitation-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-member-list-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-page.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-profile-image-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-profile-user-section.d.ts +1 -1
- package/dist/components-page/hexclave-handler-client.d.ts +1 -1
- package/dist/components-page/hexclave-handler.d.ts +1 -1
- package/dist/components-page/team-invitation-cache.d.ts +5 -0
- package/dist/components-page/team-invitation-cache.d.ts.map +1 -0
- package/dist/components-page/team-invitation-cache.js +12 -0
- package/dist/components-page/team-invitation-cache.js.map +1 -0
- package/dist/components-page/team-invitation-cache.test.d.ts +1 -0
- package/dist/components-page/team-invitation-cache.test.js +23 -0
- package/dist/components-page/team-invitation-cache.test.js.map +1 -0
- package/dist/components-page/team-invitation.js +8 -7
- package/dist/components-page/team-invitation.js.map +1 -1
- package/dist/dev-tool/dev-tool-core.d.ts +1 -1
- package/dist/dev-tool/index.d.ts +1 -1
- package/dist/esm/components/api-key-dialogs.d.ts +1 -1
- package/dist/esm/components/elements/sidebar-layout.d.ts.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +12 -2
- package/dist/esm/components/elements/sidebar-layout.js.map +1 -1
- package/dist/esm/components-page/team-invitation-cache.d.ts +5 -0
- package/dist/esm/components-page/team-invitation-cache.d.ts.map +1 -0
- package/dist/esm/components-page/team-invitation-cache.js +11 -0
- package/dist/esm/components-page/team-invitation-cache.js.map +1 -0
- package/dist/esm/components-page/team-invitation-cache.test.d.ts +1 -0
- package/dist/esm/components-page/team-invitation-cache.test.js +24 -0
- package/dist/esm/components-page/team-invitation-cache.test.js.map +1 -0
- package/dist/esm/components-page/team-invitation.d.ts.map +1 -1
- package/dist/esm/components-page/team-invitation.js +8 -7
- package/dist/esm/components-page/team-invitation.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.d.ts +2 -2
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +2 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js +30 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +3 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +94 -21
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js +83 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js +120 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/common.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js +4 -3
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/index.js +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -1
- package/dist/esm/lib/hexclave-app/session-replays/index.d.ts +2 -1
- package/dist/esm/lib/hexclave-app/session-replays/index.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/teams/index.d.ts +3 -0
- package/dist/esm/lib/hexclave-app/teams/index.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/teams/index.js.map +1 -1
- package/dist/esm/utils/monkey-patch.d.ts +7 -0
- package/dist/esm/utils/monkey-patch.d.ts.map +1 -0
- package/dist/esm/utils/monkey-patch.js +20 -0
- package/dist/esm/utils/monkey-patch.js.map +1 -0
- package/dist/esm/utils/react.d.ts +17 -0
- package/dist/esm/utils/react.d.ts.map +1 -0
- package/dist/esm/utils/react.js +60 -0
- package/dist/esm/utils/react.js.map +1 -0
- package/dist/generated/quetzal-translations.d.ts +1 -1
- package/dist/{hexclave-provider-BA97jMlx.d.ts → hexclave-provider-Bh_Bp-8K.d.ts} +3 -3
- package/dist/{hexclave-provider-BA97jMlx.d.ts.map → hexclave-provider-Bh_Bp-8K.d.ts.map} +1 -1
- package/dist/{hooks-CUe5bc3s.d.ts → hooks-DhDTiWnb.d.ts} +2 -2
- package/dist/{hooks-CUe5bc3s.d.ts.map → hooks-DhDTiWnb.d.ts.map} +1 -1
- package/dist/{index-hYjsGKNc.d.ts → index-Cf1wOohp.d.ts} +3 -2
- package/dist/index-Cf1wOohp.d.ts.map +1 -0
- package/dist/{index-CvB7XacB.d.ts → index-DXWuA5Zu.d.ts} +2 -2
- package/dist/{index-CvB7XacB.d.ts.map → index-DXWuA5Zu.d.ts.map} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +2 -0
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js +29 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +2 -2
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +93 -19
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js +82 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js +119 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/common.js +4 -3
- package/dist/lib/hexclave-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/index.js +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -1
- package/dist/lib/hexclave-app/apps/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -2
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/server-app.d.ts +1 -1
- package/dist/lib/hexclave-app/index.d.ts +1 -1
- package/dist/lib/hexclave-app/projects/index.d.ts +1 -1
- package/dist/lib/hexclave-app/session-replays/index.d.ts +1 -1
- package/dist/lib/hexclave-app/teams/index.d.ts +1 -1
- package/dist/lib/hexclave-app/teams/index.js.map +1 -1
- package/dist/lib/hexclave-app/users/index.d.ts +1 -1
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/providers/hexclave-context.d.ts +1 -1
- package/dist/providers/hexclave-provider-client.d.ts +1 -1
- package/dist/providers/hexclave-provider.d.ts +1 -1
- package/dist/providers/translation-provider.d.ts +1 -1
- package/dist/pushed-config-error-overlay/index.d.ts +1 -1
- package/dist/{quetzal-translations-CK45_4Dm.d.ts → quetzal-translations-XeH3zQ9l.d.ts} +3 -3
- package/dist/{quetzal-translations-CK45_4Dm.d.ts.map → quetzal-translations-XeH3zQ9l.d.ts.map} +1 -1
- package/dist/team-invitation-B5Ol7vdd.d.ts.map +1 -1
- package/dist/{translation-provider-DQ3BJepJ.d.ts → translation-provider-WOaZXqAz.d.ts} +2 -2
- package/dist/{translation-provider-DQ3BJepJ.d.ts.map → translation-provider-WOaZXqAz.d.ts.map} +1 -1
- package/dist/utils/monkey-patch.d.ts +7 -0
- package/dist/utils/monkey-patch.d.ts.map +1 -0
- package/dist/utils/monkey-patch.js +23 -0
- package/dist/utils/monkey-patch.js.map +1 -0
- package/dist/utils/react.d.ts +17 -0
- package/dist/utils/react.d.ts.map +1 -0
- package/dist/utils/react.js +63 -0
- package/dist/utils/react.js.map +1 -0
- package/package.json +4 -4
- package/src/components/elements/sidebar-layout.tsx +16 -1
- package/src/components-page/team-invitation-cache.test.ts +25 -0
- package/src/components-page/team-invitation-cache.ts +13 -0
- package/src/components-page/team-invitation.tsx +16 -7
- package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +2 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.ts +38 -1
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +2 -3
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.ts +96 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +134 -24
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.ts +146 -0
- package/src/lib/hexclave-app/apps/implementations/common.ts +2 -1
- package/src/lib/hexclave-app/session-replays/index.ts +2 -0
- package/src/lib/hexclave-app/teams/index.ts +3 -0
- package/src/utils/monkey-patch.tsx +31 -0
- package/src/utils/react.tsx +93 -0
- package/dist/admin-app-impl-CsH-gVGz.d.ts.map +0 -1
- package/dist/client-app-BCT5p_70.d.ts.map +0 -1
- package/dist/index-hYjsGKNc.d.ts.map +0 -1
package/dist/{quetzal-translations-CK45_4Dm.d.ts.map → quetzal-translations-XeH3zQ9l.d.ts.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quetzal-translations-
|
|
1
|
+
{"version":3,"file":"quetzal-translations-XeH3zQ9l.d.ts","names":[],"sources":["../src/generated/quetzal-translations.ts"],"mappings":";cAUa,WAAA,EAAW,GAAA;AAAA,cAqTX,cAAA,EAAc,GAAA,mIAAA,GAAA,66qBAAA,GAAA,usnBAAA,GAAA,iyrBAAA,GAAA,g+qBAAA,GAAA,8srBAAA,GAAA,2trBAAA,GAAA,41pBAAA,GAAA,m3eAAA,GAAA,oreAAA,GAAA,swpBAAA,GAAA,+0qBAAA,GAAA,8wbAAA,GAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team-invitation-B5Ol7vdd.d.ts","names":[],"sources":["../src/components-page/team-invitation.tsx"],"mappings":";
|
|
1
|
+
{"version":3,"file":"team-invitation-B5Ol7vdd.d.ts","names":[],"sources":["../src/components-page/team-invitation.tsx"],"mappings":";iBA2EgB,cAAA;EAAiB,QAAA;EAAgB;AAAA;EAAkB,QAAA;EAAoB,YAAA,EAAc,MAAA;AAAA,gCAAwB,GAAA,CAAA,OAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as quetzalLocales } from "./quetzal-translations-
|
|
1
|
+
import { n as quetzalLocales } from "./quetzal-translations-XeH3zQ9l.js";
|
|
2
2
|
|
|
3
3
|
//#region src/providers/translation-provider.d.ts
|
|
4
4
|
declare function TranslationProvider({
|
|
@@ -12,4 +12,4 @@ declare function TranslationProvider({
|
|
|
12
12
|
}): import("react/jsx-runtime").JSX.Element;
|
|
13
13
|
//#endregion
|
|
14
14
|
export { TranslationProvider as t };
|
|
15
|
-
//# sourceMappingURL=translation-provider-
|
|
15
|
+
//# sourceMappingURL=translation-provider-WOaZXqAz.d.ts.map
|
package/dist/{translation-provider-DQ3BJepJ.d.ts.map → translation-provider-WOaZXqAz.d.ts.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-provider-
|
|
1
|
+
{"version":3,"file":"translation-provider-WOaZXqAz.d.ts","names":[],"sources":["../src/providers/translation-provider.tsx"],"mappings":";;;iBAQgB,mBAAA;EAAsB,IAAA;EAAM,oBAAA;EAAsB;AAAA;EAChE,IAAA,EAAM,UAAA,QAAkB,cAAA,CAAe,GAAA;EACvC,oBAAA,GAAuB,MAAA;EACvB,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA,gCACjB,GAAA,CAAA,OAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/utils/monkey-patch.d.ts
|
|
2
|
+
declare const NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL = "__stack-no-suspense-boundary-error__";
|
|
3
|
+
declare function isNoSuspenseBoundaryError(value: unknown): boolean;
|
|
4
|
+
declare function ensureMonkeyPatch(): void;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL, ensureMonkeyPatch, isNoSuspenseBoundaryError };
|
|
7
|
+
//# sourceMappingURL=monkey-patch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monkey-patch.d.ts","names":[],"sources":["../../src/utils/monkey-patch.tsx"],"mappings":";cAMa,mCAAA;AAAA,iBAEG,yBAAA,CAA0B,KAAc;AAAA,iBASxC,iBAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _hexclave_shared_dist_utils_globals = require("@hexclave/shared/dist/utils/globals");
|
|
3
|
+
//#region src/utils/monkey-patch.tsx
|
|
4
|
+
const NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL = "__stack-no-suspense-boundary-error__";
|
|
5
|
+
function isNoSuspenseBoundaryError(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && "__noSuspenseBoundarySentinel" in value && value.__noSuspenseBoundarySentinel === "__stack-no-suspense-boundary-error__";
|
|
7
|
+
}
|
|
8
|
+
function ensureMonkeyPatch() {
|
|
9
|
+
(0, _hexclave_shared_dist_utils_globals.createGlobal)("__console-error-monkey-patch__", () => {
|
|
10
|
+
const originalConsoleError = console.error;
|
|
11
|
+
console.error = function(...args) {
|
|
12
|
+
if (args.length === 1 && isNoSuspenseBoundaryError(args[0])) return;
|
|
13
|
+
return originalConsoleError.apply(this, args);
|
|
14
|
+
};
|
|
15
|
+
return true;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
exports.NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL = NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL;
|
|
20
|
+
exports.ensureMonkeyPatch = ensureMonkeyPatch;
|
|
21
|
+
exports.isNoSuspenseBoundaryError = isNoSuspenseBoundaryError;
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=monkey-patch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monkey-patch.js","names":[],"sources":["../../src/utils/monkey-patch.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { createGlobal } from \"@hexclave/shared/dist/utils/globals\";\n\nexport const NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL = \"__stack-no-suspense-boundary-error__\";\n\nexport function isNoSuspenseBoundaryError(value: unknown): boolean {\n return (\n typeof value === \"object\"\n && value !== null\n && \"__noSuspenseBoundarySentinel\" in value\n && value.__noSuspenseBoundarySentinel === NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL\n );\n}\n\nexport function ensureMonkeyPatch() {\n createGlobal(\"__console-error-monkey-patch__\", () => {\n const originalConsoleError = console.error;\n console.error = function (...args: unknown[]) {\n // React's default error handlers log every server error, including the one we intentionally throw to skip SSR.\n // Frameworks that use those defaults (including vanilla React) therefore need this narrow sentinel-based filter.\n if (args.length === 1 && isNoSuspenseBoundaryError(args[0])) {\n return;\n }\n return originalConsoleError.apply(this, args);\n };\n return true;\n });\n}\n"],"mappings":";;;AAMA,MAAa,sCAAsC;AAEnD,SAAgB,0BAA0B,OAAyB;CACjE,OACE,OAAO,UAAU,YACd,UAAU,QACV,kCAAkC,SAClC,MAAM,iCAAA;AAEb;AAEA,SAAgB,oBAAoB;CAClC,CAAA,GAAA,oCAAA,aAAA,CAAa,wCAAwC;EACnD,MAAM,uBAAuB,QAAQ;EACrC,QAAQ,QAAQ,SAAU,GAAG,MAAiB;GAG5C,IAAI,KAAK,WAAW,KAAK,0BAA0B,KAAK,EAAE,GACxD;GAEF,OAAO,qBAAqB,MAAM,MAAM,IAAI;EAC9C;EACA,OAAO;CACT,CAAC;AACH"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/utils/react.d.ts
|
|
2
|
+
declare function shouldRethrowRenderingError(error: unknown): boolean;
|
|
3
|
+
declare class NoSuspenseBoundaryError extends Error {
|
|
4
|
+
digest: string;
|
|
5
|
+
reason: string;
|
|
6
|
+
__noSuspenseBoundarySentinel: string;
|
|
7
|
+
constructor(options: {
|
|
8
|
+
caller?: string;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Use this in a component or hook to disable SSR. It must be wrapped in a Suspense boundary.
|
|
13
|
+
*/
|
|
14
|
+
declare function suspendIfSsr(caller?: string): void;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { NoSuspenseBoundaryError, shouldRethrowRenderingError, suspendIfSsr };
|
|
17
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","names":[],"sources":["../../src/utils/react.tsx"],"mappings":";iBAYgB,2BAAA,CAA4B,KAAc;AAAA,cAI7C,uBAAA,SAAgC,KAAK;EAChD,MAAA;EACA,MAAA;EACA,4BAAA;cAEY,OAAA;IAAW,MAAA;EAAA;AAAA;;;;iBA8DT,YAAA,CAAa,MAAe"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
let _hexclave_shared_dist_utils_strings = require("@hexclave/shared/dist/utils/strings");
|
|
4
|
+
let next_navigation = require("next/navigation");
|
|
5
|
+
let _hexclave_shared_dist_utils_env = require("@hexclave/shared/dist/utils/env");
|
|
6
|
+
let __monkey_patch_js = require("./monkey-patch.js");
|
|
7
|
+
//#region src/utils/react.tsx
|
|
8
|
+
function shouldRethrowRenderingError(error) {
|
|
9
|
+
return !!error && typeof error === "object" && "digest" in error && error.digest === "BAILOUT_TO_CLIENT_SIDE_RENDERING";
|
|
10
|
+
}
|
|
11
|
+
var NoSuspenseBoundaryError = class extends Error {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
(0, __monkey_patch_js.ensureMonkeyPatch)();
|
|
14
|
+
super(_hexclave_shared_dist_utils_strings.deindent`
|
|
15
|
+
Suspense boundary not found! Read the error message below carefully (or paste it into your AI agent).
|
|
16
|
+
|
|
17
|
+
${options.caller ?? "This code path"} attempted to display a loading indicator, but didn't find a Suspense boundary above it. Please read the error message below carefully.
|
|
18
|
+
|
|
19
|
+
There are several potential causes:
|
|
20
|
+
|
|
21
|
+
1. [Next.js] You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.
|
|
22
|
+
|
|
23
|
+
2. [React] You are missing a <Suspense> boundary in your component. Fix it by wrapping your component (or the entire app) in a <Suspense> component.
|
|
24
|
+
|
|
25
|
+
3. [Next.js] The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:
|
|
26
|
+
|
|
27
|
+
- app
|
|
28
|
+
- - layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${options.caller ?? "this code path"}
|
|
29
|
+
- - loading.tsx // required for suspense
|
|
30
|
+
- - (main)
|
|
31
|
+
- - - layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${options.caller ?? "this code path"}
|
|
32
|
+
- - - route.tsx // your actual main page
|
|
33
|
+
- - - the rest of your app
|
|
34
|
+
|
|
35
|
+
For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups
|
|
36
|
+
|
|
37
|
+
4. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.
|
|
38
|
+
|
|
39
|
+
5. Your version of Hexclave is too old. Upgrade to the latest version to see if that fixes the issue.
|
|
40
|
+
|
|
41
|
+
See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
|
|
42
|
+
|
|
43
|
+
More information on SSR and Suspense boundaries: https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content
|
|
44
|
+
`);
|
|
45
|
+
this.__noSuspenseBoundarySentinel = __monkey_patch_js.NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL;
|
|
46
|
+
this.name = "NoSuspenseBoundaryError";
|
|
47
|
+
this.reason = options.caller ?? "suspendIfSsr()";
|
|
48
|
+
this.digest = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Use this in a component or hook to disable SSR. It must be wrapped in a Suspense boundary.
|
|
53
|
+
*/
|
|
54
|
+
function suspendIfSsr(caller) {
|
|
55
|
+
(0, next_navigation.useSearchParams)();
|
|
56
|
+
if (!(0, _hexclave_shared_dist_utils_env.isBrowserLike)()) throw new NoSuspenseBoundaryError({ caller });
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
exports.NoSuspenseBoundaryError = NoSuspenseBoundaryError;
|
|
60
|
+
exports.shouldRethrowRenderingError = shouldRethrowRenderingError;
|
|
61
|
+
exports.suspendIfSsr = suspendIfSsr;
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","names":["NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL"],"sources":["../../src/utils/react.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { isBrowserLike } from \"@hexclave/shared/dist/utils/env\";\nimport { deindent } from \"@hexclave/shared/dist/utils/strings\";\nimport { useSearchParams } from \"next/navigation\"; // THIS_LINE_PLATFORM next\nimport { ensureMonkeyPatch, NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL } from \"./monkey-patch\";\n\nexport function shouldRethrowRenderingError(error: unknown): boolean {\n return !!error && typeof error === \"object\" && \"digest\" in error && error.digest === \"BAILOUT_TO_CLIENT_SIDE_RENDERING\";\n}\n\nexport class NoSuspenseBoundaryError extends Error {\n digest: string;\n reason: string;\n __noSuspenseBoundarySentinel = NO_SUSPENSE_BOUNDARY_ERROR_SENTINEL;\n\n constructor(options: { caller?: string }) {\n ensureMonkeyPatch();\n\n super(deindent`\n Suspense boundary not found! Read the error message below carefully (or paste it into your AI agent).\n\n ${options.caller ?? \"This code path\"} attempted to display a loading indicator, but didn't find a Suspense boundary above it. Please read the error message below carefully.\n \n There are several potential causes:\n \n 1. [Next.js] You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.\n\n 2. [React] You are missing a <Suspense> boundary in your component. Fix it by wrapping your component (or the entire app) in a <Suspense> component.\n\n 3. [Next.js] The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:\n\n - app\n - - layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${options.caller ?? \"this code path\"}\n - - loading.tsx // required for suspense\n - - (main)\n - - - layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${options.caller ?? \"this code path\"}\n - - - route.tsx // your actual main page\n - - - the rest of your app\n\n For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups\n \n 4. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.\n\n 5. Your version of Hexclave is too old. Upgrade to the latest version to see if that fixes the issue.\n\n See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n\n More information on SSR and Suspense boundaries: https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content\n `);\n\n this.name = \"NoSuspenseBoundaryError\";\n this.reason = options.caller ?? \"suspendIfSsr()\";\n\n // Next recognizes this digest as an intentional request-time CSR bailout and suppresses its normal error reporting.\n this.digest = \"BAILOUT_TO_CLIENT_SIDE_RENDERING\";\n }\n}\nimport.meta.vitest?.test(\"NoSuspenseBoundaryError\", ({ expect }) => {\n const defaultError = new NoSuspenseBoundaryError({});\n expect(defaultError.name).toBe(\"NoSuspenseBoundaryError\");\n expect(defaultError.reason).toBe(\"suspendIfSsr()\");\n expect(defaultError.digest).toBe(\"BAILOUT_TO_CLIENT_SIDE_RENDERING\");\n expect(defaultError.message).toContain(\"This code path attempted to display a loading indicator\");\n\n const customError = new NoSuspenseBoundaryError({ caller: \"CustomComponent\" });\n expect(customError.name).toBe(\"NoSuspenseBoundaryError\");\n expect(customError.reason).toBe(\"CustomComponent\");\n expect(customError.digest).toBe(\"BAILOUT_TO_CLIENT_SIDE_RENDERING\");\n expect(customError.message).toContain(\"CustomComponent attempted to display a loading indicator\");\n expect(customError.message).toContain(\"loading.tsx\");\n expect(customError.message).toContain(\"route groups\");\n expect(customError.message).toContain(\"https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\");\n});\n\n/**\n * Use this in a component or hook to disable SSR. It must be wrapped in a Suspense boundary.\n */\nexport function suspendIfSsr(caller?: string) {\n // Next 16.3's Instant Insights is a silly goose! It rejects our deliberate recoverable SSR error during\n // prerender validation, even though React uses the surrounding Suspense fallback correctly. Calling\n // useSearchParams first makes Next create its own recognized `prerender-client` dynamic hole, so prerendering\n // stops before our error while request-time SSR still reaches the error and retries this subtree on the client.\n useSearchParams();\n if (!isBrowserLike()) {\n throw new NoSuspenseBoundaryError({ caller });\n }\n}\n"],"mappings":";;;;;;;AAYA,SAAgB,4BAA4B,OAAyB;CACnE,OAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,YAAY,SAAS,MAAM,WAAW;AACvF;AAEA,IAAa,0BAAb,cAA6C,MAAM;CAKjD,YAAY,SAA8B;EACxC,CAAA,GAAA,kBAAA,kBAAA,CAAkB;EAElB,MAAM,oCAAA,QAAQ;;;QAGV,QAAQ,UAAU,iBAAiB;;;;;;;;;;;kHAWuE,QAAQ,UAAU,iBAAiB;;;6GAGxC,QAAQ,UAAU,iBAAiB;;;;;;;;;;;;;KAa3I;sCAnC4BA,kBAAAA;EAqC7B,KAAK,OAAO;EACZ,KAAK,SAAS,QAAQ,UAAU;EAGhC,KAAK,SAAS;CAChB;AACF;;;;AAqBA,SAAgB,aAAa,QAAiB;CAK5C,CAAA,GAAA,gBAAA,gBAAA,CAAgB;CAChB,IAAI,EAAA,GAAA,gCAAA,cAAA,CAAe,GACjB,MAAM,IAAI,wBAAwB,EAAE,OAAO,CAAC;AAEhD"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
|
3
3
|
"name": "@hexclave/next",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.78",
|
|
5
5
|
"repository": "https://github.com/hexclave/hexclave",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"tailwindcss-animate": "^1.0.7",
|
|
75
75
|
"rrweb": "^1.1.3",
|
|
76
76
|
"yup": "^1.7.1",
|
|
77
|
-
"@hexclave/
|
|
78
|
-
"@hexclave/ui": "1.0.
|
|
79
|
-
"@hexclave/
|
|
77
|
+
"@hexclave/sc": "1.0.78",
|
|
78
|
+
"@hexclave/ui": "1.0.78",
|
|
79
|
+
"@hexclave/shared": "1.0.78"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"@types/react": ">=18.0.0",
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
6
6
|
//===========================================
|
|
7
7
|
|
|
8
|
-
import { useHash } from '@hexclave/shared/dist/hooks/use-hash';
|
|
9
8
|
import { Button, Typography, cn } from '@hexclave/ui';
|
|
10
9
|
import { XIcon } from 'lucide-react';
|
|
11
10
|
import React, { ReactNode } from 'react';
|
|
12
11
|
import { useStackApp } from '../..';
|
|
12
|
+
import { suspendIfSsr } from '../../utils/react';
|
|
13
13
|
|
|
14
14
|
export type SidebarItem = {
|
|
15
15
|
title: React.ReactNode,
|
|
@@ -21,6 +21,21 @@ export type SidebarItem = {
|
|
|
21
21
|
contentTitle?: React.ReactNode,
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function normalizeHash(hash: string) {
|
|
25
|
+
return hash.startsWith("#") ? hash.slice(1) : hash;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function useHash() {
|
|
29
|
+
suspendIfSsr();
|
|
30
|
+
return React.useSyncExternalStore(
|
|
31
|
+
(onStoreChange) => {
|
|
32
|
+
window.addEventListener("hashchange", onStoreChange);
|
|
33
|
+
return () => window.removeEventListener("hashchange", onStoreChange);
|
|
34
|
+
},
|
|
35
|
+
() => normalizeHash(window.location.hash),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
export function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode, className?: string }) {
|
|
25
40
|
const hash = useHash();
|
|
26
41
|
const selectedIndex = props.items.findIndex(item => item.id && (item.id === hash));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
//===========================================
|
|
3
|
+
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
4
|
+
//===========================================
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { cacheTeamInvitationOperation } from "./team-invitation-cache";
|
|
7
|
+
|
|
8
|
+
describe("cacheTeamInvitationOperation", () => {
|
|
9
|
+
it("reuses results within a session but not across sessions", async () => {
|
|
10
|
+
let resultNumber = 0;
|
|
11
|
+
const operation = vi.fn(async (_app: object, _code: string) => {
|
|
12
|
+
resultNumber += 1;
|
|
13
|
+
return resultNumber;
|
|
14
|
+
});
|
|
15
|
+
const cachedOperation = cacheTeamInvitationOperation(operation);
|
|
16
|
+
const app = {};
|
|
17
|
+
const firstSession = {};
|
|
18
|
+
const secondSession = {};
|
|
19
|
+
|
|
20
|
+
await expect(cachedOperation(app, firstSession, "shared-code")).resolves.toBe(1);
|
|
21
|
+
await expect(cachedOperation(app, firstSession, "shared-code")).resolves.toBe(1);
|
|
22
|
+
await expect(cachedOperation(app, secondSession, "shared-code")).resolves.toBe(2);
|
|
23
|
+
expect(operation).toHaveBeenCalledTimes(2);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
//===========================================
|
|
3
|
+
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
4
|
+
//===========================================
|
|
5
|
+
import { cacheFunction } from "@hexclave/shared/dist/utils/caches";
|
|
6
|
+
|
|
7
|
+
export function cacheTeamInvitationOperation<TApp extends object, TResult>(
|
|
8
|
+
operation: (app: TApp, code: string) => Promise<TResult>,
|
|
9
|
+
): (app: TApp, session: object, code: string) => Promise<TResult> {
|
|
10
|
+
return cacheFunction(async (app: TApp, _session: object, code: string) => {
|
|
11
|
+
return await operation(app, code);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
//===========================================
|
|
7
7
|
|
|
8
8
|
import { KnownErrors } from "@hexclave/shared";
|
|
9
|
-
import { cacheFunction } from "@hexclave/shared/dist/utils/caches";
|
|
10
9
|
import { runAsynchronouslyWithAlert } from "@hexclave/shared/dist/utils/promises";
|
|
11
10
|
import { use } from "@hexclave/shared/dist/utils/react";
|
|
12
11
|
import { Typography } from "@hexclave/ui";
|
|
@@ -14,21 +13,24 @@ import React from "react";
|
|
|
14
13
|
import { MessageCard, StackClientApp, useStackApp, useUser } from "..";
|
|
15
14
|
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
|
|
16
15
|
import { useTranslation } from "../lib/translations";
|
|
16
|
+
import { cacheTeamInvitationOperation } from "./team-invitation-cache";
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Both endpoints inspect the current account, so the session must participate in
|
|
19
|
+
// the key even though the SDK app object and invitation code are otherwise stable.
|
|
20
|
+
const cachedVerifyInvitation = cacheTeamInvitationOperation(async (hexclaveApp: StackClientApp<true>, code: string) => {
|
|
19
21
|
return await hexclaveApp.verifyTeamInvitationCode(code);
|
|
20
22
|
});
|
|
21
23
|
|
|
22
|
-
const cachedGetInvitationDetails =
|
|
24
|
+
const cachedGetInvitationDetails = cacheTeamInvitationOperation(async (hexclaveApp: StackClientApp<true>, code: string) => {
|
|
23
25
|
return await hexclaveApp.getTeamInvitationDetails(code);
|
|
24
26
|
});
|
|
25
27
|
|
|
26
|
-
function TeamInvitationInner(props: { fullPage?: boolean, searchParams: Record<string, string
|
|
28
|
+
function TeamInvitationInner(props: { fullPage?: boolean, searchParams: Record<string, string>, session: object }) {
|
|
27
29
|
const { t } = useTranslation();
|
|
28
30
|
const hexclaveApp = useStackApp();
|
|
29
31
|
const [success, setSuccess] = React.useState(false);
|
|
30
32
|
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
|
|
31
|
-
const details = use(cachedGetInvitationDetails(hexclaveApp, props.searchParams.code || ''));
|
|
33
|
+
const details = use(cachedGetInvitationDetails(hexclaveApp, props.session, props.searchParams.code || ''));
|
|
32
34
|
|
|
33
35
|
if (errorMessage || details.status === 'error') {
|
|
34
36
|
return (
|
|
@@ -131,7 +133,7 @@ export function TeamInvitation({ fullPage=false, searchParams }: { fullPage?: bo
|
|
|
131
133
|
);
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
const verificationResult = use(cachedVerifyInvitation(hexclaveApp, searchParams.code || ''));
|
|
136
|
+
const verificationResult = use(cachedVerifyInvitation(hexclaveApp, user._internalSession, searchParams.code || ''));
|
|
135
137
|
|
|
136
138
|
if (verificationResult.status === 'error') {
|
|
137
139
|
const error = verificationResult.error;
|
|
@@ -146,5 +148,12 @@ export function TeamInvitation({ fullPage=false, searchParams }: { fullPage?: bo
|
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
return
|
|
151
|
+
return (
|
|
152
|
+
<TeamInvitationInner
|
|
153
|
+
key={user.id}
|
|
154
|
+
fullPage={fullPage}
|
|
155
|
+
searchParams={searchParams}
|
|
156
|
+
session={user._internalSession}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
150
159
|
};
|
|
@@ -1404,6 +1404,7 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
1404
1404
|
|
|
1405
1405
|
const items: AdminSessionReplay[] = response.items.map((r) => ({
|
|
1406
1406
|
id: r.id,
|
|
1407
|
+
refreshTokenId: r.refresh_token_id,
|
|
1407
1408
|
projectUser: {
|
|
1408
1409
|
id: r.project_user.id,
|
|
1409
1410
|
displayName: r.project_user.display_name,
|
|
@@ -1425,6 +1426,7 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
1425
1426
|
const response = await this._interface.getSessionReplay(sessionReplayId);
|
|
1426
1427
|
return {
|
|
1427
1428
|
id: response.id,
|
|
1429
|
+
refreshTokenId: response.refresh_token_id,
|
|
1428
1430
|
projectUser: {
|
|
1429
1431
|
id: response.project_user.id,
|
|
1430
1432
|
displayName: response.project_user.display_name,
|
package/src/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
//===========================================
|
|
3
3
|
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
4
4
|
//===========================================
|
|
5
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { hexclaveAppInternalsSymbol } from "../../common";
|
|
7
7
|
import { HexclaveClientApp, HexclaveClientAppConstructorOptions, HexclaveClientAppJson } from "../interfaces/client-app";
|
|
8
|
+
import { stripBrowserActionQueryParam } from "./client-app-impl";
|
|
8
9
|
|
|
9
10
|
const baseOptions = {
|
|
10
11
|
baseUrl: "http://localhost:12345",
|
|
@@ -123,4 +124,40 @@ describe("HexclaveClientApp automatic side effects", () => {
|
|
|
123
124
|
|
|
124
125
|
expect(second).toBe(first);
|
|
125
126
|
});
|
|
127
|
+
|
|
128
|
+
it("keeps stripping the browser action parameter after a router restores it", () => {
|
|
129
|
+
vi.useFakeTimers();
|
|
130
|
+
const previousWindow = Reflect.get(globalThis, "window");
|
|
131
|
+
const hadPreviousWindow = Reflect.has(globalThis, "window");
|
|
132
|
+
let currentHref = "http://localhost:12345/?hexclave_action_id=action-id";
|
|
133
|
+
const browserGlobal = {
|
|
134
|
+
location: {
|
|
135
|
+
get href() {
|
|
136
|
+
return currentHref;
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
history: {
|
|
140
|
+
state: null,
|
|
141
|
+
replaceState: (_state: null, _title: string, url: string | URL) => {
|
|
142
|
+
currentHref = String(url);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
setTimeout,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
Reflect.set(globalThis, "window", browserGlobal);
|
|
149
|
+
try {
|
|
150
|
+
stripBrowserActionQueryParam();
|
|
151
|
+
currentHref = "http://localhost:12345/?hexclave_action_id=action-id";
|
|
152
|
+
vi.runAllTimers();
|
|
153
|
+
expect(currentHref).toBe("http://localhost:12345/");
|
|
154
|
+
} finally {
|
|
155
|
+
vi.useRealTimers();
|
|
156
|
+
if (hadPreviousWindow) {
|
|
157
|
+
Reflect.set(globalThis, "window", previousWindow);
|
|
158
|
+
} else {
|
|
159
|
+
Reflect.deleteProperty(globalThis, "window");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
126
163
|
});
|
|
@@ -11,9 +11,8 @@ import { StackClientApp } from "../interfaces/client-app";
|
|
|
11
11
|
import { planRedirectToHandler } from "./redirect-page-urls";
|
|
12
12
|
|
|
13
13
|
// Every app in this file is constructed with `devTool: false`. The tests install a mock window and document, which makes
|
|
14
|
-
// the SDK believe it is in a browser
|
|
15
|
-
//
|
|
16
|
-
// later `withWriteLock` caller (`_signOut`, most visibly) for the rest of the test file.
|
|
14
|
+
// the SDK believe it is in a browser; mounting the dev tool would then start background requests against a backend that
|
|
15
|
+
// does not exist here and leak that work across test cases.
|
|
17
16
|
function createAccessTokenString(refreshTokenId: string): string {
|
|
18
17
|
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
19
18
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
|
|
2
|
+
//===========================================
|
|
3
|
+
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
4
|
+
//===========================================
|
|
5
|
+
import { AsyncStore } from "@hexclave/shared/dist/utils/stores";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { StackClientApp } from "../interfaces/client-app";
|
|
8
|
+
|
|
9
|
+
function createAccessTokenString(refreshTokenId: string): string {
|
|
10
|
+
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
11
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
12
|
+
return [
|
|
13
|
+
encode({ alg: "none", typ: "JWT" }),
|
|
14
|
+
encode({ sub: "user-id", exp: nowSeconds + 60, iat: nowSeconds, refresh_token_id: refreshTokenId }),
|
|
15
|
+
"",
|
|
16
|
+
].join(".");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class Deferred {
|
|
20
|
+
readonly promise: Promise<void>;
|
|
21
|
+
private _resolve: () => void = () => {
|
|
22
|
+
throw new Error("Deferred promise was resolved before initialization");
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
this.promise = new Promise<void>((resolve) => {
|
|
27
|
+
this._resolve = resolve;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
resolve(): void {
|
|
32
|
+
this._resolve();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("StackClientApp sign-out", () => {
|
|
37
|
+
it("delays current-user reads until redirect dispatch without blocking unrelated stores", async () => {
|
|
38
|
+
const clientApp = new StackClientApp({
|
|
39
|
+
baseUrl: "http://localhost:12345",
|
|
40
|
+
projectId: "00000000-0000-4000-8000-000000000000",
|
|
41
|
+
publishableClientKey: "stack-pk-test",
|
|
42
|
+
tokenStore: "memory",
|
|
43
|
+
redirectMethod: "none",
|
|
44
|
+
noAutomaticPrefetch: true,
|
|
45
|
+
devTool: false,
|
|
46
|
+
});
|
|
47
|
+
const clientInterface = Reflect.get(clientApp, "_interface");
|
|
48
|
+
Reflect.set(clientInterface, "getClientUserByToken", async () => ({
|
|
49
|
+
id: "user-id",
|
|
50
|
+
is_anonymous: false,
|
|
51
|
+
is_restricted: false,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const signInToAccountWithTokens = Reflect.get(clientApp, "_signInToAccountWithTokens");
|
|
55
|
+
await signInToAccountWithTokens.call(clientApp, {
|
|
56
|
+
accessToken: createAccessTokenString("refresh-token-id"),
|
|
57
|
+
refreshToken: "refresh-token",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const createCookieHelper = Reflect.get(clientApp, "_createCookieHelper");
|
|
61
|
+
const getOrCreateTokenStore = Reflect.get(clientApp, "_getOrCreateTokenStore");
|
|
62
|
+
const getSessionFromTokenStore = Reflect.get(clientApp, "_getSessionFromTokenStore");
|
|
63
|
+
const tokenStore = getOrCreateTokenStore.call(clientApp, await createCookieHelper.call(clientApp));
|
|
64
|
+
const session = getSessionFromTokenStore.call(clientApp, tokenStore);
|
|
65
|
+
|
|
66
|
+
Reflect.set(clientInterface, "signOut", async () => {
|
|
67
|
+
session.markInvalid();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const redirectStarted = new Deferred();
|
|
71
|
+
const redirectFinished = new Deferred();
|
|
72
|
+
Reflect.set(clientApp, "_redirectToDefaultAfterSignOut", async () => {
|
|
73
|
+
redirectStarted.resolve();
|
|
74
|
+
await redirectFinished.promise;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const unrelatedStore = new AsyncStore<void>();
|
|
78
|
+
expect(unrelatedStore.setAsync(new Promise<void>(() => {}))).toBeInstanceOf(Promise);
|
|
79
|
+
|
|
80
|
+
const signOut = Reflect.get(clientApp, "_signOut");
|
|
81
|
+
const signOutPromise = signOut.call(clientApp, session);
|
|
82
|
+
await redirectStarted.promise;
|
|
83
|
+
|
|
84
|
+
const getUserDuringSignOut = clientApp.getUser();
|
|
85
|
+
const stateBeforeRedirectFinished = await Promise.race([
|
|
86
|
+
getUserDuringSignOut.then(() => "resolved"),
|
|
87
|
+
Promise.resolve("pending"),
|
|
88
|
+
]);
|
|
89
|
+
expect(stateBeforeRedirectFinished).toBe("pending");
|
|
90
|
+
|
|
91
|
+
redirectFinished.resolve();
|
|
92
|
+
await signOutPromise;
|
|
93
|
+
|
|
94
|
+
await expect(getUserDuringSignOut).resolves.toBeNull();
|
|
95
|
+
});
|
|
96
|
+
});
|