@hexclave/js 1.0.19 → 1.0.21

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 (71) hide show
  1. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  2. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +34 -5
  3. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  4. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  5. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  6. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +24 -4
  7. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  8. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  9. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts +1 -0
  10. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  11. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +17 -13
  12. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  13. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js +4 -8
  14. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  15. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +2 -2
  16. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
  17. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts +3 -1
  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 +19 -13
  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 +4 -9
  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 +3 -2
  24. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  25. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  26. package/dist/esm/lib/hexclave-app/project-configs/index.d.ts +7 -0
  27. package/dist/esm/lib/hexclave-app/project-configs/index.d.ts.map +1 -1
  28. package/dist/esm/lib/hexclave-app/projects/index.d.ts.map +1 -1
  29. package/dist/esm/lib/hexclave-app/projects/index.js +1 -1
  30. package/dist/esm/lib/hexclave-app/projects/index.js.map +1 -1
  31. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  32. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +34 -5
  33. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  34. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  35. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  36. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +24 -4
  37. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  38. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  39. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts +1 -0
  40. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  41. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +16 -12
  42. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  43. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js +4 -8
  44. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  45. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +2 -2
  46. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
  47. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts +3 -1
  48. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  49. package/dist/lib/hexclave-app/apps/implementations/session-replay.js +19 -12
  50. package/dist/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  51. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js +4 -9
  52. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  53. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +3 -2
  54. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  55. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  56. package/dist/lib/hexclave-app/project-configs/index.d.ts +7 -0
  57. package/dist/lib/hexclave-app/project-configs/index.d.ts.map +1 -1
  58. package/dist/lib/hexclave-app/projects/index.d.ts.map +1 -1
  59. package/dist/lib/hexclave-app/projects/index.js +1 -1
  60. package/dist/lib/hexclave-app/projects/index.js.map +1 -1
  61. package/package.json +2 -2
  62. package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +36 -0
  63. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +43 -4
  64. package/src/lib/hexclave-app/apps/implementations/event-tracker.test.ts +5 -13
  65. package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +19 -14
  66. package/src/lib/hexclave-app/apps/implementations/server-app-impl.ts +2 -2
  67. package/src/lib/hexclave-app/apps/implementations/session-replay.test.ts +4 -20
  68. package/src/lib/hexclave-app/apps/implementations/session-replay.ts +19 -12
  69. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +3 -2
  70. package/src/lib/hexclave-app/project-configs/index.ts +8 -0
  71. package/src/lib/hexclave-app/projects/index.ts +13 -11
