@hexclave/next 1.0.16 → 1.0.18

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.
Files changed (52) hide show
  1. package/dist/dev-tool/dev-tool-styles.js +7 -14
  2. package/dist/dev-tool/dev-tool-styles.js.map +1 -1
  3. package/dist/esm/dev-tool/dev-tool-styles.js +7 -14
  4. package/dist/esm/dev-tool/dev-tool-styles.js.map +1 -1
  5. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  6. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  7. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +4 -3
  8. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  9. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  10. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts +1 -0
  11. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  12. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +15 -1
  13. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  14. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js +31 -0
  15. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  16. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  17. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts +6 -4
  18. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  19. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js +21 -2
  20. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  21. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js +64 -2
  22. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  23. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +2 -2
  24. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  25. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  26. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  27. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +3 -2
  28. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  29. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  30. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts +1 -0
  31. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  32. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +15 -1
  33. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  34. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js +31 -0
  35. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  36. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  37. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts +6 -4
  38. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  39. package/dist/lib/hexclave-app/apps/implementations/session-replay.js +21 -1
  40. package/dist/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  41. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js +62 -0
  42. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  43. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +2 -2
  44. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  45. package/package.json +4 -4
  46. package/src/dev-tool/dev-tool-styles.ts +7 -14
  47. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +4 -3
  48. package/src/lib/hexclave-app/apps/implementations/event-tracker.test.ts +41 -0
  49. package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +16 -0
  50. package/src/lib/hexclave-app/apps/implementations/session-replay.test.ts +85 -2
  51. package/src/lib/hexclave-app/apps/implementations/session-replay.ts +24 -3
  52. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +2 -2
@@ -1,8 +1,20 @@
1
1
  const require_chunk = require('../../../../chunk-BE-pF4vm.js');
2
2
  let vitest = require("vitest");
3
+ let _hexclave_shared_dist_utils_results = require("@hexclave/shared/dist/utils/results");
3
4
  let __session_replay_js = require("./session-replay.js");
4
5
 
5
6
  //#region src/lib/hexclave-app/apps/implementations/session-replay.test.ts
7
+ // @vitest-environment jsdom
8
+ (0, vitest.describe)("session replay options", () => {
9
+ (0, vitest.it)("enables replays by default", () => {
10
+ (0, vitest.expect)((0, __session_replay_js.getSessionReplayOptions)(void 0).enabled).toBe(true);
11
+ (0, vitest.expect)((0, __session_replay_js.getSessionReplayOptions)({}).enabled).toBe(true);
12
+ (0, vitest.expect)((0, __session_replay_js.getSessionReplayOptions)({ replays: {} }).enabled).toBe(true);
13
+ });
14
+ (0, vitest.it)("preserves explicit replay opt-out", () => {
15
+ (0, vitest.expect)((0, __session_replay_js.getSessionReplayOptions)({ replays: { enabled: false } }).enabled).toBe(false);
16
+ });
17
+ });
6
18
  (0, vitest.describe)("analytics option JSON conversion", () => {
7
19
  (0, vitest.it)("preserves top-level analytics options when serializing replay block classes", () => {
8
20
  const json = (0, __session_replay_js.analyticsOptionsToJson)({
@@ -24,6 +36,56 @@ let __session_replay_js = require("./session-replay.js");
24
36
  (0, vitest.expect)(roundTripped?.replays?.blockClass).toEqual(/stack-sensitive/u);
25
37
  });
26
38
  });
39
+ (0, vitest.describe)("SessionRecorder flush", () => {
40
+ (0, vitest.it)("silently disables when server responds with ANALYTICS_NOT_ENABLED", async () => {
41
+ vitest.vi.useFakeTimers();
42
+ const storageKey = `hexclave:session-replay:v1:test-project`;
43
+ localStorage.setItem(storageKey, JSON.stringify({
44
+ session_id: "test-session",
45
+ created_at_ms: Date.now(),
46
+ last_activity_ms: Date.now()
47
+ }));
48
+ const sentBodies = [];
49
+ const recorder = new __session_replay_js.SessionRecorder({
50
+ projectId: "test-project",
51
+ sendBatch: async (body) => {
52
+ sentBodies.push(body);
53
+ return _hexclave_shared_dist_utils_results.Result.ok(new Response(JSON.stringify({
54
+ code: "ANALYTICS_NOT_ENABLED",
55
+ error: "Analytics is not enabled for this project."
56
+ }), {
57
+ status: 400,
58
+ headers: { "x-stack-known-error": "ANALYTICS_NOT_ENABLED" }
59
+ }));
60
+ }
61
+ }, {});
62
+ const warnSpy = vitest.vi.spyOn(console, "warn").mockImplementation(() => {});
63
+ try {
64
+ recorder._events = [{
65
+ type: 2,
66
+ timestamp: Date.now(),
67
+ data: {}
68
+ }];
69
+ recorder._tick();
70
+ await vitest.vi.advanceTimersByTimeAsync(0);
71
+ (0, vitest.expect)(sentBodies).toHaveLength(1);
72
+ (0, vitest.expect)(warnSpy.mock.calls.filter((args) => typeof args[0] === "string" && args[0].includes("SessionRecorder"))).toHaveLength(0);
73
+ recorder._events = [{
74
+ type: 3,
75
+ timestamp: Date.now(),
76
+ data: {}
77
+ }];
78
+ recorder._tick();
79
+ await vitest.vi.advanceTimersByTimeAsync(0);
80
+ (0, vitest.expect)(sentBodies).toHaveLength(1);
81
+ } finally {
82
+ recorder.stop();
83
+ warnSpy.mockRestore();
84
+ localStorage.removeItem(storageKey);
85
+ vitest.vi.useRealTimers();
86
+ }
87
+ });
88
+ });
27
89
 
28
90
  //#endregion
29
91
  //# sourceMappingURL=session-replay.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-replay.test.js","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/session-replay.test.ts"],"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 { describe, expect, it } from \"vitest\";\nimport { analyticsOptionsFromJson, analyticsOptionsToJson } from \"./session-replay\";\n\ndescribe(\"analytics option JSON conversion\", () => {\n it(\"preserves top-level analytics options when serializing replay block classes\", () => {\n const json = analyticsOptionsToJson({\n enabled: false,\n replays: {\n enabled: true,\n blockClass: /stack-sensitive/u,\n },\n });\n\n expect(json?.enabled).toBe(false);\n expect(json?.replays?.enabled).toBe(true);\n });\n\n it(\"preserves top-level analytics options when deserializing replay block classes\", () => {\n const roundTripped = analyticsOptionsFromJson(analyticsOptionsToJson({\n enabled: false,\n replays: {\n blockClass: /stack-sensitive/u,\n },\n }));\n\n expect(roundTripped?.enabled).toBe(false);\n expect(roundTripped?.replays?.blockClass).toEqual(/stack-sensitive/u);\n });\n});\n"],"mappings":";;;;;qBAOS,0CAA0C;AACjD,gBAAG,qFAAqF;EACtF,MAAM,uDAA8B;GAClC,SAAS;GACT,SAAS;IACP,SAAS;IACT,YAAY;IACb;GACF,CAAC;AAEF,qBAAO,MAAM,QAAQ,CAAC,KAAK,MAAM;AACjC,qBAAO,MAAM,SAAS,QAAQ,CAAC,KAAK,KAAK;GACzC;AAEF,gBAAG,uFAAuF;EACxF,MAAM,iHAA+D;GACnE,SAAS;GACT,SAAS,EACP,YAAY,oBACb;GACF,CAAC,CAAC;AAEH,qBAAO,cAAc,QAAQ,CAAC,KAAK,MAAM;AACzC,qBAAO,cAAc,SAAS,WAAW,CAAC,QAAQ,mBAAmB;GACrE;EACF"}
1
+ {"version":3,"file":"session-replay.test.js","names":["SessionRecorder","Result","vi"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/session-replay.test.ts"],"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//===========================================\n// @vitest-environment jsdom\n\nimport { describe, expect, it, vi } from \"vitest\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { analyticsOptionsFromJson, analyticsOptionsToJson, getSessionReplayOptions, SessionRecorder } from \"./session-replay\";\n\ndescribe(\"session replay options\", () => {\n it(\"enables replays by default\", () => {\n expect(getSessionReplayOptions(undefined).enabled).toBe(true);\n expect(getSessionReplayOptions({}).enabled).toBe(true);\n expect(getSessionReplayOptions({ replays: {} }).enabled).toBe(true);\n });\n\n it(\"preserves explicit replay opt-out\", () => {\n expect(getSessionReplayOptions({ replays: { enabled: false } }).enabled).toBe(false);\n });\n});\n\ndescribe(\"analytics option JSON conversion\", () => {\n it(\"preserves top-level analytics options when serializing replay block classes\", () => {\n const json = analyticsOptionsToJson({\n enabled: false,\n replays: {\n enabled: true,\n blockClass: /stack-sensitive/u,\n },\n });\n\n expect(json?.enabled).toBe(false);\n expect(json?.replays?.enabled).toBe(true);\n });\n\n it(\"preserves top-level analytics options when deserializing replay block classes\", () => {\n const roundTripped = analyticsOptionsFromJson(analyticsOptionsToJson({\n enabled: false,\n replays: {\n blockClass: /stack-sensitive/u,\n },\n }));\n\n expect(roundTripped?.enabled).toBe(false);\n expect(roundTripped?.replays?.blockClass).toEqual(/stack-sensitive/u);\n });\n});\n\ndescribe(\"SessionRecorder flush\", () => {\n it(\"silently disables when server responds with ANALYTICS_NOT_ENABLED\", async () => {\n vi.useFakeTimers();\n\n // Seed localStorage with a valid session so _flush doesn't fail on getOrRotateSession\n const storageKey = `hexclave:session-replay:v1:test-project`;\n localStorage.setItem(storageKey, JSON.stringify({\n session_id: \"test-session\",\n created_at_ms: Date.now(),\n last_activity_ms: Date.now(),\n }));\n\n const sentBodies: string[] = [];\n const recorder = new SessionRecorder(\n {\n projectId: \"test-project\",\n sendBatch: async (body) => {\n sentBodies.push(body);\n return Result.ok(new Response(\n JSON.stringify({ code: \"ANALYTICS_NOT_ENABLED\", error: \"Analytics is not enabled for this project.\" }),\n {\n status: 400,\n headers: { \"x-stack-known-error\": \"ANALYTICS_NOT_ENABLED\" },\n },\n ));\n },\n },\n {},\n );\n\n const warnSpy = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n try {\n // Inject an event directly into the recorder's buffer to test flush behavior\n // without needing rrweb. We access private fields for testing purposes.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n (recorder as any)._events = [{ type: 2, timestamp: Date.now(), data: {} }];\n\n // Manually trigger a tick (which calls _flush)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call\n (recorder as any)._tick();\n await vi.advanceTimersByTimeAsync(0);\n\n // One batch should have been sent\n expect(sentBodies).toHaveLength(1);\n\n // No console.warn about \"SessionRecorder flush failed\" should have been emitted\n const flushWarnings = warnSpy.mock.calls.filter(\n (args) => typeof args[0] === \"string\" && args[0].includes(\"SessionRecorder\")\n );\n expect(flushWarnings).toHaveLength(0);\n\n // After disabling, pushing new events and triggering another tick should not send\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n (recorder as any)._events = [{ type: 3, timestamp: Date.now(), data: {} }];\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call\n (recorder as any)._tick();\n await vi.advanceTimersByTimeAsync(0);\n expect(sentBodies).toHaveLength(1);\n } finally {\n recorder.stop();\n warnSpy.mockRestore();\n localStorage.removeItem(storageKey);\n vi.useRealTimers();\n }\n });\n});\n"],"mappings":";;;;;;;qBAUS,gCAAgC;AACvC,gBAAG,oCAAoC;AACrC,sEAA+B,OAAU,CAAC,QAAQ,CAAC,KAAK,KAAK;AAC7D,sEAA+B,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,KAAK;AACtD,sEAA+B,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,KAAK;GACnE;AAEF,gBAAG,2CAA2C;AAC5C,sEAA+B,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,MAAM;GACpF;EACF;qBAEO,0CAA0C;AACjD,gBAAG,qFAAqF;EACtF,MAAM,uDAA8B;GAClC,SAAS;GACT,SAAS;IACP,SAAS;IACT,YAAY;IACb;GACF,CAAC;AAEF,qBAAO,MAAM,QAAQ,CAAC,KAAK,MAAM;AACjC,qBAAO,MAAM,SAAS,QAAQ,CAAC,KAAK,KAAK;GACzC;AAEF,gBAAG,uFAAuF;EACxF,MAAM,iHAA+D;GACnE,SAAS;GACT,SAAS,EACP,YAAY,oBACb;GACF,CAAC,CAAC;AAEH,qBAAO,cAAc,QAAQ,CAAC,KAAK,MAAM;AACzC,qBAAO,cAAc,SAAS,WAAW,CAAC,QAAQ,mBAAmB;GACrE;EACF;qBAEO,+BAA+B;AACtC,gBAAG,qEAAqE,YAAY;AAClF,YAAG,eAAe;EAGlB,MAAM,aAAa;AACnB,eAAa,QAAQ,YAAY,KAAK,UAAU;GAC9C,YAAY;GACZ,eAAe,KAAK,KAAK;GACzB,kBAAkB,KAAK,KAAK;GAC7B,CAAC,CAAC;EAEH,MAAM,aAAuB,EAAE;EAC/B,MAAM,WAAW,IAAIA,oCACnB;GACE,WAAW;GACX,WAAW,OAAO,SAAS;AACzB,eAAW,KAAK,KAAK;AACrB,WAAOC,2CAAO,GAAG,IAAI,SACnB,KAAK,UAAU;KAAE,MAAM;KAAyB,OAAO;KAA8C,CAAC,EACtG;KACE,QAAQ;KACR,SAAS,EAAE,uBAAuB,yBAAyB;KAC5D,CACF,CAAC;;GAEL,EACD,EAAE,CACH;EAED,MAAM,UAAUC,UAAG,MAAM,SAAS,OAAO,CAAC,yBAAyB,GAAG;AAEtE,MAAI;AAIF,GAAC,SAAiB,UAAU,CAAC;IAAE,MAAM;IAAG,WAAW,KAAK,KAAK;IAAE,MAAM,EAAE;IAAE,CAAC;AAI1E,GAAC,SAAiB,OAAO;AACzB,SAAMA,UAAG,yBAAyB,EAAE;AAGpC,sBAAO,WAAW,CAAC,aAAa,EAAE;AAMlC,sBAHsB,QAAQ,KAAK,MAAM,QACtC,SAAS,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,kBAAkB,CAC7E,CACoB,CAAC,aAAa,EAAE;AAIrC,GAAC,SAAiB,UAAU,CAAC;IAAE,MAAM;IAAG,WAAW,KAAK,KAAK;IAAE,MAAM,EAAE;IAAE,CAAC;AAE1E,GAAC,SAAiB,OAAO;AACzB,SAAMA,UAAG,yBAAyB,EAAE;AACpC,sBAAO,WAAW,CAAC,aAAa,EAAE;YAC1B;AACR,YAAS,MAAM;AACf,WAAQ,aAAa;AACrB,gBAAa,WAAW,WAAW;AACnC,aAAG,eAAe;;GAEpB;EACF"}
@@ -36,8 +36,8 @@ type StackClientAppConstructorOptions<HasTokenStore extends boolean, ProjectId e
36
36
  */
37
37
  noAutomaticPrefetch?: boolean;
38
38
  /**
39
- * Options for analytics and session recording. Replays are disabled by default;
40
- * set `{ replays: { enabled: true } }` to opt in.
39
+ * Options for analytics and session recording. Replays are enabled by default;
40
+ * set `{ replays: { enabled: false } }` to opt out.
41
41
  */
42
42
  analytics?: AnalyticsOptions;
43
43
  } & ({
@@ -1 +1 @@
1
- {"version":3,"file":"client-app.js","names":["_HexclaveClientAppImpl"],"sources":["../../../../../src/lib/hexclave-app/apps/interfaces/client-app.ts"],"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 { KnownErrors } from \"@hexclave/shared\";\nimport { CurrentUserCrud } from \"@hexclave/shared/dist/interface/crud/current-user\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { AsyncStoreProperty, AuthLike, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, ResolvedHandlerUrls, hexclaveAppInternalsSymbol, TokenStoreInit } from \"../../common\";\nimport type { RequestListener } from \"@hexclave/shared/dist/interface/client-interface\";\nimport { CustomerInvoicesList, CustomerInvoicesRequestOptions, CustomerProductsList, CustomerProductsRequestOptions, Item } from \"../../customers\";\nimport { Project } from \"../../projects\";\nimport { ProjectCurrentUser, SyncedPartialUser, TokenPartialUser } from \"../../users\";\nimport { _HexclaveClientAppImpl } from \"../implementations\";\nimport { AnalyticsOptions } from \"../implementations/session-replay\";\n\n/** @deprecated Use `HexclaveClientAppConstructorOptions` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = {\n baseUrl?: string | { browser: string, server: string },\n extraRequestHeaders?: Record<string, string>,\n projectId?: ProjectId,\n publishableClientKey?: string,\n urls?: HandlerUrlOptions,\n oauthScopesOnSignIn?: Partial<OAuthScopesOnSignIn>,\n tokenStore?: TokenStoreInit<HasTokenStore>,\n redirectMethod?: RedirectMethod,\n inheritsFrom?: StackClientApp<any, any>,\n\n /**\n * Whether to show the Hexclave dev tool indicator in browser-like development environments.\n *\n * Defaults to true.\n */\n devTool?: boolean,\n\n /**\n * By default, the Stack app will automatically prefetch some data from Stack's server when this app is first\n * constructed. This improves the performance of your app, but will create network requests that are unnecessary if\n * the app is never used or disposed of immediately. To disable this behavior, set this option to true.\n */\n noAutomaticPrefetch?: boolean,\n\n /**\n * Options for analytics and session recording. Replays are disabled by default;\n * set `{ replays: { enabled: true } }` to opt in.\n */\n analytics?: AnalyticsOptions,\n} & (\n { tokenStore: TokenStoreInit<HasTokenStore> } | { tokenStore?: undefined, inheritsFrom: StackClientApp<HasTokenStore, any> }\n) & (\n string extends ProjectId ? unknown : ({ projectId: ProjectId } | { inheritsFrom: StackClientApp<any, ProjectId> })\n);\n\n\n/** @deprecated Use `HexclaveClientAppJson` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId> & { inheritsFrom?: undefined } & {\n uniqueIdentifier: string,\n // note: if you add more fields here, make sure to ensure the checkString in the constructor has/doesn't have them\n};\n\n/** @deprecated Use `HexclaveClientApp` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId extends string = string> = (\n & {\n readonly projectId: ProjectId,\n\n /**\n * The version of the Hexclave SDK.\n */\n readonly version: string,\n\n /**\n * @deprecated `app.urls` is static and does not include runtime redirect-back parameters.\n * For navigation, prefer `redirectToXyz()` methods (for example `redirectToSignIn()`).\n */\n readonly urls: Readonly<ResolvedHandlerUrls>,\n\n signInWithOAuth(provider: string, options?: { returnTo?: string }): Promise<void>,\n signInWithCredential(options: { email: string, password: string, noRedirect?: boolean }): Promise<Result<undefined, KnownErrors[\"EmailPasswordMismatch\"] | KnownErrors[\"InvalidTotpCode\"]>>,\n signUpWithCredential(options: {\n email: string,\n password: string,\n noRedirect?: boolean,\n } & ({ noVerificationCallback: true } | { noVerificationCallback?: false, verificationCallbackUrl?: string })): Promise<Result<undefined, KnownErrors[\"UserWithEmailAlreadyExists\"] | KnownErrors[\"PasswordRequirementsNotMet\"] | KnownErrors[\"BotChallengeFailed\"]>>,\n signInWithPasskey(): Promise<Result<undefined, KnownErrors[\"PasskeyAuthenticationFailed\"] | KnownErrors[\"InvalidTotpCode\"] | KnownErrors[\"PasskeyWebAuthnError\"]>>,\n callOAuthCallback(): Promise<boolean>,\n promptCliLogin(options: { appUrl: string, expiresInMillis?: number, anonRefreshToken?: string, promptLink?: (url: string, loginCode: string) => void }): Promise<Result<string, KnownErrors[\"CliAuthError\"] | KnownErrors[\"CliAuthExpiredError\"] | KnownErrors[\"CliAuthUsedError\"]>>,\n sendForgotPasswordEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<undefined, KnownErrors[\"UserNotFound\"]>>,\n sendMagicLinkEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<{ nonce: string }, KnownErrors[\"RedirectUrlNotWhitelisted\"] | KnownErrors[\"BotChallengeFailed\"]>>,\n resetPassword(options: { code: string, password: string }): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"]>>,\n verifyPasswordResetCode(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"]>>,\n verifyTeamInvitationCode(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"TeamInvitationEmailMismatch\"]>>,\n acceptTeamInvitation(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"TeamInvitationEmailMismatch\"]>>,\n getTeamInvitationDetails(code: string): Promise<Result<{ teamDisplayName: string }, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"TeamInvitationEmailMismatch\"]>>,\n verifyEmail(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"]>>,\n signInWithMagicLink(code: string, options?: { noRedirect?: boolean }): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"InvalidTotpCode\"]>>,\n signInWithMfa(otp: string, code: string, options?: { noRedirect?: boolean }): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"InvalidTotpCode\"]>>,\n\n redirectToOAuthCallback(): Promise<void>,\n\n getConvexClientAuth(options: HasTokenStore extends false ? { tokenStore: TokenStoreInit } : { tokenStore?: TokenStoreInit }): (args: { forceRefreshToken: boolean }) => Promise<string | null>,\n getConvexHttpClientAuth(options: { tokenStore: TokenStoreInit }): Promise<string>,\n\n useUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'redirect' }): ProjectCurrentUser<ProjectId>,\n useUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'throw' }): ProjectCurrentUser<ProjectId>,\n useUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'anonymous' }): ProjectCurrentUser<ProjectId>,\n useUser(options?: GetCurrentUserOptions<HasTokenStore>): ProjectCurrentUser<ProjectId> | null,\n\n getUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'redirect' }): Promise<ProjectCurrentUser<ProjectId>>,\n getUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'throw' }): Promise<ProjectCurrentUser<ProjectId>>,\n getUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'anonymous' }): Promise<ProjectCurrentUser<ProjectId>>,\n getUser(options?: GetCurrentUserOptions<HasTokenStore>): Promise<ProjectCurrentUser<ProjectId> | null>,\n\n cancelSubscription(options: { productId: string, subscriptionId?: string } | { productId: string, subscriptionId?: string, teamId: string }): Promise<void>,\n\n // note: we don't special-case 'anonymous' here to return non-null, see GetPartialUserOptions for more details\n getPartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'token' }): Promise<TokenPartialUser | null>,\n getPartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'convex' }): Promise<TokenPartialUser | null>,\n getPartialUser(options: GetCurrentPartialUserOptions<HasTokenStore>): Promise<SyncedPartialUser | TokenPartialUser | null>,\n usePartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'token' }): TokenPartialUser | null,\n usePartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'convex' }): TokenPartialUser | null,\n usePartialUser(options: GetCurrentPartialUserOptions<HasTokenStore>): SyncedPartialUser | TokenPartialUser | null,\n useNavigate(): (to: string) => void, // THIS_LINE_PLATFORM react-like\n\n [hexclaveAppInternalsSymbol]: {\n toClientJson(): StackClientAppJson<HasTokenStore, ProjectId>,\n setCurrentUser(userJsonPromise: Promise<CurrentUserCrud['Client']['Read'] | null>): void,\n getConstructorOptions(): StackClientAppConstructorOptions<HasTokenStore, ProjectId> & { inheritsFrom?: undefined },\n sendSessionReplayBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,\n sendAnalyticsEventBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,\n addRequestListener(listener: RequestListener): () => void,\n sendRequest(path: string, requestOptions: RequestInit, requestType?: \"client\" | \"server\" | \"admin\"): Promise<Response>,\n getRedirectMethod(): RedirectMethod,\n redirectToUrl(url: string | URL, options?: { replace?: boolean }): Promise<void>,\n redirectToHandler(handlerName: keyof HandlerUrls, options?: RedirectToOptions): Promise<void>,\n signInWithTokens(tokens: { accessToken: string, refreshToken: string }): Promise<void>,\n },\n }\n & AsyncStoreProperty<\"project\", [], Project, false>\n & AsyncStoreProperty<\n \"item\",\n [{ itemId: string, userId: string } | { itemId: string, teamId: string } | { itemId: string, customCustomerId: string }],\n Item,\n false\n >\n & AsyncStoreProperty<\n \"products\",\n [options: CustomerProductsRequestOptions],\n CustomerProductsList,\n true\n >\n & AsyncStoreProperty<\n \"invoices\",\n [options: CustomerInvoicesRequestOptions],\n CustomerInvoicesList,\n true\n >\n & { [K in `redirectTo${Capitalize<keyof Omit<HandlerUrls, 'handler' | 'oauthCallback'>>}`]: (options?: RedirectToOptions) => Promise<void> }\n & AuthLike<HasTokenStore extends false ? { tokenStore: TokenStoreInit } : { tokenStore?: TokenStoreInit }>\n);\n/** @deprecated Use `HexclaveClientAppConstructor` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientAppConstructor = {\n new <\n TokenStoreType extends string,\n HasTokenStore extends (TokenStoreType extends {} ? true : boolean),\n ProjectId extends string\n >(options: StackClientAppConstructorOptions<HasTokenStore, ProjectId>): StackClientApp<HasTokenStore, ProjectId>,\n new(options: StackClientAppConstructorOptions<boolean, string>): StackClientApp<boolean, string>,\n\n [hexclaveAppInternalsSymbol]: {\n fromClientJson<HasTokenStore extends boolean, ProjectId extends string>(\n json: StackClientAppJson<HasTokenStore, ProjectId>\n ): StackClientApp<HasTokenStore, ProjectId>,\n },\n};\nexport type HexclaveClientAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId>;\nexport type HexclaveClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppJson<HasTokenStore, ProjectId>;\nexport type HexclaveClientApp<HasTokenStore extends boolean = boolean, ProjectId extends string = string> = StackClientApp<HasTokenStore, ProjectId>;\nexport type HexclaveClientAppConstructor = StackClientAppConstructor;\nexport const HexclaveClientApp: HexclaveClientAppConstructor = _HexclaveClientAppImpl;\n/** @deprecated Use `HexclaveClientApp` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport const StackClientApp: StackClientAppConstructor = HexclaveClientApp;\n"],"mappings":";;;;;AAiLA,MAAa,oBAAkDA;;AAE/D,MAAa,iBAA4C"}
1
+ {"version":3,"file":"client-app.js","names":["_HexclaveClientAppImpl"],"sources":["../../../../../src/lib/hexclave-app/apps/interfaces/client-app.ts"],"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 { KnownErrors } from \"@hexclave/shared\";\nimport { CurrentUserCrud } from \"@hexclave/shared/dist/interface/crud/current-user\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { AsyncStoreProperty, AuthLike, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, ResolvedHandlerUrls, hexclaveAppInternalsSymbol, TokenStoreInit } from \"../../common\";\nimport type { RequestListener } from \"@hexclave/shared/dist/interface/client-interface\";\nimport { CustomerInvoicesList, CustomerInvoicesRequestOptions, CustomerProductsList, CustomerProductsRequestOptions, Item } from \"../../customers\";\nimport { Project } from \"../../projects\";\nimport { ProjectCurrentUser, SyncedPartialUser, TokenPartialUser } from \"../../users\";\nimport { _HexclaveClientAppImpl } from \"../implementations\";\nimport { AnalyticsOptions } from \"../implementations/session-replay\";\n\n/** @deprecated Use `HexclaveClientAppConstructorOptions` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = {\n baseUrl?: string | { browser: string, server: string },\n extraRequestHeaders?: Record<string, string>,\n projectId?: ProjectId,\n publishableClientKey?: string,\n urls?: HandlerUrlOptions,\n oauthScopesOnSignIn?: Partial<OAuthScopesOnSignIn>,\n tokenStore?: TokenStoreInit<HasTokenStore>,\n redirectMethod?: RedirectMethod,\n inheritsFrom?: StackClientApp<any, any>,\n\n /**\n * Whether to show the Hexclave dev tool indicator in browser-like development environments.\n *\n * Defaults to true.\n */\n devTool?: boolean,\n\n /**\n * By default, the Stack app will automatically prefetch some data from Stack's server when this app is first\n * constructed. This improves the performance of your app, but will create network requests that are unnecessary if\n * the app is never used or disposed of immediately. To disable this behavior, set this option to true.\n */\n noAutomaticPrefetch?: boolean,\n\n /**\n * Options for analytics and session recording. Replays are enabled by default;\n * set `{ replays: { enabled: false } }` to opt out.\n */\n analytics?: AnalyticsOptions,\n} & (\n { tokenStore: TokenStoreInit<HasTokenStore> } | { tokenStore?: undefined, inheritsFrom: StackClientApp<HasTokenStore, any> }\n) & (\n string extends ProjectId ? unknown : ({ projectId: ProjectId } | { inheritsFrom: StackClientApp<any, ProjectId> })\n);\n\n\n/** @deprecated Use `HexclaveClientAppJson` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId> & { inheritsFrom?: undefined } & {\n uniqueIdentifier: string,\n // note: if you add more fields here, make sure to ensure the checkString in the constructor has/doesn't have them\n};\n\n/** @deprecated Use `HexclaveClientApp` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId extends string = string> = (\n & {\n readonly projectId: ProjectId,\n\n /**\n * The version of the Hexclave SDK.\n */\n readonly version: string,\n\n /**\n * @deprecated `app.urls` is static and does not include runtime redirect-back parameters.\n * For navigation, prefer `redirectToXyz()` methods (for example `redirectToSignIn()`).\n */\n readonly urls: Readonly<ResolvedHandlerUrls>,\n\n signInWithOAuth(provider: string, options?: { returnTo?: string }): Promise<void>,\n signInWithCredential(options: { email: string, password: string, noRedirect?: boolean }): Promise<Result<undefined, KnownErrors[\"EmailPasswordMismatch\"] | KnownErrors[\"InvalidTotpCode\"]>>,\n signUpWithCredential(options: {\n email: string,\n password: string,\n noRedirect?: boolean,\n } & ({ noVerificationCallback: true } | { noVerificationCallback?: false, verificationCallbackUrl?: string })): Promise<Result<undefined, KnownErrors[\"UserWithEmailAlreadyExists\"] | KnownErrors[\"PasswordRequirementsNotMet\"] | KnownErrors[\"BotChallengeFailed\"]>>,\n signInWithPasskey(): Promise<Result<undefined, KnownErrors[\"PasskeyAuthenticationFailed\"] | KnownErrors[\"InvalidTotpCode\"] | KnownErrors[\"PasskeyWebAuthnError\"]>>,\n callOAuthCallback(): Promise<boolean>,\n promptCliLogin(options: { appUrl: string, expiresInMillis?: number, anonRefreshToken?: string, promptLink?: (url: string, loginCode: string) => void }): Promise<Result<string, KnownErrors[\"CliAuthError\"] | KnownErrors[\"CliAuthExpiredError\"] | KnownErrors[\"CliAuthUsedError\"]>>,\n sendForgotPasswordEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<undefined, KnownErrors[\"UserNotFound\"]>>,\n sendMagicLinkEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<{ nonce: string }, KnownErrors[\"RedirectUrlNotWhitelisted\"] | KnownErrors[\"BotChallengeFailed\"]>>,\n resetPassword(options: { code: string, password: string }): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"]>>,\n verifyPasswordResetCode(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"]>>,\n verifyTeamInvitationCode(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"TeamInvitationEmailMismatch\"]>>,\n acceptTeamInvitation(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"TeamInvitationEmailMismatch\"]>>,\n getTeamInvitationDetails(code: string): Promise<Result<{ teamDisplayName: string }, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"TeamInvitationEmailMismatch\"]>>,\n verifyEmail(code: string): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"]>>,\n signInWithMagicLink(code: string, options?: { noRedirect?: boolean }): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"InvalidTotpCode\"]>>,\n signInWithMfa(otp: string, code: string, options?: { noRedirect?: boolean }): Promise<Result<undefined, KnownErrors[\"VerificationCodeError\"] | KnownErrors[\"InvalidTotpCode\"]>>,\n\n redirectToOAuthCallback(): Promise<void>,\n\n getConvexClientAuth(options: HasTokenStore extends false ? { tokenStore: TokenStoreInit } : { tokenStore?: TokenStoreInit }): (args: { forceRefreshToken: boolean }) => Promise<string | null>,\n getConvexHttpClientAuth(options: { tokenStore: TokenStoreInit }): Promise<string>,\n\n useUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'redirect' }): ProjectCurrentUser<ProjectId>,\n useUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'throw' }): ProjectCurrentUser<ProjectId>,\n useUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'anonymous' }): ProjectCurrentUser<ProjectId>,\n useUser(options?: GetCurrentUserOptions<HasTokenStore>): ProjectCurrentUser<ProjectId> | null,\n\n getUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'redirect' }): Promise<ProjectCurrentUser<ProjectId>>,\n getUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'throw' }): Promise<ProjectCurrentUser<ProjectId>>,\n getUser(options: GetCurrentUserOptions<HasTokenStore> & { or: 'anonymous' }): Promise<ProjectCurrentUser<ProjectId>>,\n getUser(options?: GetCurrentUserOptions<HasTokenStore>): Promise<ProjectCurrentUser<ProjectId> | null>,\n\n cancelSubscription(options: { productId: string, subscriptionId?: string } | { productId: string, subscriptionId?: string, teamId: string }): Promise<void>,\n\n // note: we don't special-case 'anonymous' here to return non-null, see GetPartialUserOptions for more details\n getPartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'token' }): Promise<TokenPartialUser | null>,\n getPartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'convex' }): Promise<TokenPartialUser | null>,\n getPartialUser(options: GetCurrentPartialUserOptions<HasTokenStore>): Promise<SyncedPartialUser | TokenPartialUser | null>,\n usePartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'token' }): TokenPartialUser | null,\n usePartialUser(options: GetCurrentPartialUserOptions<HasTokenStore> & { from: 'convex' }): TokenPartialUser | null,\n usePartialUser(options: GetCurrentPartialUserOptions<HasTokenStore>): SyncedPartialUser | TokenPartialUser | null,\n useNavigate(): (to: string) => void, // THIS_LINE_PLATFORM react-like\n\n [hexclaveAppInternalsSymbol]: {\n toClientJson(): StackClientAppJson<HasTokenStore, ProjectId>,\n setCurrentUser(userJsonPromise: Promise<CurrentUserCrud['Client']['Read'] | null>): void,\n getConstructorOptions(): StackClientAppConstructorOptions<HasTokenStore, ProjectId> & { inheritsFrom?: undefined },\n sendSessionReplayBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,\n sendAnalyticsEventBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,\n addRequestListener(listener: RequestListener): () => void,\n sendRequest(path: string, requestOptions: RequestInit, requestType?: \"client\" | \"server\" | \"admin\"): Promise<Response>,\n getRedirectMethod(): RedirectMethod,\n redirectToUrl(url: string | URL, options?: { replace?: boolean }): Promise<void>,\n redirectToHandler(handlerName: keyof HandlerUrls, options?: RedirectToOptions): Promise<void>,\n signInWithTokens(tokens: { accessToken: string, refreshToken: string }): Promise<void>,\n },\n }\n & AsyncStoreProperty<\"project\", [], Project, false>\n & AsyncStoreProperty<\n \"item\",\n [{ itemId: string, userId: string } | { itemId: string, teamId: string } | { itemId: string, customCustomerId: string }],\n Item,\n false\n >\n & AsyncStoreProperty<\n \"products\",\n [options: CustomerProductsRequestOptions],\n CustomerProductsList,\n true\n >\n & AsyncStoreProperty<\n \"invoices\",\n [options: CustomerInvoicesRequestOptions],\n CustomerInvoicesList,\n true\n >\n & { [K in `redirectTo${Capitalize<keyof Omit<HandlerUrls, 'handler' | 'oauthCallback'>>}`]: (options?: RedirectToOptions) => Promise<void> }\n & AuthLike<HasTokenStore extends false ? { tokenStore: TokenStoreInit } : { tokenStore?: TokenStoreInit }>\n);\n/** @deprecated Use `HexclaveClientAppConstructor` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport type StackClientAppConstructor = {\n new <\n TokenStoreType extends string,\n HasTokenStore extends (TokenStoreType extends {} ? true : boolean),\n ProjectId extends string\n >(options: StackClientAppConstructorOptions<HasTokenStore, ProjectId>): StackClientApp<HasTokenStore, ProjectId>,\n new(options: StackClientAppConstructorOptions<boolean, string>): StackClientApp<boolean, string>,\n\n [hexclaveAppInternalsSymbol]: {\n fromClientJson<HasTokenStore extends boolean, ProjectId extends string>(\n json: StackClientAppJson<HasTokenStore, ProjectId>\n ): StackClientApp<HasTokenStore, ProjectId>,\n },\n};\nexport type HexclaveClientAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId>;\nexport type HexclaveClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppJson<HasTokenStore, ProjectId>;\nexport type HexclaveClientApp<HasTokenStore extends boolean = boolean, ProjectId extends string = string> = StackClientApp<HasTokenStore, ProjectId>;\nexport type HexclaveClientAppConstructor = StackClientAppConstructor;\nexport const HexclaveClientApp: HexclaveClientAppConstructor = _HexclaveClientAppImpl;\n/** @deprecated Use `HexclaveClientApp` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */\nexport const StackClientApp: StackClientAppConstructor = HexclaveClientApp;\n"],"mappings":";;;;;AAiLA,MAAa,oBAAkDA;;AAE/D,MAAa,iBAA4C"}
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.16",
4
+ "version": "1.0.18",
5
5
  "repository": "https://github.com/hexclave/hexclave",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -75,9 +75,9 @@
75
75
  "rrweb": "^1.1.3",
76
76
  "tsx": "^4.21.0",
77
77
  "yup": "^1.7.1",
78
- "@hexclave/sc": "1.0.16",
79
- "@hexclave/shared": "1.0.16",
80
- "@hexclave/ui": "1.0.16"
78
+ "@hexclave/sc": "1.0.18",
79
+ "@hexclave/shared": "1.0.18",
80
+ "@hexclave/ui": "1.0.18"
81
81
  },