@@ -1 +1 @@
1
- {"version":3,"file":"session-replay.js","names":["Result"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/session-replay.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 { isBrowserLike } from \"@hexclave/shared/dist/utils/env\";\nimport { captureWarning } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\n\nexport type AnalyticsReplayOptions = {\n /**\n * Whether session replays are enabled.\n *\n * @default true\n */\n enabled?: boolean,\n /**\n * Whether to mask the content of all `<input>` elements.\n *\n * @default true\n */\n maskAllInputs?: boolean,\n /**\n * A CSS class name or RegExp. Elements with a matching class will be blocked\n * (replaced with a placeholder in the recording).\n *\n * @default undefined\n */\n blockClass?: string | RegExp,\n /**\n * A CSS selector string. Elements matching this selector will be blocked\n * (replaced with a placeholder in the recording).\n *\n * @default undefined\n */\n blockSelector?: string,\n};\n\nexport type AnalyticsOptions = {\n /**\n * Whether SDK-managed analytics capture is enabled.\n *\n * @default true\n */\n enabled?: boolean,\n /**\n * Options for session replay recording. Replays are enabled by default;\n * set `enabled: false` to opt out.\n */\n replays?: AnalyticsReplayOptions,\n};\n\nexport function getSessionReplayOptions(analyticsOptions: AnalyticsOptions | undefined): AnalyticsReplayOptions {\n return {\n ...analyticsOptions?.replays,\n enabled: analyticsOptions?.replays?.enabled ?? true,\n };\n}\n\n/**\n * Converts AnalyticsOptions to a JSON-safe representation.\n * RegExp blockClass values are serialized as `{ __regexp, __flags }` objects.\n * The return type is AnalyticsOptions to keep StackClientAppJson simple;\n * the actual runtime value is JSON-safe.\n */\nexport function analyticsOptionsToJson(options: AnalyticsOptions | undefined): AnalyticsOptions | undefined {\n if (!options?.replays?.blockClass) return options;\n const { blockClass, ...rest } = options.replays;\n if (!(blockClass instanceof RegExp)) return options;\n return {\n ...options,\n replays: {\n ...rest,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n blockClass: { __regexp: blockClass.source, __flags: blockClass.flags } as any,\n },\n };\n}\n\n/**\n * Reconstructs AnalyticsOptions from a JSON-deserialized value.\n * Converts `{ __regexp, __flags }` objects back to RegExp instances.\n */\nexport function analyticsOptionsFromJson(json: AnalyticsOptions | undefined): AnalyticsOptions | undefined {\n if (!json?.replays?.blockClass) return json;\n const { blockClass, ...rest } = json.replays;\n if (typeof blockClass === 'object' && '__regexp' in blockClass) {\n const bc = blockClass as unknown as { __regexp: string, __flags: string };\n return {\n ...json,\n replays: {\n ...rest,\n blockClass: new RegExp(bc.__regexp, bc.__flags),\n },\n };\n }\n return json;\n}\n\n// ---------- Recording internals ----------\n\n// Hexclave rebrand: canonical localStorage prefix (colon delimiters preserved).\nconst LOCAL_STORAGE_PREFIX = \"hexclave:session-replay:v1\";\n// Hexclave rebrand: legacy prefix — dual-read only, so a recording session active\n// across an SDK upgrade is not orphaned. Never written.\nconst LEGACY_LOCAL_STORAGE_PREFIX = \"stack:session-replay:v1\";\nconst IDLE_TTL_MS = 3 * 60 * 1000;\n\nconst FLUSH_INTERVAL_MS = 5_000;\nconst MAX_EVENTS_PER_BATCH = 200;\nconst MAX_APPROX_BYTES_PER_BATCH = 512_000;\n\nexport type StoredSession = {\n session_id: string,\n created_at_ms: number,\n last_activity_ms: number,\n};\n\nexport function safeParseStoredSession(raw: string | null): StoredSession | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null) return null;\n if (typeof parsed.session_id !== \"string\") return null;\n if (typeof parsed.created_at_ms !== \"number\") return null;\n if (typeof parsed.last_activity_ms !== \"number\") return null;\n return parsed as StoredSession;\n } catch {\n return null;\n }\n}\n\nexport function makeStorageKey(projectId: string) {\n return `${LOCAL_STORAGE_PREFIX}:${projectId}`;\n}\n\n// Hexclave rebrand: legacy key, dual-read only (never written).\nexport function makeLegacyStorageKey(projectId: string) {\n return `${LEGACY_LOCAL_STORAGE_PREFIX}:${projectId}`;\n}\n\nexport function generateUuid() {\n return crypto.randomUUID();\n}\n\nexport function getOrRotateSession(options: { key: string, legacyKey?: string, nowMs: number }): StoredSession {\n // Hexclave rebrand: prefer the new key; fall back to the legacy key so a\n // recording session active across an SDK upgrade is not orphaned.\n const existing = safeParseStoredSession(localStorage.getItem(options.key))\n ?? (options.legacyKey ? safeParseStoredSession(localStorage.getItem(options.legacyKey)) : null);\n if (existing && options.nowMs - existing.last_activity_ms <= IDLE_TTL_MS) {\n return existing;\n }\n const next: StoredSession = {\n session_id: generateUuid(),\n created_at_ms: options.nowMs,\n last_activity_ms: options.nowMs,\n };\n localStorage.setItem(options.key, JSON.stringify(next));\n return next;\n}\n\nexport type SessionRecorderDeps = {\n projectId: string,\n sendBatch: (body: string, options: { keepalive: boolean }) => Promise<Result<Response, Error>>,\n};\n\nexport class SessionRecorder {\n private _started = false;\n private _cancelled = false;\n private _disabled = false;\n private _stopRecording: (() => void) | null = null;\n private _detachListeners: (() => void) | null = null;\n private _flushTimer: ReturnType<typeof setInterval> | null = null;\n private _events: unknown[] = [];\n private _approxBytes = 0;\n private _lastPersistActivity = 0;\n private _recording = false;\n private _rrwebModule: typeof import(\"rrweb\") | null = null;\n private _lastBrowserSessionId: string | null = null;\n private _takingSnapshot = false;\n private _flushInProgress = false;\n private readonly _sessionReplaySegmentId: string;\n private readonly _storageKey: string;\n // Hexclave rebrand: legacy key used for dual-read fallback only.\n private readonly _legacyStorageKey: string;\n private readonly _deps: SessionRecorderDeps;\n private readonly _replayOptions: AnalyticsReplayOptions;\n\n constructor(deps: SessionRecorderDeps, replayOptions: AnalyticsReplayOptions) {\n this._deps = deps;\n this._replayOptions = replayOptions;\n this._sessionReplaySegmentId = generateUuid();\n this._storageKey = makeStorageKey(deps.projectId);\n this._legacyStorageKey = makeLegacyStorageKey(deps.projectId);\n }\n\n /**\n * Starts recording. Idempotent — calling multiple times is safe.\n */\n start() {\n if (this._started) return;\n if (!isBrowserLike()) return;\n this._started = true;\n\n // Kick off rrweb recording\n runAsynchronously(() => this._startRecording(), { noErrorLogging: true });\n\n // Periodic flush\n this._flushTimer = setInterval(() => this._tick(), FLUSH_INTERVAL_MS);\n }\n\n stop() {\n this._cancelled = true;\n if (this._flushTimer !== null) {\n clearInterval(this._flushTimer);\n this._flushTimer = null;\n }\n // Flush remaining events before cleanup\n runAsynchronously(() => this._flush({ keepalive: true }));\n this._stopCurrentRecording();\n }\n\n clearBuffer() {\n this._events = [];\n this._approxBytes = 0;\n }\n\n private _persistActivity(nowMs: number): StoredSession {\n const stored = getOrRotateSession({ key: this._storageKey, legacyKey: this._legacyStorageKey, nowMs });\n if (nowMs - this._lastPersistActivity < 5_000) return stored;\n this._lastPersistActivity = nowMs;\n const updated: StoredSession = { ...stored, last_activity_ms: nowMs };\n localStorage.setItem(this._storageKey, JSON.stringify(updated));\n return stored;\n }\n\n private async _flush(options: { keepalive: boolean }) {\n if (this._disabled) return;\n if (this._events.length === 0) return;\n // Prevent concurrent in-flight HTTP requests. When a flush is already\n // in-flight, a second batch could race on the server (both call\n // findRecentSessionReplay before either upsert commits) and create\n // duplicate SessionReplay records. Events stay in _events and will be\n // picked up by the next tick or batch-size check.\n if (this._flushInProgress) return;\n\n const nowMs = Date.now();\n const stored = getOrRotateSession({ key: this._storageKey, legacyKey: this._legacyStorageKey, nowMs });\n\n const batchId = generateUuid();\n const payload = {\n browser_session_id: stored.session_id,\n session_replay_segment_id: this._sessionReplaySegmentId,\n batch_id: batchId,\n started_at_ms: stored.created_at_ms,\n sent_at_ms: nowMs,\n events: this._events,\n };\n\n this._events = [];\n this._approxBytes = 0;\n\n this._flushInProgress = true;\n try {\n const res = await this._deps.sendBatch(\n JSON.stringify(payload),\n { keepalive: options.keepalive },\n );\n\n if (res.status === \"error\") {\n captureWarning(\"SessionRecorder.flush\", res.error);\n return;\n }\n\n if (!res.data.ok) {\n // If the server tells us analytics is not enabled for this project,\n // silently disable the recorder — no point retrying or warning the user.\n const knownError = res.data.headers.get(\"x-hexclave-known-error\") ?? res.data.headers.get(\"x-stack-known-error\");\n if (knownError === \"ANALYTICS_NOT_ENABLED\") {\n this._disabled = true;\n if (this._flushTimer !== null) {\n clearInterval(this._flushTimer);\n this._flushTimer = null;\n }\n this._stopCurrentRecording();\n return;\n }\n captureWarning(\"SessionRecorder.flush\", new Error(`SessionRecorder flush failed: ${res.data.status} ${await res.data.text()}`));\n }\n } finally {\n this._flushInProgress = false;\n }\n }\n\n private async _startRecording() {\n if (this._recording || this._cancelled) return;\n\n if (!this._rrwebModule) {\n const rrwebImport = await Result.fromPromise(import(\"rrweb\"));\n if (rrwebImport.status === \"error\") {\n console.warn(\"SessionRecorder: rrweb import failed. Is rrweb installed?\", rrwebImport.error);\n return;\n }\n this._rrwebModule = rrwebImport.data;\n }\n\n // cancelled may change during the await above\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (this._cancelled) return;\n\n this._stopRecording = this._rrwebModule.record({\n emit: (event) => {\n const nowMs = Date.now();\n const stored = this._persistActivity(nowMs);\n\n // Detect session rotation: after 3+ minutes idle, getOrRotateSession\n // creates a new session ID. We need to inject a FullSnapshot so the\n // new server-side SessionReplay record is playable.\n if (this._lastBrowserSessionId === null) {\n this._lastBrowserSessionId = stored.session_id;\n } else if (stored.session_id !== this._lastBrowserSessionId && !this._takingSnapshot) {\n this._lastBrowserSessionId = stored.session_id;\n // Inject a FullSnapshot for the new session (calls emit synchronously)\n this._takingSnapshot = true;\n try {\n this._rrwebModule!.record.takeFullSnapshot();\n } finally {\n this._takingSnapshot = false;\n }\n }\n\n this._events.push(event);\n this._approxBytes += JSON.stringify(event).length;\n if (this._events.length >= MAX_EVENTS_PER_BATCH || this._approxBytes >= MAX_APPROX_BYTES_PER_BATCH) {\n runAsynchronously(() => this._flush({ keepalive: false }));\n }\n },\n maskAllInputs: this._replayOptions.maskAllInputs ?? true,\n ...(this._replayOptions.blockClass !== undefined ? { blockClass: this._replayOptions.blockClass } : {}),\n ...(this._replayOptions.blockSelector !== undefined ? { blockSelector: this._replayOptions.blockSelector } : {}),\n }) ?? null;\n\n this._recording = true;\n\n const onPageHide = () => {\n runAsynchronously(() => this._flush({ keepalive: true }));\n };\n window.addEventListener(\"pagehide\", onPageHide);\n document.addEventListener(\"visibilitychange\", onPageHide);\n this._detachListeners = () => {\n window.removeEventListener(\"pagehide\", onPageHide);\n document.removeEventListener(\"visibilitychange\", onPageHide);\n };\n }\n\n private _stopCurrentRecording() {\n if (this._detachListeners) {\n this._detachListeners();\n this._detachListeners = null;\n }\n if (this._stopRecording) {\n this._stopRecording();\n this._stopRecording = null;\n }\n this._events = [];\n this._approxBytes = 0;\n this._recording = false;\n }\n\n private _tick() {\n if (this._cancelled) return;\n if (this._events.length > 0) {\n runAsynchronously(() => this._flush({ keepalive: false }));\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-replay.js","names":["KnownErrors","Result"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/session-replay.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/dist/known-errors\";\nimport { isBrowserLike } from \"@hexclave/shared/dist/utils/env\";\nimport { captureWarning } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\n\nexport type AnalyticsReplayOptions = {\n /**\n * Whether session replays are enabled.\n *\n * @default true\n */\n enabled?: boolean,\n /**\n * Whether to mask the content of all `<input>` elements.\n *\n * @default true\n */\n maskAllInputs?: boolean,\n /**\n * A CSS class name or RegExp. Elements with a matching class will be blocked\n * (replaced with a placeholder in the recording).\n *\n * @default undefined\n */\n blockClass?: string | RegExp,\n /**\n * A CSS selector string. Elements matching this selector will be blocked\n * (replaced with a placeholder in the recording).\n *\n * @default undefined\n */\n blockSelector?: string,\n};\n\nexport type AnalyticsOptions = {\n /**\n * Whether SDK-managed analytics capture is enabled.\n *\n * @default true\n */\n enabled?: boolean,\n /**\n * Options for session replay recording. Replays are enabled by default;\n * set `enabled: false` to opt out.\n */\n replays?: AnalyticsReplayOptions,\n};\n\nexport function getSessionReplayOptions(analyticsOptions: AnalyticsOptions | undefined): AnalyticsReplayOptions {\n return {\n ...analyticsOptions?.replays,\n enabled: analyticsOptions?.replays?.enabled ?? true,\n };\n}\n\n/**\n * Converts AnalyticsOptions to a JSON-safe representation.\n * RegExp blockClass values are serialized as `{ __regexp, __flags }` objects.\n * The return type is AnalyticsOptions to keep StackClientAppJson simple;\n * the actual runtime value is JSON-safe.\n */\nexport function analyticsOptionsToJson(options: AnalyticsOptions | undefined): AnalyticsOptions | undefined {\n if (!options?.replays?.blockClass) return options;\n const { blockClass, ...rest } = options.replays;\n if (!(blockClass instanceof RegExp)) return options;\n return {\n ...options,\n replays: {\n ...rest,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n blockClass: { __regexp: blockClass.source, __flags: blockClass.flags } as any,\n },\n };\n}\n\n/**\n * Reconstructs AnalyticsOptions from a JSON-deserialized value.\n * Converts `{ __regexp, __flags }` objects back to RegExp instances.\n */\nexport function analyticsOptionsFromJson(json: AnalyticsOptions | undefined): AnalyticsOptions | undefined {\n if (!json?.replays?.blockClass) return json;\n const { blockClass, ...rest } = json.replays;\n if (typeof blockClass === 'object' && '__regexp' in blockClass) {\n const bc = blockClass as unknown as { __regexp: string, __flags: string };\n return {\n ...json,\n replays: {\n ...rest,\n blockClass: new RegExp(bc.__regexp, bc.__flags),\n },\n };\n }\n return json;\n}\n\n// ---------- Recording internals ----------\n\n// Hexclave rebrand: canonical localStorage prefix (colon delimiters preserved).\nconst LOCAL_STORAGE_PREFIX = \"hexclave:session-replay:v1\";\n// Hexclave rebrand: legacy prefix — dual-read only, so a recording session active\n// across an SDK upgrade is not orphaned. Never written.\nconst LEGACY_LOCAL_STORAGE_PREFIX = \"stack:session-replay:v1\";\nconst IDLE_TTL_MS = 3 * 60 * 1000;\n\nconst FLUSH_INTERVAL_MS = 5_000;\nconst MAX_EVENTS_PER_BATCH = 200;\nconst MAX_APPROX_BYTES_PER_BATCH = 512_000;\n\nexport type StoredSession = {\n session_id: string,\n created_at_ms: number,\n last_activity_ms: number,\n};\n\nexport function safeParseStoredSession(raw: string | null): StoredSession | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null) return null;\n if (typeof parsed.session_id !== \"string\") return null;\n if (typeof parsed.created_at_ms !== \"number\") return null;\n if (typeof parsed.last_activity_ms !== \"number\") return null;\n return parsed as StoredSession;\n } catch {\n return null;\n }\n}\n\nexport function makeStorageKey(projectId: string) {\n return `${LOCAL_STORAGE_PREFIX}:${projectId}`;\n}\n\n// Hexclave rebrand: legacy key, dual-read only (never written).\nexport function makeLegacyStorageKey(projectId: string) {\n return `${LEGACY_LOCAL_STORAGE_PREFIX}:${projectId}`;\n}\n\nexport function generateUuid() {\n return crypto.randomUUID();\n}\n\nexport function getOrRotateSession(options: { key: string, legacyKey?: string, nowMs: number }): StoredSession {\n // Hexclave rebrand: prefer the new key; fall back to the legacy key so a\n // recording session active across an SDK upgrade is not orphaned.\n const existing = safeParseStoredSession(localStorage.getItem(options.key))\n ?? (options.legacyKey ? safeParseStoredSession(localStorage.getItem(options.legacyKey)) : null);\n if (existing && options.nowMs - existing.last_activity_ms <= IDLE_TTL_MS) {\n return existing;\n }\n const next: StoredSession = {\n session_id: generateUuid(),\n created_at_ms: options.nowMs,\n last_activity_ms: options.nowMs,\n };\n localStorage.setItem(options.key, JSON.stringify(next));\n return next;\n}\n\nexport type SessionRecorderDeps = {\n projectId: string,\n sendBatch: (body: string, options: { keepalive: boolean }) => Promise<Result<Response, Error>>,\n};\n\nexport function isAnalyticsNotEnabledError(error: unknown): boolean {\n return KnownErrors.AnalyticsNotEnabled.isInstance(error);\n}\n\nexport class SessionRecorder {\n private _started = false;\n private _cancelled = false;\n private _disabled = false;\n private _stopRecording: (() => void) | null = null;\n private _detachListeners: (() => void) | null = null;\n private _flushTimer: ReturnType<typeof setInterval> | null = null;\n private _events: unknown[] = [];\n private _approxBytes = 0;\n private _lastPersistActivity = 0;\n private _recording = false;\n private _rrwebModule: typeof import(\"rrweb\") | null = null;\n private _lastBrowserSessionId: string | null = null;\n private _takingSnapshot = false;\n private _flushInProgress = false;\n private readonly _sessionReplaySegmentId: string;\n private readonly _storageKey: string;\n // Hexclave rebrand: legacy key used for dual-read fallback only.\n private readonly _legacyStorageKey: string;\n private readonly _deps: SessionRecorderDeps;\n private readonly _replayOptions: AnalyticsReplayOptions;\n\n constructor(deps: SessionRecorderDeps, replayOptions: AnalyticsReplayOptions) {\n this._deps = deps;\n this._replayOptions = replayOptions;\n this._sessionReplaySegmentId = generateUuid();\n this._storageKey = makeStorageKey(deps.projectId);\n this._legacyStorageKey = makeLegacyStorageKey(deps.projectId);\n }\n\n /**\n * Starts recording. Idempotent — calling multiple times is safe.\n */\n start() {\n if (this._started) return;\n if (!isBrowserLike()) return;\n this._started = true;\n\n // Kick off rrweb recording\n runAsynchronously(() => this._startRecording(), { noErrorLogging: true });\n\n // Periodic flush\n this._flushTimer = setInterval(() => this._tick(), FLUSH_INTERVAL_MS);\n }\n\n stop() {\n this._cancelled = true;\n if (this._flushTimer !== null) {\n clearInterval(this._flushTimer);\n this._flushTimer = null;\n }\n // Flush remaining events before cleanup\n runAsynchronously(() => this._flush({ keepalive: true }));\n this._stopCurrentRecording();\n }\n\n clearBuffer() {\n this._events = [];\n this._approxBytes = 0;\n }\n\n private _persistActivity(nowMs: number): StoredSession {\n const stored = getOrRotateSession({ key: this._storageKey, legacyKey: this._legacyStorageKey, nowMs });\n if (nowMs - this._lastPersistActivity < 5_000) return stored;\n this._lastPersistActivity = nowMs;\n const updated: StoredSession = { ...stored, last_activity_ms: nowMs };\n localStorage.setItem(this._storageKey, JSON.stringify(updated));\n return stored;\n }\n\n private async _flush(options: { keepalive: boolean }) {\n if (this._disabled) return;\n if (this._events.length === 0) return;\n // Prevent concurrent in-flight HTTP requests. When a flush is already\n // in-flight, a second batch could race on the server (both call\n // findRecentSessionReplay before either upsert commits) and create\n // duplicate SessionReplay records. Events stay in _events and will be\n // picked up by the next tick or batch-size check.\n if (this._flushInProgress) return;\n\n const nowMs = Date.now();\n const stored = getOrRotateSession({ key: this._storageKey, legacyKey: this._legacyStorageKey, nowMs });\n\n const batchId = generateUuid();\n const payload = {\n browser_session_id: stored.session_id,\n session_replay_segment_id: this._sessionReplaySegmentId,\n batch_id: batchId,\n started_at_ms: stored.created_at_ms,\n sent_at_ms: nowMs,\n events: this._events,\n };\n\n this._events = [];\n this._approxBytes = 0;\n\n this._flushInProgress = true;\n try {\n const res = await this._deps.sendBatch(\n JSON.stringify(payload),\n { keepalive: options.keepalive },\n );\n\n if (res.status === \"error\") {\n if (isAnalyticsNotEnabledError(res.error)) {\n this._disable();\n return;\n }\n captureWarning(\"SessionRecorder.flush\", res.error);\n return;\n }\n\n if (!res.data.ok) {\n captureWarning(\"SessionRecorder.flush\", new Error(`SessionRecorder flush failed: ${res.data.status} ${await res.data.text()}`));\n }\n } finally {\n this._flushInProgress = false;\n }\n }\n\n private _disable() {\n this._disabled = true;\n this.clearBuffer();\n if (this._flushTimer !== null) {\n clearInterval(this._flushTimer);\n this._flushTimer = null;\n }\n this._stopCurrentRecording();\n }\n\n private async _startRecording() {\n if (this._recording || this._cancelled) return;\n\n if (!this._rrwebModule) {\n const rrwebImport = await Result.fromPromise(import(\"rrweb\"));\n if (rrwebImport.status === \"error\") {\n console.warn(\"SessionRecorder: rrweb import failed. Is rrweb installed?\", rrwebImport.error);\n return;\n }\n this._rrwebModule = rrwebImport.data;\n }\n\n // cancelled may change during the await above\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (this._cancelled) return;\n\n this._stopRecording = this._rrwebModule.record({\n emit: (event) => {\n const nowMs = Date.now();\n const stored = this._persistActivity(nowMs);\n\n // Detect session rotation: after 3+ minutes idle, getOrRotateSession\n // creates a new session ID. We need to inject a FullSnapshot so the\n // new server-side SessionReplay record is playable.\n if (this._lastBrowserSessionId === null) {\n this._lastBrowserSessionId = stored.session_id;\n } else if (stored.session_id !== this._lastBrowserSessionId && !this._takingSnapshot) {\n this._lastBrowserSessionId = stored.session_id;\n // Inject a FullSnapshot for the new session (calls emit synchronously)\n this._takingSnapshot = true;\n try {\n this._rrwebModule!.record.takeFullSnapshot();\n } finally {\n this._takingSnapshot = false;\n }\n }\n\n this._events.push(event);\n this._approxBytes += JSON.stringify(event).length;\n if (this._events.length >= MAX_EVENTS_PER_BATCH || this._approxBytes >= MAX_APPROX_BYTES_PER_BATCH) {\n runAsynchronously(() => this._flush({ keepalive: false }));\n }\n },\n maskAllInputs: this._replayOptions.maskAllInputs ?? true,\n ...(this._replayOptions.blockClass !== undefined ? { blockClass: this._replayOptions.blockClass } : {}),\n ...(this._replayOptions.blockSelector !== undefined ? { blockSelector: this._replayOptions.blockSelector } : {}),\n }) ?? null;\n\n this._recording = true;\n\n const onPageHide = () => {\n runAsynchronously(() => this._flush({ keepalive: true }));\n };\n window.addEventListener(\"pagehide\", onPageHide);\n document.addEventListener(\"visibilitychange\", onPageHide);\n this._detachListeners = () => {\n window.removeEventListener(\"pagehide\", onPageHide);\n document.removeEventListener(\"visibilitychange\", onPageHide);\n };\n }\n\n private _stopCurrentRecording() {\n if (this._detachListeners) {\n this._detachListeners();\n this._detachListeners = null;\n }\n if (this._stopRecording) {\n this._stopRecording();\n this._stopRecording = null;\n }\n this._events = [];\n this._approxBytes = 0;\n this._recording = false;\n }\n\n private _tick() {\n if (this._cancelled) return;\n if (this._events.length > 0) {\n runAsynchronously(() => this._flush({ keepalive: false }));\n }\n }\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ const require_chunk = require('../../../../chunk-BE-pF4vm.js');
2
2
  let vitest = require("vitest");
3
3
  let _hexclave_shared_dist_utils_results = require("@hexclave/shared/dist/utils/results");
4
4
  let __session_replay_js = require("./session-replay.js");
5
+ let _hexclave_shared_dist_known_errors = require("@hexclave/shared/dist/known-errors");
5
6
 
6
7
  //#region src/lib/hexclave-app/apps/implementations/session-replay.test.ts
7
8
  // @vitest-environment jsdom
@@ -37,7 +38,7 @@ let __session_replay_js = require("./session-replay.js");
37
38
  });
38
39
  });