82
82
  "peerDependencies": {
83
83
  "@types/react": ">=18.0.0",
@@ -306,11 +306,14 @@ export const devToolCSS = getInPageUiBaseCSS('.hexclave-devtool') + `
306
306
  .hexclave-devtool .sdt-tab-pane {
307
307
  position: absolute;
308
308
  inset: 0;
309
+ display: none;
309
310
  overflow-y: auto;
310
311
  overflow-x: hidden;
311
312
  padding: 16px;
312
- visibility: hidden;
313
+ background: var(--sdt-bg);
314
+ opacity: 0;
313
315
  pointer-events: none;
316
+ z-index: 0;
314
317
  }
315
318
 
316
319
  .hexclave-devtool .sdt-tab-pane-iframe {
@@ -319,20 +322,10 @@ export const devToolCSS = getInPageUiBaseCSS('.hexclave-devtool') + `
319
322
  }
320
323
 
321
324
  .hexclave-devtool .sdt-tab-pane-active {
322
- visibility: visible;
325
+ display: block;
326
+ opacity: 1;
323
327
  pointer-events: auto;
324
- animation: sdt-tab-fade-in 0.15s ease-out;
325
- }
326
-
327
- @keyframes sdt-tab-fade-in {
328
- from {
329
- opacity: 0;
330
- transform: translateY(6px);
331
- }
332
- to {
333
- opacity: 1;
334
- transform: translateY(0);
335
- }
328
+ z-index: 1;
336
329
  }
337
330
 
338
331
  /* ===== Overview tab — single column ===== */
@@ -67,7 +67,7 @@ import { EventTracker } from "./event-tracker";
67
67
  import type { CrossDomainHandoffParams } from "./redirect-page-urls";
68
68
  import { crossDomainAuthQueryParams, getCrossDomainHandoffParamsFromCurrentUrl, planRedirectToHandler } from "./redirect-page-urls";
69
69
  import { subscribeSessionRefresh } from "./session-refresh-subscription";
70
- import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsOptionsToJson } from "./session-replay";
70
+ import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsOptionsToJson, getSessionReplayOptions } from "./session-replay";
71
71
 
72
72
  import { useAsyncCache } from "./common";
73
73
  import { mountClickmapOverlay } from "../../../../clickmap";
@@ -658,13 +658,14 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
658
658
 
659
659
  const analyticsEnabled = this._analyticsOptions?.enabled !== false;
660
660
 
661
- if (analyticsEnabled && isBrowserLike() && this._hasPersistentTokenStore() && this._analyticsOptions?.replays?.enabled === true) {
661
+ const sessionReplayOptions = getSessionReplayOptions(this._analyticsOptions);
662
+ if (analyticsEnabled && isBrowserLike() && this._hasPersistentTokenStore() && sessionReplayOptions.enabled) {
662
663
  this._sessionRecorder = new SessionRecorder({
663
664
  projectId: this.projectId,
664
665
  sendBatch: async (body, opts) => {
665
666
  return await this._interface.sendSessionReplayBatch(body, await getAnalyticsSession(), opts);
666
667
  },
667
- }, this._analyticsOptions.replays);
668
+ }, sessionReplayOptions);
668
669
  this._sessionRecorder.start();