39
40
  (0, vitest.describe)("SessionRecorder flush", () => {
40
- (0, vitest.it)("silently disables when server responds with ANALYTICS_NOT_ENABLED", async () => {
41
+ (0, vitest.it)("silently disables when client interface returns ANALYTICS_NOT_ENABLED as an error", async () => {
41
42
  vitest.vi.useFakeTimers();
42
43
  const storageKey = `hexclave:session-replay:v1:test-project`;
43
44
  localStorage.setItem(storageKey, JSON.stringify({
@@ -50,13 +51,7 @@ let __session_replay_js = require("./session-replay.js");
50
51
  projectId: "test-project",
51
52
  sendBatch: async (body) => {
52
53
  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
- }));
54
+ return _hexclave_shared_dist_utils_results.Result.error(new _hexclave_shared_dist_known_errors.KnownErrors.AnalyticsNotEnabled());
60
55
  }
61
56
  }, {});
62
57
  const warnSpy = vitest.vi.spyOn(console, "warn").mockImplementation(() => {});
@@ -69,7 +64,7 @@ let __session_replay_js = require("./session-replay.js");
69
64
  recorder._tick();
70
65
  await vitest.vi.advanceTimersByTimeAsync(0);
71
66
  (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);
67
+ (0, vitest.expect)(warnSpy).not.toHaveBeenCalled();
73
68
  recorder._events = [{
74
69
  type: 3,
75
70
  timestamp: Date.now(),
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"session-replay.test.js","names":["SessionRecorder","Result","KnownErrors","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 { KnownErrors } from \"@hexclave/shared/dist/known-errors\";\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 client interface returns ANALYTICS_NOT_ENABLED as an error\", async () => {\n vi.useFakeTimers();\n\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.error(new KnownErrors.AnalyticsNotEnabled());\n },\n },\n {},\n );\n\n const warnSpy = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n (recorder as any)._events = [{ type: 2, timestamp: Date.now(), data: {} }];\n\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 expect(sentBodies).toHaveLength(1);\n expect(warnSpy).not.toHaveBeenCalled();\n\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":";;;;;;;;qBAWS,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,qFAAqF,YAAY;AAClG,YAAG,eAAe;EAElB,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,MAAM,IAAIC,+CAAY,qBAAqB,CAAC;;GAE7D,EACD,EAAE,CACH;EAED,MAAM,UAAUC,UAAG,MAAM,SAAS,OAAO,CAAC,yBAAyB,GAAG;AAEtE,MAAI;AAEF,GAAC,SAAiB,UAAU,CAAC;IAAE,MAAM;IAAG,WAAW,KAAK,KAAK;IAAE,MAAM,EAAE;IAAE,CAAC;AAG1E,GAAC,SAAiB,OAAO;AACzB,SAAMA,UAAG,yBAAyB,EAAE;AAEpC,sBAAO,WAAW,CAAC,aAAa,EAAE;AAClC,sBAAO,QAAQ,CAAC,IAAI,kBAAkB;AAGtC,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"}
@@ -64,8 +64,9 @@ type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId extends s
64
64
  */
65
65
  readonly version: string;
66
66
  /**
67
- * @deprecated `app.urls` is static and does not include runtime redirect-back parameters.
68
- * For navigation, prefer `redirectToXyz()` methods (for example `redirectToSignIn()`).
67
+ * @deprecated Do not use `app.urls` for navigation. It is static and does not include runtime redirect-back,
68
+ * cross-domain auth, or sign-out state. Use the matching `redirectToXyz()` method instead, for example
69
+ * `redirectToSignIn()`, `redirectToSignUp()`, `redirectToSignOut()`, or `redirectToAccountSettings()`.
69
70
  */
70
71
  readonly urls: Readonly<ResolvedHandlerUrls>;
71
72
  signInWithOAuth(provider: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"client-app.d.ts","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/interfaces/client-app.ts"],"mappings":"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"}
1
+ {"version":3,"file":"client-app.d.ts","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/interfaces/client-app.ts"],"mappings":"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"}
@@ -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 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\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\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":";;;;;AAyKA,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 Do not use `app.urls` for navigation. It is static and does not include runtime redirect-back,\n * cross-domain auth, or sign-out state. Use the matching `redirectToXyz()` method instead, for example\n * `redirectToSignIn()`, `redirectToSignUp()`, `redirectToSignOut()`, or `redirectToAccountSettings()`.\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\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\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":";;;;;AA0KA,MAAa,oBAAkDA;;AAE/D,MAAa,iBAA4C"}
@@ -64,6 +64,13 @@ type AdminOAuthProviderConfig = {
64
64
  facebookConfigId?: string;
65
65
  microsoftTenantId?: string;
66
66
  appleBundleIds?: string[];
67
+ } | {
68
+ type: 'custom_oidc';
69
+ clientId: string;
70
+ clientSecret: string;
71
+ issuerUrl: string;
72
+ scope?: string;
73
+ displayName?: string;
67
74
  }) & OAuthProviderConfig;
68
75
  type AdminProjectConfigUpdateOptions = {
69
76
  domains?: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../../src/lib/hexclave-app/project-configs/index.ts"],"mappings":";;;KAMY,aAAA;EAAA,SACD,aAAA;EAAA,SACA,iBAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,yBAAA;EAAA,SACA,yBAAA;EAAA,SACA,cAAA,EAAgB,mBAAA;EAAA,SAChB,gBAAA;EAAA,SACA,gBAAA;AAAA;AAAA,KAGC,mBAAA;EAAA,SACD,EAAA;AAAA;;;;KAMC,kBAAA;EAAA,SACD,aAAA;EAAA,SACA,iBAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,yBAAA;EAAA,SACA,yBAAA;EAAA,SACA,cAAA;EAAA,SACA,cAAA,EAAgB,wBAAA;EAAA,SAChB,WAAA,GAAc,gBAAA;EAAA,SACd,UAAA;EAAA,SACA,OAAA,EAAS,iBAAA;EAAA,SACT,kBAAA;EAAA,SACA,6BAAA,EAA+B,mBAAA;EAAA,SAC/B,4BAAA,EAA8B,mBAAA;EAAA,SAC9B,sBAAA,EAAwB,mBAAA;EAAA,SACxB,yBAAA;EAAA,SACA,gBAAA;EAAA,SACA,gBAAA;AAAA;AAAA,KAGC,gBAAA;EAER,IAAA;EACA,UAAA;EACA,WAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA;EACA,QAAA;AAAA;EAGA,IAAA;AAAA;AAAA,KAIQ,iBAAA;EACV,MAAA;EACA,WAAA;AAAA;AAAA,KAGU,wBAAA;EACV,EAAA;AAAA;EAEM,IAAA;AAAA;EAEF,IAAA;EACA,QAAA;EACA,YAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AAAA,KAEA,mBAAA;AAAA,KAEM,+BAAA;EACV,OAAA;IACE,MAAA;IACA,WAAA;EAAA;EAEF,cAAA,GAAiB,wBAAA;EACjB,aAAA;EACA,iBAAA;EACA,gBAAA;EACA,cAAA;EACA,yBAAA;EACA,yBAAA;EACA,cAAA;EACA,kBAAA;EACA,WAAA,GAAc,gBAAA;EACd,UAAA;EACA,6BAAA;IAAkC,EAAA;EAAA;EAClC,4BAAA;IAAiC,EAAA;EAAA;EACjC,sBAAA;IAA2B,EAAA;EAAA;EAC3B,yBAAA;EACA,gBAAA;EACA,gBAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../../src/lib/hexclave-app/project-configs/index.ts"],"mappings":";;;KAMY,aAAA;EAAA,SACD,aAAA;EAAA,SACA,iBAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,yBAAA;EAAA,SACA,yBAAA;EAAA,SACA,cAAA,EAAgB,mBAAA;EAAA,SAChB,gBAAA;EAAA,SACA,gBAAA;AAAA;AAAA,KAGC,mBAAA;EAAA,SACD,EAAA;AAAA;;;;KAMC,kBAAA;EAAA,SACD,aAAA;EAAA,SACA,iBAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,yBAAA;EAAA,SACA,yBAAA;EAAA,SACA,cAAA;EAAA,SACA,cAAA,EAAgB,wBAAA;EAAA,SAChB,WAAA,GAAc,gBAAA;EAAA,SACd,UAAA;EAAA,SACA,OAAA,EAAS,iBAAA;EAAA,SACT,kBAAA;EAAA,SACA,6BAAA,EAA+B,mBAAA;EAAA,SAC/B,4BAAA,EAA8B,mBAAA;EAAA,SAC9B,sBAAA,EAAwB,mBAAA;EAAA,SACxB,yBAAA;EAAA,SACA,gBAAA;EAAA,SACA,gBAAA;AAAA;AAAA,KAGC,gBAAA;EAER,IAAA;EACA,UAAA;EACA,WAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA;EACA,QAAA;AAAA;EAGA,IAAA;AAAA;AAAA,KAIQ,iBAAA;EACV,MAAA;EACA,WAAA;AAAA;AAAA,KAGU,wBAAA;EACV,EAAA;AAAA;EAEM,IAAA;AAAA;EAEF,IAAA;EACA,QAAA;EACA,YAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AAAA;EAGA,IAAA;EACA,QAAA;EACA,YAAA;EACA,SAAA;EACA,KAAA;EACA,WAAA;AAAA,KAEA,mBAAA;AAAA,KAEM,+BAAA;EACV,OAAA;IACE,MAAA;IACA,WAAA;EAAA;EAEF,cAAA,GAAiB,wBAAA;EACjB,aAAA;EACA,iBAAA;EACA,gBAAA;EACA,cAAA;EACA,yBAAA;EACA,yBAAA;EACA,cAAA;EACA,kBAAA;EACA,WAAA,GAAc,gBAAA;EACd,UAAA;EACA,6BAAA;IAAkC,EAAA;EAAA;EAClC,4BAAA;IAAiC,EAAA;EAAA;EACjC,sBAAA;IAA2B,EAAA;EAAA;EAC3B,yBAAA;EACA,gBAAA;EACA,gBAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../../src/lib/hexclave-app/projects/index.ts"],"mappings":";;;;;;;;;;AAgBA;;KAAY,kBAAA;EACN,IAAA;EAA4B,KAAA;EAAe,IAAA;EAAc,MAAA;EAAgB,UAAA;EAAoB,cAAA;EAAwB,YAAA;AAAA;EACrH,IAAA;AAAA;EACA,IAAA;AAAA;AAAA,KAEM,iBAAA;EAAiB;;;EAI3B,MAAA,EAAQ,kBAAA;AAAA;AAAA,KAIE,OAAA;EAAA,SACD,EAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,EAAQ,aAAA;AAAA;AAAA,KAGP,YAAA;EAAA,SACD,EAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,SAAA,EAAW,IAAA;EAAA,SACX,gBAAA;EAAA,SACA,wBAAA;EAAA,SACA,WAAA;EAAA,SACA,gBAAA,EAAkB,uBAAA;EAAA,SAClB,OAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA;EAAA,SACA,mBAAA;EAAA,SAEA,MAAA,EAAQ,kBAAA;EAEjB,MAAA,CAAO,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,yBAAA,GAA4B,OAAA;EAC/D,MAAA,CAAO,IAAA,EAAM,YAAA,GAAe,OAAA;EAE5B,SAAA,CAAU,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,cAAA;EAAR;;;;;;;EAU/B,YAAA,CACE,IAAA,EAAM,YAAA,EACN,MAAA,EAAQ,iCAAA,GACP,OAAA;EA6BK;;;;;;;;;;EAjBR,UAAA,CACE,IAAA,EAAM,YAAA,EACN,MAAA,EAAQ,iCAAA,EACR,OAAA,EAAS,iBAAA,GACR,OAAA;EA8C6E;;;;;;;;;;EAlChF,kBAAA,CACE,IAAA,EAAM,YAAA,EACN,MAAA,EAAQ,iCAAA,GACP,OAAA;EA9DM;;;;;;;;EAwET,qBAAA,CAAsB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,kBAAA;EAjExB;;;;EAuE3B,wBAAA,CAAyB,IAAA,EAAM,YAAA,GAAe,OAAA;EAjErC;;;;;;;EA0ET,uBAAA,CAAwB,IAAA,EAAM,YAAA,EAAc,KAAA,4BAAiC,IAAA,aAAiB,OAAA;EAvE9F;;;;EA6EA,iBAAA,CAAkB,IAAA,EAAM,YAAA,EAAc,KAAA,6BAAkC,OAAA,CAAQ,MAAA;EA3EhE;;;;EAiFhB,qBAAA,CAAsB,IAAA,EAAM,YAAA,EAAc,KAAA,4BAAiC,MAAA,EAAQ,MAAA,oBAA0B,OAAA;EAE7G,uBAAA,CAAwB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,mBAAA;AAAA,IACnD,OAAA;AAAA,KAEQ,iBAAA;EAAA,SACD,GAAA,EAAK,aAAA;AAAA,IACZ,YAAA;AAAA,KAEQ,yBAAA;EACV,WAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA,GAAmB,uBAAA;EAlER;;;EAsEX,2BAAA;EACA,OAAA;EACA,WAAA;EACA,eAAA;EACA,mBAAA;EACA,MAAA,GAAS,+BAAA;AAAA;AAAA,iBAEK,+BAAA,CAAgC,OAAA,EAAS,yBAAA,GAA4B,YAAA;AAAA,KA8DzE,yBAAA,GAA4B,IAAA,CAAK,yBAAA;EAC3C,WAAA;EACA,MAAA;EACA,wBAAA;AAAA;AAAA,iBAEc,+BAAA,CAAgC,OAAA,EAAS,yBAAA,GAA4B,qBAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../../src/lib/hexclave-app/projects/index.ts"],"mappings":";;;;;;;;;;AAgBA;;KAAY,kBAAA;EACN,IAAA;EAA4B,KAAA;EAAe,IAAA;EAAc,MAAA;EAAgB,UAAA;EAAoB,cAAA;EAAwB,YAAA;AAAA;EACrH,IAAA;AAAA;EACA,IAAA;AAAA;AAAA,KAEM,iBAAA;EAAiB;;;EAI3B,MAAA,EAAQ,kBAAA;AAAA;AAAA,KAIE,OAAA;EAAA,SACD,EAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,EAAQ,aAAA;AAAA;AAAA,KAGP,YAAA;EAAA,SACD,EAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,SAAA,EAAW,IAAA;EAAA,SACX,gBAAA;EAAA,SACA,wBAAA;EAAA,SACA,WAAA;EAAA,SACA,gBAAA,EAAkB,uBAAA;EAAA,SAClB,OAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA;EAAA,SACA,mBAAA;EAAA,SAEA,MAAA,EAAQ,kBAAA;EAEjB,MAAA,CAAO,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,yBAAA,GAA4B,OAAA;EAC/D,MAAA,CAAO,IAAA,EAAM,YAAA,GAAe,OAAA;EAE5B,SAAA,CAAU,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,cAAA;EAAR;;;;;;;EAU/B,YAAA,CACE,IAAA,EAAM,YAAA,EACN,MAAA,EAAQ,iCAAA,GACP,OAAA;EA6BK;;;;;;;;;;EAjBR,UAAA,CACE,IAAA,EAAM,YAAA,EACN,MAAA,EAAQ,iCAAA,EACR,OAAA,EAAS,iBAAA,GACR,OAAA;EA8C6E;;;;;;;;;;EAlChF,kBAAA,CACE,IAAA,EAAM,YAAA,EACN,MAAA,EAAQ,iCAAA,GACP,OAAA;EA9DM;;;;;;;;EAwET,qBAAA,CAAsB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,kBAAA;EAjExB;;;;EAuE3B,wBAAA,CAAyB,IAAA,EAAM,YAAA,GAAe,OAAA;EAjErC;;;;;;;EA0ET,uBAAA,CAAwB,IAAA,EAAM,YAAA,EAAc,KAAA,4BAAiC,IAAA,aAAiB,OAAA;EAvE9F;;;;EA6EA,iBAAA,CAAkB,IAAA,EAAM,YAAA,EAAc,KAAA,6BAAkC,OAAA,CAAQ,MAAA;EA3EhE;;;;EAiFhB,qBAAA,CAAsB,IAAA,EAAM,YAAA,EAAc,KAAA,4BAAiC,MAAA,EAAQ,MAAA,oBAA0B,OAAA;EAE7G,uBAAA,CAAwB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,mBAAA;AAAA,IACnD,OAAA;AAAA,KAEQ,iBAAA;EAAA,SACD,GAAA,EAAK,aAAA;AAAA,IACZ,YAAA;AAAA,KAEQ,yBAAA;EACV,WAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA,GAAmB,uBAAA;EAlER;;;EAsEX,2BAAA;EACA,OAAA;EACA,WAAA;EACA,eAAA;EACA,mBAAA;EACA,MAAA,GAAS,+BAAA;AAAA;AAAA,iBAEK,+BAAA,CAAgC,OAAA,EAAS,yBAAA,GAA4B,YAAA;AAAA,KAgEzE,yBAAA,GAA4B,IAAA,CAAK,yBAAA;EAC3C,WAAA;EACA,MAAA;EACA,wBAAA;AAAA;AAAA,iBAEc,+BAAA,CAAgC,OAAA,EAAS,yBAAA,GAA4B,qBAAA"}
@@ -16,7 +16,7 @@ function adminProjectUpdateOptionsToCrud(options) {
16
16
  domain: d.domain,
17
17
  handler_path: d.handlerPath
18
18
  })),
19
- oauth_providers: options.config?.oauthProviders?.map((p) => ({
19
+ oauth_providers: options.config?.oauthProviders?.filter((p) => p.type !== "custom_oidc").map((p) => ({
20
20
  id: p.id,
21
21
  type: p.type,
22
22
  ...p.type === "standard" && {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/lib/hexclave-app/projects/index.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 { ProductionModeError } from \"@hexclave/shared/dist/helpers/production-mode\";\nimport { AdminUserProjectsCrud, ProjectsCrud } from \"@hexclave/shared/dist/interface/crud/projects\";\nimport { ProjectOnboardingStatus } from \"@hexclave/shared/dist/schema-fields\";\n\nimport { CompleteConfig, EnvironmentConfigNormalizedOverride, EnvironmentConfigOverrideOverride } from \"@hexclave/shared/dist/config/schema\";\nimport { StackAdminApp } from \"../apps/interfaces/admin-app\";\nimport { AdminProjectConfig, AdminProjectConfigUpdateOptions, ProjectConfig } from \"../project-configs\";\n\n/**\n * SDK type for pushed config source (camelCase for SDK).\n * Represents where the branch config was pushed from.\n */\nexport type PushedConfigSource =\n | { type: \"pushed-from-github\", owner: string, repo: string, branch: string, commitHash: string, configFilePath: string, workflowPath?: string }\n | { type: \"pushed-from-unknown\" }\n | { type: \"unlinked\" };\n\nexport type PushConfigOptions = {\n /**\n * The source of this config push.\n */\n source: PushedConfigSource,\n};\n\n\nexport type Project = {\n readonly id: string,\n readonly displayName: string,\n readonly config: ProjectConfig,\n};\n\nexport type AdminProject = {\n readonly id: string,\n readonly displayName: string,\n readonly description: string | null,\n readonly createdAt: Date,\n readonly isProductionMode: boolean,\n readonly isDevelopmentEnvironment: boolean,\n readonly ownerTeamId: string | null,\n readonly onboardingStatus: ProjectOnboardingStatus,\n readonly logoUrl: string | null | undefined,\n readonly logoFullUrl: string | null | undefined,\n readonly logoDarkModeUrl: string | null | undefined,\n readonly logoFullDarkModeUrl: string | null | undefined,\n\n readonly config: AdminProjectConfig,\n\n update(this: AdminProject, update: AdminProjectUpdateOptions): Promise<void>,\n delete(this: AdminProject): Promise<void>,\n\n getConfig(this: AdminProject): Promise<CompleteConfig>,\n\n /**\n * Updates the environment's config by merging the provided config into the existing config.\n *\n * Changes made with `updateConfig` always take precedence over those made with `pushConfig`, even if the `pushConfig`\n * config was pushed after the changes were made with `updateConfig`. This is best for environment-specific\n * configuration like secrets, API keys, and other values that you wouldn't push into a source repository.\n */\n // We have some strict types here in order to prevent accidental overwriting of a top-level property of a config object\n updateConfig(\n this: AdminProject,\n config: EnvironmentConfigOverrideOverride,\n ): Promise<void>,\n\n /**\n * Pushes a config, replacing any previous config pushed with `pushConfig`.\n *\n * **Note:** This function does **not** replace any changes made with `updateConfig`. Changes made with\n * `updateConfig` always take precedence over those made with `pushConfig`, even if the `pushConfig`\n * config was pushed after the changes were made with `updateConfig`.\n *\n * This is useful for programmatically deploying configuration. More often than not, you'll want to use\n * `updateConfig` instead.\n */\n pushConfig(\n this: AdminProject,\n config: EnvironmentConfigOverrideOverride,\n options: PushConfigOptions,\n ): Promise<void>,\n\n /**\n * Updates the pushed config by merging the provided config into the existing pushed config.\n *\n * **Warning:** This is almost always **not** the function you want to call. Changes made with\n * `updatePushedConfig` will be replaced entirely the next time `pushConfig` is called. Consider using\n * `pushConfig` to set the full pushed config, or `updateConfig` for environment-specific values that\n * should persist across pushes.\n *\n * This function is useful for making temporary modifications to the pushed config before the next push.\n */\n updatePushedConfig(\n this: AdminProject,\n config: EnvironmentConfigOverrideOverride\n ): Promise<void>,\n\n /**\n * Gets the source metadata for the pushed config, indicating where it was pushed from.\n *\n * The source can be:\n * - `pushed-from-github`: Config was pushed from a GitHub repository\n * - `pushed-from-unknown`: Config was pushed via CLI but source details unknown\n * - `unlinked`: Config can be edited directly on the dashboard\n */\n getPushedConfigSource(this: AdminProject): Promise<PushedConfigSource>,\n\n /**\n * Unlinks the pushed config source, setting it to \"unlinked\".\n * This allows the config to be edited directly on the dashboard without external push restrictions.\n */\n unlinkPushedConfigSource(this: AdminProject): Promise<void>,\n\n /**\n * Resets (removes) specific keys from the config override at the specified level.\n * Uses the same nested key logic as the override algorithm: resetting key \"a.b\" also resets \"a.b.c\".\n *\n * This is useful when updating the pushed config (branch level) and wanting to remove the same keys\n * from the environment config override so that the branch config values take precedence.\n */\n resetConfigOverrideKeys(this: AdminProject, level: \"branch\" | \"environment\", keys: string[]): Promise<void>,\n\n /**\n * Gets the raw config override at the specified level (before merging/defaults).\n * Useful for inspecting exactly what's been set at each level.\n */\n getConfigOverride(this: AdminProject, level: \"branch\" | \"environment\"): Promise<Record<string, unknown>>,\n\n /**\n * Replaces the entire config override at the specified level.\n * For branch level, preserves the existing source metadata.\n */\n replaceConfigOverride(this: AdminProject, level: \"branch\" | \"environment\", config: Record<string, unknown>): Promise<void>,\n\n getProductionModeErrors(this: AdminProject): Promise<ProductionModeError[]>,\n} & Project;\n\nexport type AdminOwnedProject = {\n readonly app: StackAdminApp<false>,\n} & AdminProject;\n\nexport type AdminProjectUpdateOptions = {\n displayName?: string,\n description?: string,\n isProductionMode?: boolean,\n onboardingStatus?: ProjectOnboardingStatus,\n /**\n * Updates `project.requirePublishableClientKey` in the project-level config override.\n */\n requirePublishableClientKey?: boolean,\n logoUrl?: string | null,\n logoFullUrl?: string | null,\n logoDarkModeUrl?: string | null,\n logoFullDarkModeUrl?: string | null,\n config?: AdminProjectConfigUpdateOptions,\n};\nexport function adminProjectUpdateOptionsToCrud(options: AdminProjectUpdateOptions): ProjectsCrud[\"Admin\"][\"Update\"] {\n return {\n display_name: options.displayName,\n description: options.description,\n is_production_mode: options.isProductionMode,\n onboarding_status: options.onboardingStatus,\n logo_url: options.logoUrl,\n logo_full_url: options.logoFullUrl,\n logo_dark_mode_url: options.logoDarkModeUrl,\n logo_full_dark_mode_url: options.logoFullDarkModeUrl,\n /**\n * NOTE: Do not update this config anymore. It's been superseded by the new config in schema.ts.\n * @deprecated\n */\n config: {\n domains: options.config?.domains?.map((d) => ({\n domain: d.domain,\n handler_path: d.handlerPath\n })),\n oauth_providers: options.config?.oauthProviders?.map((p) => ({\n id: p.id as any,\n type: p.type,\n ...(p.type === 'standard' && {\n client_id: p.clientId,\n client_secret: p.clientSecret,\n facebook_config_id: p.facebookConfigId,\n microsoft_tenant_id: p.microsoftTenantId,\n apple_bundle_ids: p.appleBundleIds,\n }),\n })),\n email_config: options.config?.emailConfig && (\n options.config.emailConfig.type === 'shared' ? {\n type: 'shared',\n } : {\n type: 'standard',\n host: options.config.emailConfig.host,\n port: options.config.emailConfig.port,\n username: options.config.emailConfig.username,\n password: options.config.emailConfig.password,\n sender_name: options.config.emailConfig.senderName,\n sender_email: options.config.emailConfig.senderEmail,\n }\n ),\n email_theme: options.config?.emailTheme,\n sign_up_enabled: options.config?.signUpEnabled,\n credential_enabled: options.config?.credentialEnabled,\n magic_link_enabled: options.config?.magicLinkEnabled,\n passkey_enabled: options.config?.passkeyEnabled,\n allow_localhost: options.config?.allowLocalhost,\n create_team_on_sign_up: options.config?.createTeamOnSignUp,\n client_team_creation_enabled: options.config?.clientTeamCreationEnabled,\n client_user_deletion_enabled: options.config?.clientUserDeletionEnabled,\n team_creator_default_permissions: options.config?.teamCreatorDefaultPermissions,\n team_member_default_permissions: options.config?.teamMemberDefaultPermissions,\n user_default_permissions: options.config?.userDefaultPermissions,\n oauth_account_merge_strategy: options.config?.oauthAccountMergeStrategy,\n allow_user_api_keys: options.config?.allowUserApiKeys,\n allow_team_api_keys: options.config?.allowTeamApiKeys,\n },\n };\n}\n\nexport type AdminProjectCreateOptions = Omit<AdminProjectUpdateOptions, 'displayName'> & {\n displayName: string,\n teamId: string,\n isDevelopmentEnvironment?: boolean,\n};\nexport function adminProjectCreateOptionsToCrud(options: AdminProjectCreateOptions): AdminUserProjectsCrud[\"Server\"][\"Create\"] {\n return {\n ...adminProjectUpdateOptionsToCrud(options),\n display_name: options.displayName,\n is_development_environment: options.isDevelopmentEnvironment,\n owner_team_id: options.teamId,\n };\n}\n"],"mappings":";;;AA+JA,SAAgB,gCAAgC,SAAqE;AACnH,QAAO;EACL,cAAc,QAAQ;EACtB,aAAa,QAAQ;EACrB,oBAAoB,QAAQ;EAC5B,mBAAmB,QAAQ;EAC3B,UAAU,QAAQ;EAClB,eAAe,QAAQ;EACvB,oBAAoB,QAAQ;EAC5B,yBAAyB,QAAQ;EAKjC,QAAQ;GACN,SAAS,QAAQ,QAAQ,SAAS,KAAK,OAAO;IAC5C,QAAQ,EAAE;IACV,cAAc,EAAE;IACjB,EAAE;GACH,iBAAiB,QAAQ,QAAQ,gBAAgB,KAAK,OAAO;IAC3D,IAAI,EAAE;IACN,MAAM,EAAE;IACR,GAAI,EAAE,SAAS,cAAc;KAC3B,WAAW,EAAE;KACb,eAAe,EAAE;KACjB,oBAAoB,EAAE;KACtB,qBAAqB,EAAE;KACvB,kBAAkB,EAAE;KACrB;IACF,EAAE;GACH,cAAc,QAAQ,QAAQ,gBAC5B,QAAQ,OAAO,YAAY,SAAS,WAAW,EAC7C,MAAM,UACP,GAAG;IACF,MAAM;IACN,MAAM,QAAQ,OAAO,YAAY;IACjC,MAAM,QAAQ,OAAO,YAAY;IACjC,UAAU,QAAQ,OAAO,YAAY;IACrC,UAAU,QAAQ,OAAO,YAAY;IACrC,aAAa,QAAQ,OAAO,YAAY;IACxC,cAAc,QAAQ,OAAO,YAAY;IAC1C;GAEH,aAAa,QAAQ,QAAQ;GAC7B,iBAAiB,QAAQ,QAAQ;GACjC,oBAAoB,QAAQ,QAAQ;GACpC,oBAAoB,QAAQ,QAAQ;GACpC,iBAAiB,QAAQ,QAAQ;GACjC,iBAAiB,QAAQ,QAAQ;GACjC,wBAAwB,QAAQ,QAAQ;GACxC,8BAA8B,QAAQ,QAAQ;GAC9C,8BAA8B,QAAQ,QAAQ;GAC9C,kCAAkC,QAAQ,QAAQ;GAClD,iCAAiC,QAAQ,QAAQ;GACjD,0BAA0B,QAAQ,QAAQ;GAC1C,8BAA8B,QAAQ,QAAQ;GAC9C,qBAAqB,QAAQ,QAAQ;GACrC,qBAAqB,QAAQ,QAAQ;GACtC;EACF;;AAQH,SAAgB,gCAAgC,SAA+E;AAC7H,QAAO;EACL,GAAG,gCAAgC,QAAQ;EAC3C,cAAc,QAAQ;EACtB,4BAA4B,QAAQ;EACpC,eAAe,QAAQ;EACxB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/lib/hexclave-app/projects/index.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 { ProductionModeError } from \"@hexclave/shared/dist/helpers/production-mode\";\nimport { AdminUserProjectsCrud, ProjectsCrud } from \"@hexclave/shared/dist/interface/crud/projects\";\nimport { ProjectOnboardingStatus } from \"@hexclave/shared/dist/schema-fields\";\n\nimport { CompleteConfig, EnvironmentConfigNormalizedOverride, EnvironmentConfigOverrideOverride } from \"@hexclave/shared/dist/config/schema\";\nimport { StackAdminApp } from \"../apps/interfaces/admin-app\";\nimport { AdminProjectConfig, AdminProjectConfigUpdateOptions, ProjectConfig } from \"../project-configs\";\n\n/**\n * SDK type for pushed config source (camelCase for SDK).\n * Represents where the branch config was pushed from.\n */\nexport type PushedConfigSource =\n | { type: \"pushed-from-github\", owner: string, repo: string, branch: string, commitHash: string, configFilePath: string, workflowPath?: string }\n | { type: \"pushed-from-unknown\" }\n | { type: \"unlinked\" };\n\nexport type PushConfigOptions = {\n /**\n * The source of this config push.\n */\n source: PushedConfigSource,\n};\n\n\nexport type Project = {\n readonly id: string,\n readonly displayName: string,\n readonly config: ProjectConfig,\n};\n\nexport type AdminProject = {\n readonly id: string,\n readonly displayName: string,\n readonly description: string | null,\n readonly createdAt: Date,\n readonly isProductionMode: boolean,\n readonly isDevelopmentEnvironment: boolean,\n readonly ownerTeamId: string | null,\n readonly onboardingStatus: ProjectOnboardingStatus,\n readonly logoUrl: string | null | undefined,\n readonly logoFullUrl: string | null | undefined,\n readonly logoDarkModeUrl: string | null | undefined,\n readonly logoFullDarkModeUrl: string | null | undefined,\n\n readonly config: AdminProjectConfig,\n\n update(this: AdminProject, update: AdminProjectUpdateOptions): Promise<void>,\n delete(this: AdminProject): Promise<void>,\n\n getConfig(this: AdminProject): Promise<CompleteConfig>,\n\n /**\n * Updates the environment's config by merging the provided config into the existing config.\n *\n * Changes made with `updateConfig` always take precedence over those made with `pushConfig`, even if the `pushConfig`\n * config was pushed after the changes were made with `updateConfig`. This is best for environment-specific\n * configuration like secrets, API keys, and other values that you wouldn't push into a source repository.\n */\n // We have some strict types here in order to prevent accidental overwriting of a top-level property of a config object\n updateConfig(\n this: AdminProject,\n config: EnvironmentConfigOverrideOverride,\n ): Promise<void>,\n\n /**\n * Pushes a config, replacing any previous config pushed with `pushConfig`.\n *\n * **Note:** This function does **not** replace any changes made with `updateConfig`. Changes made with\n * `updateConfig` always take precedence over those made with `pushConfig`, even if the `pushConfig`\n * config was pushed after the changes were made with `updateConfig`.\n *\n * This is useful for programmatically deploying configuration. More often than not, you'll want to use\n * `updateConfig` instead.\n */\n pushConfig(\n this: AdminProject,\n config: EnvironmentConfigOverrideOverride,\n options: PushConfigOptions,\n ): Promise<void>,\n\n /**\n * Updates the pushed config by merging the provided config into the existing pushed config.\n *\n * **Warning:** This is almost always **not** the function you want to call. Changes made with\n * `updatePushedConfig` will be replaced entirely the next time `pushConfig` is called. Consider using\n * `pushConfig` to set the full pushed config, or `updateConfig` for environment-specific values that\n * should persist across pushes.\n *\n * This function is useful for making temporary modifications to the pushed config before the next push.\n */\n updatePushedConfig(\n this: AdminProject,\n config: EnvironmentConfigOverrideOverride\n ): Promise<void>,\n\n /**\n * Gets the source metadata for the pushed config, indicating where it was pushed from.\n *\n * The source can be:\n * - `pushed-from-github`: Config was pushed from a GitHub repository\n * - `pushed-from-unknown`: Config was pushed via CLI but source details unknown\n * - `unlinked`: Config can be edited directly on the dashboard\n */\n getPushedConfigSource(this: AdminProject): Promise<PushedConfigSource>,\n\n /**\n * Unlinks the pushed config source, setting it to \"unlinked\".\n * This allows the config to be edited directly on the dashboard without external push restrictions.\n */\n unlinkPushedConfigSource(this: AdminProject): Promise<void>,\n\n /**\n * Resets (removes) specific keys from the config override at the specified level.\n * Uses the same nested key logic as the override algorithm: resetting key \"a.b\" also resets \"a.b.c\".\n *\n * This is useful when updating the pushed config (branch level) and wanting to remove the same keys\n * from the environment config override so that the branch config values take precedence.\n */\n resetConfigOverrideKeys(this: AdminProject, level: \"branch\" | \"environment\", keys: string[]): Promise<void>,\n\n /**\n * Gets the raw config override at the specified level (before merging/defaults).\n * Useful for inspecting exactly what's been set at each level.\n */\n getConfigOverride(this: AdminProject, level: \"branch\" | \"environment\"): Promise<Record<string, unknown>>,\n\n /**\n * Replaces the entire config override at the specified level.\n * For branch level, preserves the existing source metadata.\n */\n replaceConfigOverride(this: AdminProject, level: \"branch\" | \"environment\", config: Record<string, unknown>): Promise<void>,\n\n getProductionModeErrors(this: AdminProject): Promise<ProductionModeError[]>,\n} & Project;\n\nexport type AdminOwnedProject = {\n readonly app: StackAdminApp<false>,\n} & AdminProject;\n\nexport type AdminProjectUpdateOptions = {\n displayName?: string,\n description?: string,\n isProductionMode?: boolean,\n onboardingStatus?: ProjectOnboardingStatus,\n /**\n * Updates `project.requirePublishableClientKey` in the project-level config override.\n */\n requirePublishableClientKey?: boolean,\n logoUrl?: string | null,\n logoFullUrl?: string | null,\n logoDarkModeUrl?: string | null,\n logoFullDarkModeUrl?: string | null,\n config?: AdminProjectConfigUpdateOptions,\n};\nexport function adminProjectUpdateOptionsToCrud(options: AdminProjectUpdateOptions): ProjectsCrud[\"Admin\"][\"Update\"] {\n return {\n display_name: options.displayName,\n description: options.description,\n is_production_mode: options.isProductionMode,\n onboarding_status: options.onboardingStatus,\n logo_url: options.logoUrl,\n logo_full_url: options.logoFullUrl,\n logo_dark_mode_url: options.logoDarkModeUrl,\n logo_full_dark_mode_url: options.logoFullDarkModeUrl,\n /**\n * NOTE: Do not update this config anymore. It's been superseded by the new config in schema.ts.\n * @deprecated\n */\n config: {\n domains: options.config?.domains?.map((d) => ({\n domain: d.domain,\n handler_path: d.handlerPath\n })),\n oauth_providers: options.config?.oauthProviders\n ?.filter((p): p is Exclude<typeof p, { type: 'custom_oidc' }> => p.type !== 'custom_oidc')\n .map((p) => ({\n id: p.id as any,\n type: p.type,\n ...(p.type === 'standard' && {\n client_id: p.clientId,\n client_secret: p.clientSecret,\n facebook_config_id: p.facebookConfigId,\n microsoft_tenant_id: p.microsoftTenantId,\n apple_bundle_ids: p.appleBundleIds,\n }),\n })),\n email_config: options.config?.emailConfig && (\n options.config.emailConfig.type === 'shared' ? {\n type: 'shared',\n } : {\n type: 'standard',\n host: options.config.emailConfig.host,\n port: options.config.emailConfig.port,\n username: options.config.emailConfig.username,\n password: options.config.emailConfig.password,\n sender_name: options.config.emailConfig.senderName,\n sender_email: options.config.emailConfig.senderEmail,\n }\n ),\n email_theme: options.config?.emailTheme,\n sign_up_enabled: options.config?.signUpEnabled,\n credential_enabled: options.config?.credentialEnabled,\n magic_link_enabled: options.config?.magicLinkEnabled,\n passkey_enabled: options.config?.passkeyEnabled,\n allow_localhost: options.config?.allowLocalhost,\n create_team_on_sign_up: options.config?.createTeamOnSignUp,\n client_team_creation_enabled: options.config?.clientTeamCreationEnabled,\n client_user_deletion_enabled: options.config?.clientUserDeletionEnabled,\n team_creator_default_permissions: options.config?.teamCreatorDefaultPermissions,\n team_member_default_permissions: options.config?.teamMemberDefaultPermissions,\n user_default_permissions: options.config?.userDefaultPermissions,\n oauth_account_merge_strategy: options.config?.oauthAccountMergeStrategy,\n allow_user_api_keys: options.config?.allowUserApiKeys,\n allow_team_api_keys: options.config?.allowTeamApiKeys,\n },\n };\n}\n\nexport type AdminProjectCreateOptions = Omit<AdminProjectUpdateOptions, 'displayName'> & {\n displayName: string,\n teamId: string,\n isDevelopmentEnvironment?: boolean,\n};\nexport function adminProjectCreateOptionsToCrud(options: AdminProjectCreateOptions): AdminUserProjectsCrud[\"Server\"][\"Create\"] {\n return {\n ...adminProjectUpdateOptionsToCrud(options),\n display_name: options.displayName,\n is_development_environment: options.isDevelopmentEnvironment,\n owner_team_id: options.teamId,\n };\n}\n"],"mappings":";;;AA+JA,SAAgB,gCAAgC,SAAqE;AACnH,QAAO;EACL,cAAc,QAAQ;EACtB,aAAa,QAAQ;EACrB,oBAAoB,QAAQ;EAC5B,mBAAmB,QAAQ;EAC3B,UAAU,QAAQ;EAClB,eAAe,QAAQ;EACvB,oBAAoB,QAAQ;EAC5B,yBAAyB,QAAQ;EAKjC,QAAQ;GACN,SAAS,QAAQ,QAAQ,SAAS,KAAK,OAAO;IAC5C,QAAQ,EAAE;IACV,cAAc,EAAE;IACjB,EAAE;GACH,iBAAiB,QAAQ,QAAQ,gBAC7B,QAAQ,MAAuD,EAAE,SAAS,cAAc,CACzF,KAAK,OAAO;IACX,IAAI,EAAE;IACN,MAAM,EAAE;IACR,GAAI,EAAE,SAAS,cAAc;KAC3B,WAAW,EAAE;KACb,eAAe,EAAE;KACjB,oBAAoB,EAAE;KACtB,qBAAqB,EAAE;KACvB,kBAAkB,EAAE;KACrB;IACF,EAAE;GACL,cAAc,QAAQ,QAAQ,gBAC5B,QAAQ,OAAO,YAAY,SAAS,WAAW,EAC7C,MAAM,UACP,GAAG;IACF,MAAM;IACN,MAAM,QAAQ,OAAO,YAAY;IACjC,MAAM,QAAQ,OAAO,YAAY;IACjC,UAAU,QAAQ,OAAO,YAAY;IACrC,UAAU,QAAQ,OAAO,YAAY;IACrC,aAAa,QAAQ,OAAO,YAAY;IACxC,cAAc,QAAQ,OAAO,YAAY;IAC1C;GAEH,aAAa,QAAQ,QAAQ;GAC7B,iBAAiB,QAAQ,QAAQ;GACjC,oBAAoB,QAAQ,QAAQ;GACpC,oBAAoB,QAAQ,QAAQ;GACpC,iBAAiB,QAAQ,QAAQ;GACjC,iBAAiB,QAAQ,QAAQ;GACjC,wBAAwB,QAAQ,QAAQ;GACxC,8BAA8B,QAAQ,QAAQ;GAC9C,8BAA8B,QAAQ,QAAQ;GAC9C,kCAAkC,QAAQ,QAAQ;GAClD,iCAAiC,QAAQ,QAAQ;GACjD,0BAA0B,QAAQ,QAAQ;GAC1C,8BAA8B,QAAQ,QAAQ;GAC9C,qBAAqB,QAAQ,QAAQ;GACrC,qBAAqB,QAAQ,QAAQ;GACtC;EACF;;AAQH,SAAgB,gCAAgC,SAA+E;AAC7H,QAAO;EACL,GAAG,gCAAgC,QAAQ;EAC3C,cAAc,QAAQ;EACtB,4BAA4B,QAAQ;EACpC,eAAe,QAAQ;EACxB"}
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/js",
4
- "version": "1.0.19",
4
+ "version": "1.0.21",
5
5
  "repository": "https://github.com/hexclave/hexclave",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -66,7 +66,7 @@
66
66
  "rrweb": "^1.1.3",
67
67
  "tsx": "^4.21.0",
68
68
  "yup": "^1.7.1",
69
- "@hexclave/shared": "1.0.19"
69
+ "@hexclave/shared": "1.0.21"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@quetzallabs/i18n": "^0.1.19",
@@ -273,6 +273,8 @@ describe("StackClientApp cross-domain auth", () => {
273
273
  protocol: "https:",
274
274
  hostname: "demo.stack-auth.com",
275
275
  },
276
+ addEventListener: () => {},
277
+ removeEventListener: () => {},
276
278
  } as any;
277
279
 
278
280
  const clientApp = new StackClientApp({
@@ -460,6 +462,40 @@ describe("StackClientApp cross-domain auth", () => {
460
462
  }
461
463
  });
462
464
 
465
+ it("throws when public app.urls reads would return hosted component URLs", () => {
466
+ const clientApp = new StackClientApp({
467
+ baseUrl: "http://localhost:12345",
468
+ projectId: "00000000-0000-4000-8000-000000000003",
469
+ publishableClientKey: "stack-pk-test",
470
+ tokenStore: "memory",
471
+ redirectMethod: "window",
472
+ urls: {
473
+ default: { type: "hosted" },
474
+ },
475
+ noAutomaticPrefetch: true,
476
+ });
477
+
478
+ expect(() => clientApp.urls.signIn).toThrowError(/app\.urls\.signIn cannot be used when this app is configured to use hosted components.*Use app\.redirectToSignIn\(\) instead/s);
479
+ expect(() => clientApp.urls.signOut).toThrowError(/app\.urls\.signOut cannot be used when this app is configured to use hosted components.*Use app\.redirectToSignOut\(\) instead/s);
480
+ expect(clientApp.urls.afterSignIn).toBe("/");
481
+ });
482
+
483
+ it("keeps public app.urls reads available for non-hosted targets", () => {
484
+ const clientApp = new StackClientApp({
485
+ baseUrl: "http://localhost:12345",
486
+ projectId: "00000000-0000-4000-8000-000000000003",
487
+ publishableClientKey: "stack-pk-test",
488
+ tokenStore: "memory",
489
+ redirectMethod: "window",
490
+ urls: {
491
+ handler: "/custom-handler",
492
+ },
493
+ noAutomaticPrefetch: true,
494
+ });
495
+
496
+ expect(clientApp.urls.signIn).toBe("/custom-handler/sign-in");
497
+ });
498
+
463
499
  it("keeps default hosted signOut() on the source domain when afterSignOut is not configured", async () => {
464
500
  const clientApp = new StackClientApp({
465
501
  baseUrl: "http://localhost:12345",
@@ -84,6 +84,38 @@ const nestedCrossDomainAuthQueryParams = {
84
84
  afterCallbackRedirectUrl: "after_callback_redirect_url",
85
85
  } as const;
86
86
 
87
+ function getRedirectHelperInstruction(handlerName: string): string {
88
+ if (handlerName === "handler") {
89
+ return "Use a page-specific redirect helper such as app.redirectToSignIn() instead.";
90
+ }
91
+ const redirectMethodName = `redirectTo${handlerName.slice(0, 1).toUpperCase()}${handlerName.slice(1)}`;
92
+ return `Use app.${redirectMethodName}() instead.`;
93
+ }
94
+
95
+ function createUrlsForPublicAccess(options: {
96
+ urls: ResolvedHandlerUrls,
97
+ projectId: string,
98
+ }): Readonly<ResolvedHandlerUrls> {
99
+ const hostedUrlNames = new Set(
100
+ Object.entries(options.urls)
101
+ .filter(([, url]) => isHostedHandlerUrlForProject({ url, projectId: options.projectId }))
102
+ .map(([handlerName]) => handlerName),
103
+ );
104
+
105
+ return new Proxy(options.urls, {
106
+ get(target, property, receiver) {
107
+ if (typeof property === "string" && hostedUrlNames.has(property)) {
108
+ throw new Error(
109
+ `app.urls.${property} cannot be used when this app is configured to use hosted components. ` +
110
+ "`app.urls` is static and does not include the runtime redirect-back, cross-domain auth, or sign-out state required by hosted components. " +
111
+ getRedirectHelperInstruction(property),
112
+ );
113
+ }
114
+ return Reflect.get(target, property, receiver);
115
+ },
116
+ });
117
+ }
118
+
87
119
  const oauthCallbackResponseQueryParams = ["code", "state", "error", "error_description", "errorCode", "message", "details"] as const;
88
120
 
89
121
  const allClientApps = new Map<string, [checkString: string | undefined, app: StackClientApp<any, any>]>();
@@ -1630,7 +1662,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1630
1662
  teamId: crud.id,
1631
1663
  email: options.email,
1632
1664
  session,
1633
- callbackUrl: options.callbackUrl ?? constructRedirectUrl(app.urls.teamInvitation, "callbackUrl"),
1665
+ callbackUrl: options.callbackUrl ?? constructRedirectUrl(app._getUrls().teamInvitation, "callbackUrl"),
1634
1666
  });
1635
1667
  await app._teamInvitationsCache.refresh([session, crud.id]);
1636
1668
  },
@@ -1682,7 +1714,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1682
1714
  async sendVerificationEmail(options?: { callbackUrl?: string }) {
1683
1715
  await app._interface.sendCurrentUserContactChannelVerificationEmail(
1684
1716
  crud.id,
1685
- options?.callbackUrl || constructRedirectUrl(app.urls.emailVerification, "callbackUrl"),
1717
+ options?.callbackUrl || constructRedirectUrl(app._getUrls().emailVerification, "callbackUrl"),
1686
1718
  session
1687
1719
  );
1688
1720
  },
@@ -1959,7 +1991,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1959
1991
  {
1960
1992
  provider,
1961
1993
  redirectUrl: app._getOAuthCallbackRedirectUri(),
1962
- errorRedirectUrl: app.urls.error,
1994
+ errorRedirectUrl: app._getUrls().error,
1963
1995
  providerScope: mergeScopeStrings(scopeString, (app._oauthScopesOnSignIn[provider as ProviderType] ?? []).join(" ")),
1964
1996
  },
1965
1997
  session,
@@ -2046,7 +2078,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
2046
2078
  }
2047
2079
  return await app._interface.sendVerificationEmail(
2048
2080
  crud.primary_email,
2049
- options?.callbackUrl ?? constructRedirectUrl(app.urls.emailVerification, "callbackUrl"),
2081
+ options?.callbackUrl ?? constructRedirectUrl(app._getUrls().emailVerification, "callbackUrl"),
2050
2082
  session
2051
2083
  );
2052
2084
  },
@@ -2437,6 +2469,13 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
2437
2469
  }
2438
2470
 
2439
2471
  get urls(): Readonly<ResolvedHandlerUrls> {
2472
+ return createUrlsForPublicAccess({
2473
+ urls: this._getUrls(),
2474
+ projectId: this.projectId,
2475
+ });
2476
+ }
2477
+
2478
+ protected _getUrls(): Readonly<ResolvedHandlerUrls> {
2440
2479
  return getUrls(this._urlOptions, { projectId: this.projectId });
2441
2480
  }
2442
2481
 
@@ -4,6 +4,7 @@
4
4
  //===========================================
5
5
  // @vitest-environment jsdom
6
6
 
7
+ import { KnownErrors } from "@hexclave/shared/dist/known-errors";
7
8
  import { Result } from "@hexclave/shared/dist/utils/results";
8
9
  import { afterEach, describe, expect, it, vi } from "vitest";
9
10
  import { EventTracker } from "./event-tracker";
@@ -390,7 +391,7 @@ describe("EventTracker", () => {
390
391
  }
391
392
  });
392
393
 
393
- it("silently disables when server responds with ANALYTICS_NOT_ENABLED", async () => {
394
+ it("silently disables when client interface returns ANALYTICS_NOT_ENABLED as an error", async () => {
394
395
  vi.useFakeTimers();
395
396
  document.body.innerHTML = "<button>Click me</button>";
396
397
 
@@ -400,28 +401,19 @@ describe("EventTracker", () => {
400
401
  projectId: "internal",
401
402
  sendBatch: async (body) => {
402
403
  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
- ));
404
+ return Result.error(new KnownErrors.AnalyticsNotEnabled());
410
405
  },
411
406
  });
412
407
 
413
408
  try {
414
409
  tracker.start();
415
410
 
416
- // First flush sends the initial page-view event; server rejects it.
417
411
  await advancePastFlush();
418
412
  expect(sentBodies).toHaveLength(1);
419
-
420
- // No console.warn should have been emitted.
421
413
  expect(warnSpy).not.toHaveBeenCalled();
414
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
415
+ expect((tracker as any)._flushTimer).toBeNull();
422
416
 
423
- // After disabling, new events should not accumulate or trigger further
424
- // flushes.
425
417
  document.querySelector("button")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
426
418
  await advancePastFlush();
427
419
  expect(sentBodies).toHaveLength(1);