669
670
  }
670
671
 
@@ -389,4 +389,45 @@ describe("EventTracker", () => {
389
389
  tracker.stop();
390
390
  }
391
391
  });
392
+
393
+ it("silently disables when server responds with ANALYTICS_NOT_ENABLED", async () => {
394
+ vi.useFakeTimers();
395
+ document.body.innerHTML = "<button>Click me</button>";
396
+
397
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
398
+ const sentBodies: string[] = [];
399
+ const tracker = new EventTracker({
400
+ projectId: "internal",
401
+ sendBatch: async (body) => {
402
+ sentBodies.push(body);
403
+ return Result.ok(new Response(
404
+ JSON.stringify({ code: "ANALYTICS_NOT_ENABLED", error: "Analytics is not enabled for this project." }),
405
+ {
406
+ status: 400,
407
+ headers: { "x-stack-known-error": "ANALYTICS_NOT_ENABLED" },
408
+ },
409
+ ));
410
+ },
411
+ });
412
+
413
+ try {
414
+ tracker.start();
415
+
416
+ // First flush sends the initial page-view event; server rejects it.
417
+ await advancePastFlush();
418
+ expect(sentBodies).toHaveLength(1);
419
+
420
+ // No console.warn should have been emitted.
421
+ expect(warnSpy).not.toHaveBeenCalled();
422
+
423
+ // After disabling, new events should not accumulate or trigger further
424
+ // flushes.
425
+ document.querySelector("button")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
426
+ await advancePastFlush();
427
+ expect(sentBodies).toHaveLength(1);
428
+ } finally {
429
+ tracker.stop();
430
+ warnSpy.mockRestore();
431
+ }
432
+ });
392
433
  });
@@ -112,6 +112,7 @@ type TrackedEvent = {
112
112
  export class EventTracker {
113
113
  private _started = false;
114
114
  private _cancelled = false;
115
+ private _disabled = false;
115
116
  private _detachListeners: (() => void) | null = null;
116
117
  private _flushTimer: ReturnType<typeof setInterval> | null = null;
117
118
  private _events: TrackedEvent[] = [];
@@ -177,6 +178,7 @@ export class EventTracker {
177
178
  }
178
179
 
179
180
  private _pushEvent(event: TrackedEvent) {
181
+ if (this._disabled) return;
180
182
  this._events.push(event);
181
183
  this._approxBytes += JSON.stringify(event).length;
182
184
  if (this._events.length >= MAX_EVENTS_PER_BATCH || this._approxBytes >= MAX_APPROX_BYTES_PER_BATCH) {
@@ -468,6 +470,8 @@ export class EventTracker {
468
470
  }
469
471
 
470
472
  private async _flush(options: { keepalive: boolean }) {
473
+ if (this._disabled) return;
474
+
471
475
  // A keepalive flush means the page is unloading — a click still awaiting
472
476
  // dead-click classification led to that unload, so it is alive by
473
477
  // definition and ships unmarked.
@@ -504,6 +508,18 @@ export class EventTracker {
504
508
  }
505
509
 
506
510
  if (!res.data.ok) {
511
+ // If the server tells us analytics is not enabled for this project,
512
+ // silently disable the tracker — no point retrying or warning the user.
513
+ const knownError = res.data.headers.get("x-hexclave-known-error") ?? res.data.headers.get("x-stack-known-error");
514
+ if (knownError === "ANALYTICS_NOT_ENABLED") {
515
+ this._disabled = true;
516
+ if (this._flushTimer !== null) {
517
+ clearInterval(this._flushTimer);
518
+ this._flushTimer = null;
519
+ }
520
+ this._teardown();
521
+ return;
522
+ }
507
523
  console.warn("EventTracker flush failed:", res.data.status, await res.data.text());
508
524
  }
509
525
  }
@@ -2,8 +2,23 @@
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";
6
- import { analyticsOptionsFromJson, analyticsOptionsToJson } from "./session-replay";
5
+ // @vitest-environment jsdom
6
+
7
+ import { describe, expect, it, vi } from "vitest";
8
+ import { Result } from "@hexclave/shared/dist/utils/results";
9
+ import { analyticsOptionsFromJson, analyticsOptionsToJson, getSessionReplayOptions, SessionRecorder } from "./session-replay";
10
+
11
+ describe("session replay options", () => {
12
+ it("enables replays by default", () => {
13
+ expect(getSessionReplayOptions(undefined).enabled).toBe(true);
14
+ expect(getSessionReplayOptions({}).enabled).toBe(true);
15
+ expect(getSessionReplayOptions({ replays: {} }).enabled).toBe(true);
16
+ });
17
+
18
+ it("preserves explicit replay opt-out", () => {
19
+ expect(getSessionReplayOptions({ replays: { enabled: false } }).enabled).toBe(false);
20
+ });
21
+ });
7
22
 
8
23
  describe("analytics option JSON conversion", () => {
9
24
  it("preserves top-level analytics options when serializing replay block classes", () => {
@@ -31,3 +46,71 @@ describe("analytics option JSON conversion", () => {
31
46
  expect(roundTripped?.replays?.blockClass).toEqual(/stack-sensitive/u);
32
47
  });
33
48
  });
49
+
50
+ describe("SessionRecorder flush", () => {
51
+ it("silently disables when server responds with ANALYTICS_NOT_ENABLED", async () => {
52
+ vi.useFakeTimers();
53
+
54
+ // Seed localStorage with a valid session so _flush doesn't fail on getOrRotateSession
55
+ const storageKey = `hexclave:session-replay:v1:test-project`;
56
+ localStorage.setItem(storageKey, JSON.stringify({
57
+ session_id: "test-session",
58
+ created_at_ms: Date.now(),
59
+ last_activity_ms: Date.now(),
60
+ }));
61
+
62
+ const sentBodies: string[] = [];
63
+ const recorder = new SessionRecorder(
64
+ {
65
+ projectId: "test-project",
66
+ sendBatch: async (body) => {
67
+ sentBodies.push(body);
68
+ return Result.ok(new Response(
69
+ JSON.stringify({ code: "ANALYTICS_NOT_ENABLED", error: "Analytics is not enabled for this project." }),
70
+ {
71
+ status: 400,
72
+ headers: { "x-stack-known-error": "ANALYTICS_NOT_ENABLED" },
73
+ },
74
+ ));
75
+ },
76
+ },
77
+ {},
78
+ );
79
+
80
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
81
+
82
+ try {
83
+ // Inject an event directly into the recorder's buffer to test flush behavior
84
+ // without needing rrweb. We access private fields for testing purposes.
85
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
86
+ (recorder as any)._events = [{ type: 2, timestamp: Date.now(), data: {} }];
87
+
88
+ // Manually trigger a tick (which calls _flush)
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
90
+ (recorder as any)._tick();
91
+ await vi.advanceTimersByTimeAsync(0);
92
+
93
+ // One batch should have been sent
94
+ expect(sentBodies).toHaveLength(1);
95
+
96
+ // No console.warn about "SessionRecorder flush failed" should have been emitted
97
+ const flushWarnings = warnSpy.mock.calls.filter(
98
+ (args) => typeof args[0] === "string" && args[0].includes("SessionRecorder")
99
+ );
100
+ expect(flushWarnings).toHaveLength(0);
101
+
102
+ // After disabling, pushing new events and triggering another tick should not send
103
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
104
+ (recorder as any)._events = [{ type: 3, timestamp: Date.now(), data: {} }];
105
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
106
+ (recorder as any)._tick();
107
+ await vi.advanceTimersByTimeAsync(0);
108
+ expect(sentBodies).toHaveLength(1);
109
+ } finally {
110
+ recorder.stop();
111
+ warnSpy.mockRestore();
112
+ localStorage.removeItem(storageKey);
113
+ vi.useRealTimers();
114
+ }
115
+ });
116
+ });
@@ -11,7 +11,7 @@ export type AnalyticsReplayOptions = {
11
11
  /**
12
12
  * Whether session replays are enabled.
13
13
  *
14
- * @default false
14
+ * @default true
15
15
  */
16
16
  enabled?: boolean,
17
17
  /**
@@ -44,12 +44,19 @@ export type AnalyticsOptions = {
44
44
  */
45
45
  enabled?: boolean,
46
46
  /**
47
- * Options for session replay recording. Replays are disabled by default;
48
- * set `enabled: true` to opt in.
47
+ * Options for session replay recording. Replays are enabled by default;
48
+ * set `enabled: false` to opt out.
49
49
  */
50
50
  replays?: AnalyticsReplayOptions,
51
51
  };
52
52
 
53
+ export function getSessionReplayOptions(analyticsOptions: AnalyticsOptions | undefined): AnalyticsReplayOptions {
54
+ return {
55
+ ...analyticsOptions?.replays,
56
+ enabled: analyticsOptions?.replays?.enabled ?? true,
57
+ };
58
+ }
59
+
53
60
  /**
54
61
  * Converts AnalyticsOptions to a JSON-safe representation.
55
62
  * RegExp blockClass values are serialized as `{ __regexp, __flags }` objects.
@@ -161,6 +168,7 @@ export type SessionRecorderDeps = {
161
168
  export class SessionRecorder {
162
169
  private _started = false;
163
170
  private _cancelled = false;
171
+ private _disabled = false;
164
172
  private _stopRecording: (() => void) | null = null;
165
173
  private _detachListeners: (() => void) | null = null;
166
174
  private _flushTimer: ReturnType<typeof setInterval> | null = null;
@@ -228,6 +236,7 @@ export class SessionRecorder {
228
236
  }
229
237
 
230
238
  private async _flush(options: { keepalive: boolean }) {
239
+ if (this._disabled) return;
231
240
  if (this._events.length === 0) return;
232
241
  // Prevent concurrent in-flight HTTP requests. When a flush is already
233
242
  // in-flight, a second batch could race on the server (both call
@@ -265,6 +274,18 @@ export class SessionRecorder {
265
274
  }
266
275
 
267
276
  if (!res.data.ok) {
277
+ // If the server tells us analytics is not enabled for this project,
278
+ // silently disable the recorder — no point retrying or warning the user.
279
+ const knownError = res.data.headers.get("x-hexclave-known-error") ?? res.data.headers.get("x-stack-known-error");
280
+ if (knownError === "ANALYTICS_NOT_ENABLED") {
281
+ this._disabled = true;
282
+ if (this._flushTimer !== null) {
283
+ clearInterval(this._flushTimer);
284
+ this._flushTimer = null;
285
+ }
286
+ this._stopCurrentRecording();
287
+ return;
288
+ }
268
289
  captureWarning("SessionRecorder.flush", new Error(`SessionRecorder flush failed: ${res.data.status} ${await res.data.text()}`));
269
290
  }
270
291
  } finally {
@@ -40,8 +40,8 @@ export type StackClientAppConstructorOptions<HasTokenStore extends boolean, Proj
40
40
  noAutomaticPrefetch?: boolean,
41
41
 
42
42
  /**
43
- * Options for analytics and session recording. Replays are disabled by default;
44
- * set `{ replays: { enabled: true } }` to opt in.
43
+ * Options for analytics and session recording. Replays are enabled by default;
44
+ * set `{ replays: { enabled: false } }` to opt out.
45
45
  */
46
46
  analytics?: AnalyticsOptions,
47
47
  } & (