@hexclave/next 1.0.9 → 1.0.11

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 (47) hide show
  1. package/dist/config.d.ts +2 -0
  2. package/dist/config.js +22 -0
  3. package/dist/esm/config.d.ts +2 -0
  4. package/dist/esm/config.js +3 -0
  5. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  6. package/dist/esm/index.js +1 -1
  7. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +9 -1
  8. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  9. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +23 -6
  10. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  11. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  12. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +3 -2
  13. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  14. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  15. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  16. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +2 -1
  17. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  18. package/dist/esm/lib/hexclave-app/url-targets.d.ts.map +1 -1
  19. package/dist/esm/lib/hexclave-app/url-targets.js +1 -0
  20. package/dist/esm/lib/hexclave-app/url-targets.js.map +1 -1
  21. package/dist/esm/lib/hexclave-app/url-targets.test.js +1 -0
  22. package/dist/esm/lib/hexclave-app/url-targets.test.js.map +1 -1
  23. package/dist/generated/quetzal-translations.d.ts +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +9 -1
  26. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  27. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +23 -6
  28. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  29. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  30. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +3 -2
  31. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  32. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  33. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  34. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +2 -1
  35. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  36. package/dist/lib/hexclave-app/url-targets.d.ts.map +1 -1
  37. package/dist/lib/hexclave-app/url-targets.js +1 -0
  38. package/dist/lib/hexclave-app/url-targets.js.map +1 -1
  39. package/dist/lib/hexclave-app/url-targets.test.js +1 -0
  40. package/dist/lib/hexclave-app/url-targets.test.js.map +1 -1
  41. package/package.json +13 -4
  42. package/src/config.ts +17 -0
  43. package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +21 -6
  44. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +8 -4
  45. package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +1 -0
  46. package/src/lib/hexclave-app/url-targets.test.ts +1 -0
  47. package/src/lib/hexclave-app/url-targets.ts +1 -0
@@ -17,7 +17,7 @@ let ____________generated_env_js = require("../../../../generated/env.js");
17
17
  let ______url_targets_js = require("../../url-targets.js");
18
18
 
19
19
  //#region src/lib/hexclave-app/apps/implementations/common.ts
20
- const clientVersion = "js @hexclave/next@1.0.9";
20
+ const clientVersion = "js @hexclave/next@1.0.11";
21
21
  if (clientVersion.startsWith("STACK_COMPILE_TIME")) throw new _hexclave_shared_dist_utils_errors.HexclaveAssertionError("Client version was not replaced. Something went wrong during build!");
22
22
  const replaceHexclavePortPrefix = (input) => {
23
23
  if (!input) return input;
@@ -1 +1 @@
1
- {"version":3,"file":"event-tracker.d.ts","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/event-tracker.ts"],"mappings":";;;KAiCY,gBAAA;EACV,SAAA;EACA,SAAA,GAAY,IAAA,UAAc,OAAA;IAAW,SAAA;EAAA,MAAyB,OAAA,CAAQ,MAAA,CAAO,QAAA,EAAU,KAAA;AAAA;AAAA,cAS5E,YAAA;EAAA,QACH,QAAA;EAAA,QACA,UAAA;EAAA,QACA,gBAAA;EAAA,QACA,WAAA;EAAA,QACA,OAAA;EAAA,QACA,YAAA;EAAA,QACA,QAAA;EAAA,iBACS,uBAAA;EAAA,iBACA,KAAA;EAAA,QAET,kBAAA;EAAA,QACA,qBAAA;cAEI,IAAA,EAAM,gBAAA;EAKlB,KAAA,CAAA;EAqBA,IAAA,CAAA;EAUA,WAAA,CAAA;EAAA,QAKQ,UAAA;EAAA,QAQA,gBAAA;EAAA,QA2BA,qBAAA;EAAA,iBA4BS,WAAA;EAAA,QAIT,cAAA;EAAA,QA0BA,sBAAA;EAAA,iBAWS,eAAA;EAAA,QAsBT,kBAAA;EAAA,iBAIS,WAAA;EAAA,QAIT,uBAAA;EAAA,QASA,SAAA;EAAA,QA0BM,MAAA;EAAA,QA+BN,KAAA;AAAA"}
1
+ {"version":3,"file":"event-tracker.d.ts","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/event-tracker.ts"],"mappings":";;;KAiCY,gBAAA;EACV,SAAA;EACA,SAAA,GAAY,IAAA,UAAc,OAAA;IAAW,SAAA;EAAA,MAAyB,OAAA,CAAQ,MAAA,CAAO,QAAA,EAAU,KAAA;AAAA;AAAA,cAS5E,YAAA;EAAA,QACH,QAAA;EAAA,QACA,UAAA;EAAA,QACA,gBAAA;EAAA,QACA,WAAA;EAAA,QACA,OAAA;EAAA,QACA,YAAA;EAAA,QACA,QAAA;EAAA,iBACS,uBAAA;EAAA,iBACA,KAAA;EAAA,QAET,kBAAA;EAAA,QACA,qBAAA;cAEI,IAAA,EAAM,gBAAA;EAKlB,KAAA,CAAA;EAqBA,IAAA,CAAA;EAUA,WAAA,CAAA;EAAA,QAKQ,UAAA;EAAA,QAQA,gBAAA;EAAA,QA4BA,qBAAA;EAAA,iBA4BS,WAAA;EAAA,QAIT,cAAA;EAAA,QA0BA,sBAAA;EAAA,iBAWS,eAAA;EAAA,QAsBT,kBAAA;EAAA,iBAIS,WAAA;EAAA,QAIT,uBAAA;EAAA,QASA,SAAA;EAAA,QA0BM,MAAA;EAAA,QA+BN,KAAA;AAAA"}
@@ -104,7 +104,8 @@ var EventTracker = class {
104
104
  viewport_width: window.innerWidth,
105
105
  viewport_height: window.innerHeight,
106
106
  screen_width: screenObject.width,
107
- screen_height: screenObject.height
107
+ screen_height: screenObject.height,
108
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : null
108
109
  }
109
110
  });
110
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"event-tracker.js","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/event-tracker.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 { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { generateUuid } from \"./session-replay\";\n\nconst FLUSH_INTERVAL_MS = 10_000;\nconst MAX_EVENTS_PER_BATCH = 50;\nconst MAX_APPROX_BYTES_PER_BATCH = 64_000;\n\nfunction hasScreenDimensions(value: unknown): value is { width: number, height: number } {\n if (value == null || typeof value !== \"object\") {\n return false;\n }\n if (!(\"width\" in value) || !(\"height\" in value)) {\n return false;\n }\n return typeof value.width === \"number\" && typeof value.height === \"number\";\n}\n\nfunction hasHistoryMethods(value: unknown): value is { pushState: History[\"pushState\"], replaceState: History[\"replaceState\"] } {\n if (value == null || typeof value !== \"object\") {\n return false;\n }\n if (!(\"pushState\" in value) || !(\"replaceState\" in value)) {\n return false;\n }\n return typeof value.pushState === \"function\" && typeof value.replaceState === \"function\";\n}\n\nexport type EventTrackerDeps = {\n projectId: string,\n sendBatch: (body: string, options: { keepalive: boolean }) => Promise<Result<Response, Error>>,\n};\n\ntype TrackedEvent = {\n event_type: \"$page-view\" | \"$click\",\n event_at_ms: number,\n data: Record<string, unknown>,\n};\n\nexport class EventTracker {\n private _started = false;\n private _cancelled = false;\n private _detachListeners: (() => void) | null = null;\n private _flushTimer: ReturnType<typeof setInterval> | null = null;\n private _events: TrackedEvent[] = [];\n private _approxBytes = 0;\n private _lastUrl: string | null = null;\n private readonly _sessionReplaySegmentId: string;\n private readonly _deps: EventTrackerDeps;\n\n private _originalPushState: History[\"pushState\"] | null = null;\n private _originalReplaceState: History[\"replaceState\"] | null = null;\n\n constructor(deps: EventTrackerDeps) {\n this._deps = deps;\n this._sessionReplaySegmentId = generateUuid();\n }\n\n start() {\n if (this._started) return;\n if (!isBrowserLike()) return;\n if (\n typeof window.addEventListener !== \"function\"\n || typeof window.removeEventListener !== \"function\"\n || typeof document.addEventListener !== \"function\"\n || typeof document.removeEventListener !== \"function\"\n || !hasScreenDimensions(window.screen)\n ) {\n return;\n }\n this._started = true;\n\n this._setupPageViewCapture();\n this._setupClickCapture();\n this._setupPageHideListeners();\n\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 runAsynchronously(() => this._flush({ keepalive: true }));\n this._teardown();\n }\n\n clearBuffer() {\n this._events = [];\n this._approxBytes = 0;\n }\n\n private _pushEvent(event: TrackedEvent) {\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\n private _capturePageView(entryType: \"initial\" | \"push\" | \"replace\" | \"pop\") {\n const screenObject = window.screen;\n if (!hasScreenDimensions(screenObject)) {\n return;\n }\n\n const url = window.location.href;\n if (url === this._lastUrl && entryType !== \"initial\") return;\n this._lastUrl = url;\n\n this._pushEvent({\n event_type: \"$page-view\",\n event_at_ms: Date.now(),\n data: {\n url,\n path: window.location.pathname,\n referrer: document.referrer,\n title: document.title,\n entry_type: entryType,\n viewport_width: window.innerWidth,\n viewport_height: window.innerHeight,\n screen_width: screenObject.width,\n screen_height: screenObject.height,\n },\n });\n }\n\n private _setupPageViewCapture() {\n // Fire initial page-view\n this._capturePageView(\"initial\");\n const historyObject = window.history;\n if (!hasHistoryMethods(historyObject)) {\n return;\n }\n const originalPushState = historyObject.pushState;\n const originalReplaceState = historyObject.replaceState;\n\n // Monkey-patch history.pushState\n this._originalPushState = (...args: Parameters<History[\"pushState\"]>) => originalPushState.apply(historyObject, args);\n historyObject.pushState = (...args: Parameters<History[\"pushState\"]>) => {\n this._originalPushState!(...args);\n this._capturePageView(\"push\");\n };\n\n // Monkey-patch history.replaceState\n this._originalReplaceState = (...args: Parameters<History[\"replaceState\"]>) => originalReplaceState.apply(historyObject, args);\n historyObject.replaceState = (...args: Parameters<History[\"replaceState\"]>) => {\n this._originalReplaceState!(...args);\n this._capturePageView(\"replace\");\n };\n\n // Listen for popstate (back/forward navigation)\n window.addEventListener(\"popstate\", this._onPopState);\n }\n\n private readonly _onPopState = () => {\n this._capturePageView(\"pop\");\n };\n\n private _buildSelector(element: Element): string {\n const parts: string[] = [];\n let current: Element | null = element;\n let depth = 0;\n\n while (current && depth < 5) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n parts.unshift(part);\n break;\n }\n if (current.className && typeof current.className === \"string\") {\n const classes = current.className.trim().split(/\\s+/).filter(Boolean);\n if (classes.length > 0) {\n part += `.${classes.join(\".\")}`;\n }\n }\n parts.unshift(part);\n current = current.parentElement;\n depth++;\n }\n\n return parts.join(\" > \");\n }\n\n private _findNearestAnchorHref(element: Element): string | null {\n let current: Element | null = element;\n while (current) {\n if (current.tagName === \"A\" && current.hasAttribute(\"href\")) {\n return current.getAttribute(\"href\");\n }\n current = current.parentElement;\n }\n return null;\n }\n\n private readonly _onClickCapture = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n this._pushEvent({\n event_type: \"$click\",\n event_at_ms: Date.now(),\n data: {\n tag_name: target.tagName.toLowerCase(),\n text: target.textContent.trim().substring(0, 200),\n href: this._findNearestAnchorHref(target),\n selector: this._buildSelector(target),\n x: event.clientX,\n y: event.clientY,\n page_x: event.pageX,\n page_y: event.pageY,\n viewport_width: window.innerWidth,\n viewport_height: window.innerHeight,\n },\n });\n };\n\n private _setupClickCapture() {\n document.addEventListener(\"click\", this._onClickCapture, { capture: true });\n }\n\n private readonly _onPageHide = () => {\n runAsynchronously(() => this._flush({ keepalive: true }));\n };\n\n private _setupPageHideListeners() {\n window.addEventListener(\"pagehide\", this._onPageHide);\n document.addEventListener(\"visibilitychange\", this._onPageHide);\n this._detachListeners = () => {\n window.removeEventListener(\"pagehide\", this._onPageHide);\n document.removeEventListener(\"visibilitychange\", this._onPageHide);\n };\n }\n\n private _teardown() {\n if (this._detachListeners) {\n this._detachListeners();\n this._detachListeners = null;\n }\n\n // Restore history methods\n const historyObject = window.history;\n if (hasHistoryMethods(historyObject)) {\n if (this._originalPushState) {\n historyObject.pushState = this._originalPushState;\n }\n if (this._originalReplaceState) {\n historyObject.replaceState = this._originalReplaceState;\n }\n }\n this._originalPushState = null;\n this._originalReplaceState = null;\n\n window.removeEventListener(\"popstate\", this._onPopState);\n document.removeEventListener(\"click\", this._onClickCapture, { capture: true });\n\n this._events = [];\n this._approxBytes = 0;\n }\n\n private async _flush(options: { keepalive: boolean }) {\n if (this._events.length === 0) return;\n\n const nowMs = Date.now();\n\n const batchId = generateUuid();\n const payload = {\n session_replay_segment_id: this._sessionReplaySegmentId,\n batch_id: batchId,\n sent_at_ms: nowMs,\n events: this._events,\n };\n\n this._events = [];\n this._approxBytes = 0;\n\n const res = await this._deps.sendBatch(\n JSON.stringify(payload),\n { keepalive: options.keepalive },\n );\n\n if (res.status === \"error\") {\n console.warn(\"EventTracker flush failed:\", res.error);\n return;\n }\n\n if (!res.data.ok) {\n console.warn(\"EventTracker flush failed:\", res.data.status, await res.data.text());\n }\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":"event-tracker.js","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/event-tracker.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 { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { generateUuid } from \"./session-replay\";\n\nconst FLUSH_INTERVAL_MS = 10_000;\nconst MAX_EVENTS_PER_BATCH = 50;\nconst MAX_APPROX_BYTES_PER_BATCH = 64_000;\n\nfunction hasScreenDimensions(value: unknown): value is { width: number, height: number } {\n if (value == null || typeof value !== \"object\") {\n return false;\n }\n if (!(\"width\" in value) || !(\"height\" in value)) {\n return false;\n }\n return typeof value.width === \"number\" && typeof value.height === \"number\";\n}\n\nfunction hasHistoryMethods(value: unknown): value is { pushState: History[\"pushState\"], replaceState: History[\"replaceState\"] } {\n if (value == null || typeof value !== \"object\") {\n return false;\n }\n if (!(\"pushState\" in value) || !(\"replaceState\" in value)) {\n return false;\n }\n return typeof value.pushState === \"function\" && typeof value.replaceState === \"function\";\n}\n\nexport type EventTrackerDeps = {\n projectId: string,\n sendBatch: (body: string, options: { keepalive: boolean }) => Promise<Result<Response, Error>>,\n};\n\ntype TrackedEvent = {\n event_type: \"$page-view\" | \"$click\",\n event_at_ms: number,\n data: Record<string, unknown>,\n};\n\nexport class EventTracker {\n private _started = false;\n private _cancelled = false;\n private _detachListeners: (() => void) | null = null;\n private _flushTimer: ReturnType<typeof setInterval> | null = null;\n private _events: TrackedEvent[] = [];\n private _approxBytes = 0;\n private _lastUrl: string | null = null;\n private readonly _sessionReplaySegmentId: string;\n private readonly _deps: EventTrackerDeps;\n\n private _originalPushState: History[\"pushState\"] | null = null;\n private _originalReplaceState: History[\"replaceState\"] | null = null;\n\n constructor(deps: EventTrackerDeps) {\n this._deps = deps;\n this._sessionReplaySegmentId = generateUuid();\n }\n\n start() {\n if (this._started) return;\n if (!isBrowserLike()) return;\n if (\n typeof window.addEventListener !== \"function\"\n || typeof window.removeEventListener !== \"function\"\n || typeof document.addEventListener !== \"function\"\n || typeof document.removeEventListener !== \"function\"\n || !hasScreenDimensions(window.screen)\n ) {\n return;\n }\n this._started = true;\n\n this._setupPageViewCapture();\n this._setupClickCapture();\n this._setupPageHideListeners();\n\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 runAsynchronously(() => this._flush({ keepalive: true }));\n this._teardown();\n }\n\n clearBuffer() {\n this._events = [];\n this._approxBytes = 0;\n }\n\n private _pushEvent(event: TrackedEvent) {\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\n private _capturePageView(entryType: \"initial\" | \"push\" | \"replace\" | \"pop\") {\n const screenObject = window.screen;\n if (!hasScreenDimensions(screenObject)) {\n return;\n }\n\n const url = window.location.href;\n if (url === this._lastUrl && entryType !== \"initial\") return;\n this._lastUrl = url;\n\n this._pushEvent({\n event_type: \"$page-view\",\n event_at_ms: Date.now(),\n data: {\n url,\n path: window.location.pathname,\n referrer: document.referrer,\n title: document.title,\n entry_type: entryType,\n viewport_width: window.innerWidth,\n viewport_height: window.innerHeight,\n screen_width: screenObject.width,\n screen_height: screenObject.height,\n user_agent: typeof navigator !== \"undefined\" ? navigator.userAgent : null,\n },\n });\n }\n\n private _setupPageViewCapture() {\n // Fire initial page-view\n this._capturePageView(\"initial\");\n const historyObject = window.history;\n if (!hasHistoryMethods(historyObject)) {\n return;\n }\n const originalPushState = historyObject.pushState;\n const originalReplaceState = historyObject.replaceState;\n\n // Monkey-patch history.pushState\n this._originalPushState = (...args: Parameters<History[\"pushState\"]>) => originalPushState.apply(historyObject, args);\n historyObject.pushState = (...args: Parameters<History[\"pushState\"]>) => {\n this._originalPushState!(...args);\n this._capturePageView(\"push\");\n };\n\n // Monkey-patch history.replaceState\n this._originalReplaceState = (...args: Parameters<History[\"replaceState\"]>) => originalReplaceState.apply(historyObject, args);\n historyObject.replaceState = (...args: Parameters<History[\"replaceState\"]>) => {\n this._originalReplaceState!(...args);\n this._capturePageView(\"replace\");\n };\n\n // Listen for popstate (back/forward navigation)\n window.addEventListener(\"popstate\", this._onPopState);\n }\n\n private readonly _onPopState = () => {\n this._capturePageView(\"pop\");\n };\n\n private _buildSelector(element: Element): string {\n const parts: string[] = [];\n let current: Element | null = element;\n let depth = 0;\n\n while (current && depth < 5) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n parts.unshift(part);\n break;\n }\n if (current.className && typeof current.className === \"string\") {\n const classes = current.className.trim().split(/\\s+/).filter(Boolean);\n if (classes.length > 0) {\n part += `.${classes.join(\".\")}`;\n }\n }\n parts.unshift(part);\n current = current.parentElement;\n depth++;\n }\n\n return parts.join(\" > \");\n }\n\n private _findNearestAnchorHref(element: Element): string | null {\n let current: Element | null = element;\n while (current) {\n if (current.tagName === \"A\" && current.hasAttribute(\"href\")) {\n return current.getAttribute(\"href\");\n }\n current = current.parentElement;\n }\n return null;\n }\n\n private readonly _onClickCapture = (event: MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n this._pushEvent({\n event_type: \"$click\",\n event_at_ms: Date.now(),\n data: {\n tag_name: target.tagName.toLowerCase(),\n text: target.textContent.trim().substring(0, 200),\n href: this._findNearestAnchorHref(target),\n selector: this._buildSelector(target),\n x: event.clientX,\n y: event.clientY,\n page_x: event.pageX,\n page_y: event.pageY,\n viewport_width: window.innerWidth,\n viewport_height: window.innerHeight,\n },\n });\n };\n\n private _setupClickCapture() {\n document.addEventListener(\"click\", this._onClickCapture, { capture: true });\n }\n\n private readonly _onPageHide = () => {\n runAsynchronously(() => this._flush({ keepalive: true }));\n };\n\n private _setupPageHideListeners() {\n window.addEventListener(\"pagehide\", this._onPageHide);\n document.addEventListener(\"visibilitychange\", this._onPageHide);\n this._detachListeners = () => {\n window.removeEventListener(\"pagehide\", this._onPageHide);\n document.removeEventListener(\"visibilitychange\", this._onPageHide);\n };\n }\n\n private _teardown() {\n if (this._detachListeners) {\n this._detachListeners();\n this._detachListeners = null;\n }\n\n // Restore history methods\n const historyObject = window.history;\n if (hasHistoryMethods(historyObject)) {\n if (this._originalPushState) {\n historyObject.pushState = this._originalPushState;\n }\n if (this._originalReplaceState) {\n historyObject.replaceState = this._originalReplaceState;\n }\n }\n this._originalPushState = null;\n this._originalReplaceState = null;\n\n window.removeEventListener(\"popstate\", this._onPopState);\n document.removeEventListener(\"click\", this._onClickCapture, { capture: true });\n\n this._events = [];\n this._approxBytes = 0;\n }\n\n private async _flush(options: { keepalive: boolean }) {\n if (this._events.length === 0) return;\n\n const nowMs = Date.now();\n\n const batchId = generateUuid();\n const payload = {\n session_replay_segment_id: this._sessionReplaySegmentId,\n batch_id: batchId,\n sent_at_ms: nowMs,\n events: this._events,\n };\n\n this._events = [];\n this._approxBytes = 0;\n\n const res = await this._deps.sendBatch(\n JSON.stringify(payload),\n { keepalive: options.keepalive },\n );\n\n if (res.status === \"error\") {\n console.warn(\"EventTracker flush failed:\", res.error);\n return;\n }\n\n if (!res.data.ok) {\n console.warn(\"EventTracker flush failed:\", res.data.status, await res.data.text());\n }\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 +1 @@
1
- {"version":3,"file":"url-targets.d.ts","names":[],"sources":["../../../src/lib/hexclave-app/url-targets.ts"],"mappings":";;;cA2Ga,mBAAA,GAAuB,OAAA;EAAW,SAAA;EAAmB,QAAA;AAAA;AAAA,cAyBrD,uBAAA,GAA2B,OAAA;EACtC,SAAA;EACA,WAAA;EACA,aAAA;AAAA;AAAA,cA+DW,kBAAA,GAAsB,OAAA;EAAW,IAAA,EAAM,iBAAA;EAA+B,SAAA;AAAA,MAAsB,mBAAA;AAAA,cAqJ5F,sBAAA,GAA0B,OAAA;EACrC,iBAAA,UAvNA;EAyNA,MAAA;EACA,SAAA;AAAA;AAAA,cAOW,oCAAA,GAAwC,OAAA;EACnD,aAAA,EAAe,uBAAA;EACf,SAAA;EACA,WAAA;AAAA;AAAA,iBAiBc,aAAA,CAAc,QAAA,UAAkB,cAAA;EAA4B,KAAA;EAAe,UAAA;EAAoB,aAAA;EAA8B,aAAA;AAAA;AAAA,cAuBhI,4BAAA,GAAgC,OAAA;EAAW,GAAA;EAAa,SAAA;AAAA"}
1
+ {"version":3,"file":"url-targets.d.ts","names":[],"sources":["../../../src/lib/hexclave-app/url-targets.ts"],"mappings":";;;cA2Ga,mBAAA,GAAuB,OAAA;EAAW,SAAA;EAAmB,QAAA;AAAA;AAAA,cA0BrD,uBAAA,GAA2B,OAAA;EACtC,SAAA;EACA,WAAA;EACA,aAAA;AAAA;AAAA,cA+DW,kBAAA,GAAsB,OAAA;EAAW,IAAA,EAAM,iBAAA;EAA+B,SAAA;AAAA,MAAsB,mBAAA;AAAA,cAqJ5F,sBAAA,GAA0B,OAAA;EACrC,iBAAA,UAvNA;EAyNA,MAAA;EACA,SAAA;AAAA;AAAA,cAOW,oCAAA,GAAwC,OAAA;EACnD,aAAA,EAAe,uBAAA;EACf,SAAA;EACA,WAAA;AAAA;AAAA,iBAiBc,aAAA,CAAc,QAAA,UAAkB,cAAA;EAA4B,KAAA;EAAe,UAAA;EAAoB,aAAA;EAA8B,aAAA;AAAA;AAAA,cAuBhI,4BAAA,GAAgC,OAAA;EAAW,GAAA;EAAa,SAAA;AAAA"}
@@ -62,6 +62,7 @@ const isRelativeUrlString = (url) => {
62
62
  return !schemePrefixRegex.test(url);
63
63
  };
64
64
  const nonHostedHandlerNames = new Set([
65
+ "home",
65
66
  "afterSignIn",
66
67
  "afterSignUp",
67
68
  "afterSignOut"
@@ -1 +1 @@
1
- {"version":3,"file":"url-targets.js","names":["envVars","HexclaveAssertionError"],"sources":["../../../src/lib/hexclave-app/url-targets.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 { getCustomPagePrompts, type CustomPagePrompt } from \"@hexclave/shared/dist/interface/handler-urls\";\nimport { HexclaveAssertionError } from \"@hexclave/shared/dist/utils/errors\";\nimport { getHostedHandlerUrlFromConfig } from \"@hexclave/shared/dist/utils/redirect-urls\";\nimport { envVars } from \"../../generated/env\";\nimport { DefaultHandlerUrlTarget, HandlerPageUrls, HandlerUrlOptions, HandlerUrlTarget, HandlerUrls, ResolvedHandlerUrls } from \"./common\";\n\nconst localUrlPlaceholderOrigin = \"http://example.com\";\nconst schemePrefixRegex = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\n\nconst customPagePrompts: Record<keyof Omit<HandlerPageUrls, \"handler\">, CustomPagePrompt> = getCustomPagePrompts();\n\nconst joinHandlerComponentPath = (basePath: string, pagePath: string): string => {\n const normalizedBasePath = basePath.endsWith(\"/\") && basePath.length > 1\n ? basePath.slice(0, -1)\n : basePath;\n if (pagePath.length === 0) {\n return normalizedBasePath;\n }\n if (normalizedBasePath === \"/\") {\n return `/${pagePath}`;\n }\n return `${normalizedBasePath}/${pagePath}`;\n};\n\nconst getHostedPagePathForHandlerName = (handlerName: keyof HandlerUrls): string => {\n switch (handlerName) {\n case \"handler\": {\n return \"\";\n }\n case \"home\": {\n return \"\";\n }\n case \"afterSignIn\": {\n return \"\";\n }\n case \"afterSignUp\": {\n return \"\";\n }\n case \"afterSignOut\": {\n return \"\";\n }\n case \"signIn\": {\n return \"sign-in\";\n }\n case \"signUp\": {\n return \"sign-up\";\n }\n case \"signOut\": {\n return \"sign-out\";\n }\n case \"emailVerification\": {\n return \"email-verification\";\n }\n case \"passwordReset\": {\n return \"password-reset\";\n }\n case \"forgotPassword\": {\n return \"forgot-password\";\n }\n case \"oauthCallback\": {\n return \"oauth-callback\";\n }\n case \"magicLinkCallback\": {\n return \"magic-link-callback\";\n }\n case \"accountSettings\": {\n return \"account-settings\";\n }\n case \"teamInvitation\": {\n return \"team-invitation\";\n }\n case \"cliAuthConfirm\": {\n return \"cli-auth-confirm\";\n }\n case \"mfa\": {\n return \"mfa\";\n }\n case \"error\": {\n return \"error\";\n }\n case \"onboarding\": {\n return \"onboarding\";\n }\n }\n};\n\nconst resolveCustomTargetUrl = (options: {\n target: { type: \"custom\", url: string, version: number },\n handlerName: keyof HandlerUrls,\n}): string => {\n const handlerName = options.handlerName;\n if (handlerName in customPagePrompts) {\n const customPagePrompt = customPagePrompts[handlerName as keyof typeof customPagePrompts];\n if (options.target.version === 0 || options.target.version in customPagePrompt.versions) {\n return options.target.url;\n }\n\n throw new Error(`Unsupported custom page version ${options.target.version} for ${options.handlerName} page at ${options.target.url}. The latest supported version of this page is ${Math.max(0, ...Object.keys(customPagePrompt.versions).map(Number))}. Please upgrade your Hexclave SDK to a version that supports this version.`);\n } else {\n throw new Error(`URL target ${options.handlerName} cannot be a custom page. Please specify the URL as a string instead.`);\n }\n};\n\nexport const getHostedHandlerUrl = (options: { projectId: string, pagePath: string }): string => {\n const normalizedPagePath = options.pagePath.replace(/^\\/+/, \"\");\n const hostedPath = normalizedPagePath.length > 0 ? `handler/${normalizedPagePath}` : \"handler\";\n return getHostedHandlerUrlFromConfig({\n projectId: options.projectId,\n hostedPath,\n hostedHandlerDomainSuffix: envVars.HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX,\n hostedHandlerUrlTemplate: envVars.HEXCLAVE_HOSTED_HANDLER_URL_TEMPLATE,\n hexclavePortPrefix: envVars.HEXCLAVE_PORT_PREFIX,\n });\n};\n\nconst isRelativeUrlString = (url: string): boolean => {\n if (url.startsWith(\"//\")) {\n return false;\n }\n return !schemePrefixRegex.test(url);\n};\n\nconst nonHostedHandlerNames = new Set<keyof HandlerUrls>([\n \"afterSignIn\",\n \"afterSignUp\",\n \"afterSignOut\",\n]);\n\nexport const isLocalHandlerUrlTarget = (options: {\n targetUrl: string,\n handlerPath: string,\n currentOrigin?: string,\n}): boolean => {\n const urlObject = new URL(options.targetUrl, localUrlPlaceholderOrigin);\n const isHandlerPathTarget = urlObject.pathname === options.handlerPath\n || urlObject.pathname.startsWith(`${options.handlerPath}/`);\n if (!isHandlerPathTarget) {\n return false;\n }\n\n // On server we only have path information, so treat matching handler paths as local.\n if (options.currentOrigin == null) {\n return true;\n }\n\n return isRelativeUrlString(options.targetUrl) || urlObject.origin === options.currentOrigin;\n};\n\nconst resolveUrlTarget = (options: {\n target: HandlerUrlTarget,\n fallbackPath: string,\n handlerName: keyof HandlerUrls,\n projectId: string,\n}): string => {\n if (typeof options.target === \"string\") {\n return options.target;\n }\n\n switch (options.target.type) {\n case \"handler-component\": {\n return options.fallbackPath;\n }\n case \"hosted\": {\n if (nonHostedHandlerNames.has(options.handlerName)) {\n return options.fallbackPath;\n }\n return getHostedHandlerUrl({\n projectId: options.projectId,\n pagePath: getHostedPagePathForHandlerName(options.handlerName),\n });\n }\n case \"custom\": {\n return resolveCustomTargetUrl({\n target: options.target,\n handlerName: options.handlerName,\n });\n }\n }\n};\n\nconst assertOAuthCallbackTargetIsRelative = (target: HandlerUrlTarget): void => {\n const url = typeof target === \"string\"\n ? target\n : target.type === \"custom\"\n ? target.url\n : null;\n if (url != null && !isRelativeUrlString(url)) {\n throw new HexclaveAssertionError(\"OAuth callback URLs must be relative.\", {\n oauthCallbackUrl: url,\n hint: \"Use a relative URL like '/handler/oauth-callback', or use { type: 'hosted' } to let Stack use the current page for hosted callbacks.\",\n });\n }\n};\n\nexport const resolveHandlerUrls = (options: { urls: HandlerUrlOptions | undefined, projectId: string }): ResolvedHandlerUrls => {\n const configuredUrls = options.urls;\n const defaultTarget = configuredUrls?.default ?? { type: \"handler-component\" } as const;\n const oauthCallbackTarget: HandlerUrlTarget = configuredUrls?.oauthCallback ?? (\n defaultTarget.type === \"hosted\"\n ? defaultTarget\n : { type: \"handler-component\" }\n );\n assertOAuthCallbackTargetIsRelative(oauthCallbackTarget);\n let handlerComponentBasePath = \"/handler\";\n if (typeof configuredUrls?.handler === \"string\") {\n handlerComponentBasePath = configuredUrls.handler;\n } else if (configuredUrls?.handler != null && configuredUrls.handler.type === \"custom\") {\n handlerComponentBasePath = resolveCustomTargetUrl({\n target: configuredUrls.handler,\n handlerName: \"handler\",\n });\n }\n\n const homeTarget = configuredUrls?.home ?? defaultTarget;\n const localHome = resolveUrlTarget({\n target: typeof homeTarget !== \"string\" && homeTarget.type === \"hosted\"\n ? { type: \"handler-component\" }\n : homeTarget,\n fallbackPath: \"/\",\n handlerName: \"home\",\n projectId: options.projectId,\n });\n const home = resolveUrlTarget({\n target: homeTarget,\n fallbackPath: \"/\",\n handlerName: \"home\",\n projectId: options.projectId,\n });\n const afterSignIn = resolveUrlTarget({\n target: configuredUrls?.afterSignIn ?? defaultTarget,\n fallbackPath: localHome,\n handlerName: \"afterSignIn\",\n projectId: options.projectId,\n });\n\n return {\n handler: resolveUrlTarget({\n target: configuredUrls?.handler ?? defaultTarget,\n fallbackPath: handlerComponentBasePath,\n handlerName: \"handler\",\n projectId: options.projectId,\n }),\n signIn: resolveUrlTarget({\n target: configuredUrls?.signIn ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"sign-in\"),\n handlerName: \"signIn\",\n projectId: options.projectId,\n }),\n signUp: resolveUrlTarget({\n target: configuredUrls?.signUp ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"sign-up\"),\n handlerName: \"signUp\",\n projectId: options.projectId,\n }),\n afterSignIn,\n afterSignUp: resolveUrlTarget({\n target: configuredUrls?.afterSignUp ?? defaultTarget,\n fallbackPath: afterSignIn,\n handlerName: \"afterSignUp\",\n projectId: options.projectId,\n }),\n signOut: resolveUrlTarget({\n target: configuredUrls?.signOut ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"sign-out\"),\n handlerName: \"signOut\",\n projectId: options.projectId,\n }),\n afterSignOut: resolveUrlTarget({\n target: configuredUrls?.afterSignOut ?? defaultTarget,\n fallbackPath: localHome,\n handlerName: \"afterSignOut\",\n projectId: options.projectId,\n }),\n emailVerification: resolveUrlTarget({\n target: configuredUrls?.emailVerification ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"email-verification\"),\n handlerName: \"emailVerification\",\n projectId: options.projectId,\n }),\n passwordReset: resolveUrlTarget({\n target: configuredUrls?.passwordReset ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"password-reset\"),\n handlerName: \"passwordReset\",\n projectId: options.projectId,\n }),\n forgotPassword: resolveUrlTarget({\n target: configuredUrls?.forgotPassword ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"forgot-password\"),\n handlerName: \"forgotPassword\",\n projectId: options.projectId,\n }),\n home,\n oauthCallback: resolveUrlTarget({\n target: oauthCallbackTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"oauth-callback\"),\n handlerName: \"oauthCallback\",\n projectId: options.projectId,\n }),\n magicLinkCallback: resolveUrlTarget({\n target: configuredUrls?.magicLinkCallback ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"magic-link-callback\"),\n handlerName: \"magicLinkCallback\",\n projectId: options.projectId,\n }),\n accountSettings: resolveUrlTarget({\n target: configuredUrls?.accountSettings ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"account-settings\"),\n handlerName: \"accountSettings\",\n projectId: options.projectId,\n }),\n teamInvitation: resolveUrlTarget({\n target: configuredUrls?.teamInvitation ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"team-invitation\"),\n handlerName: \"teamInvitation\",\n projectId: options.projectId,\n }),\n cliAuthConfirm: resolveUrlTarget({\n target: configuredUrls?.cliAuthConfirm ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"cli-auth-confirm\"),\n handlerName: \"cliAuthConfirm\",\n projectId: options.projectId,\n }),\n mfa: resolveUrlTarget({\n target: configuredUrls?.mfa ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"mfa\"),\n handlerName: \"mfa\",\n projectId: options.projectId,\n }),\n error: resolveUrlTarget({\n target: configuredUrls?.error ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"error\"),\n handlerName: \"error\",\n projectId: options.projectId,\n }),\n onboarding: resolveUrlTarget({\n target: configuredUrls?.onboarding ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"onboarding\"),\n handlerName: \"onboarding\",\n projectId: options.projectId,\n }),\n };\n};\n\nexport const buildCliAuthConfirmUrl = (options: {\n cliAuthConfirmUrl: string,\n /** Used as the base URL only when cliAuthConfirmUrl is relative. */\n appUrl: string,\n loginCode: string,\n}): string => {\n const url = new URL(options.cliAuthConfirmUrl, options.appUrl);\n url.searchParams.set(\"login_code\", options.loginCode);\n return url.toString();\n};\n\nexport const resolveUnknownHandlerPathFallbackUrl = (options: {\n defaultTarget: DefaultHandlerUrlTarget | undefined,\n projectId: string,\n unknownPath: string,\n}): string | null => {\n const defaultTarget = options.defaultTarget ?? { type: \"handler-component\" } satisfies DefaultHandlerUrlTarget;\n\n switch (defaultTarget.type) {\n case \"handler-component\": {\n return null;\n }\n case \"hosted\": {\n return getHostedHandlerUrl({\n projectId: options.projectId,\n pagePath: options.unknownPath,\n });\n }\n }\n};\n\nexport function getPagePrompt(pageName: string, currentVersion?: number): { title: string; fullPrompt: string; upgradePrompt: string | null; latestVersion: number } | null {\n if (!(pageName in customPagePrompts)) return null;\n const prompt = customPagePrompts[pageName as keyof typeof customPagePrompts];\n const versionKeys = Object.keys(prompt.versions).map(Number);\n const latestVersion = versionKeys.length > 0 ? Math.max(...versionKeys) : 0;\n\n let upgradePrompt: string | null = null;\n if (currentVersion != null) {\n const relevantVersions = versionKeys\n .filter(v => v > currentVersion)\n .sort((a, b) => a - b);\n const prompts = relevantVersions\n .map(v => prompt.versions[v].upgradePrompt)\n .filter(p => p.length > 0);\n upgradePrompt = prompts.length > 0 ? prompts.join(\"\\n\\n\") : null;\n } else {\n const upgradeEntry = latestVersion > 0 ? prompt.versions[latestVersion] : undefined;\n upgradePrompt = upgradeEntry?.upgradePrompt ?? null;\n }\n\n return { title: prompt.title, fullPrompt: prompt.fullPrompt, upgradePrompt, latestVersion };\n}\n\nexport const isHostedHandlerUrlForProject = (options: { url: string, projectId: string }): boolean => {\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(options.url);\n } catch {\n return false;\n }\n\n const hostedBaseUrl = new URL(getHostedHandlerUrl({ projectId: options.projectId, pagePath: \"\" }));\n return parsedUrl.origin === hostedBaseUrl.origin\n && (parsedUrl.pathname === hostedBaseUrl.pathname || parsedUrl.pathname.startsWith(`${hostedBaseUrl.pathname}/`));\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"url-targets.js","names":["envVars","HexclaveAssertionError"],"sources":["../../../src/lib/hexclave-app/url-targets.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 { getCustomPagePrompts, type CustomPagePrompt } from \"@hexclave/shared/dist/interface/handler-urls\";\nimport { HexclaveAssertionError } from \"@hexclave/shared/dist/utils/errors\";\nimport { getHostedHandlerUrlFromConfig } from \"@hexclave/shared/dist/utils/redirect-urls\";\nimport { envVars } from \"../../generated/env\";\nimport { DefaultHandlerUrlTarget, HandlerPageUrls, HandlerUrlOptions, HandlerUrlTarget, HandlerUrls, ResolvedHandlerUrls } from \"./common\";\n\nconst localUrlPlaceholderOrigin = \"http://example.com\";\nconst schemePrefixRegex = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\n\nconst customPagePrompts: Record<keyof Omit<HandlerPageUrls, \"handler\">, CustomPagePrompt> = getCustomPagePrompts();\n\nconst joinHandlerComponentPath = (basePath: string, pagePath: string): string => {\n const normalizedBasePath = basePath.endsWith(\"/\") && basePath.length > 1\n ? basePath.slice(0, -1)\n : basePath;\n if (pagePath.length === 0) {\n return normalizedBasePath;\n }\n if (normalizedBasePath === \"/\") {\n return `/${pagePath}`;\n }\n return `${normalizedBasePath}/${pagePath}`;\n};\n\nconst getHostedPagePathForHandlerName = (handlerName: keyof HandlerUrls): string => {\n switch (handlerName) {\n case \"handler\": {\n return \"\";\n }\n case \"home\": {\n return \"\";\n }\n case \"afterSignIn\": {\n return \"\";\n }\n case \"afterSignUp\": {\n return \"\";\n }\n case \"afterSignOut\": {\n return \"\";\n }\n case \"signIn\": {\n return \"sign-in\";\n }\n case \"signUp\": {\n return \"sign-up\";\n }\n case \"signOut\": {\n return \"sign-out\";\n }\n case \"emailVerification\": {\n return \"email-verification\";\n }\n case \"passwordReset\": {\n return \"password-reset\";\n }\n case \"forgotPassword\": {\n return \"forgot-password\";\n }\n case \"oauthCallback\": {\n return \"oauth-callback\";\n }\n case \"magicLinkCallback\": {\n return \"magic-link-callback\";\n }\n case \"accountSettings\": {\n return \"account-settings\";\n }\n case \"teamInvitation\": {\n return \"team-invitation\";\n }\n case \"cliAuthConfirm\": {\n return \"cli-auth-confirm\";\n }\n case \"mfa\": {\n return \"mfa\";\n }\n case \"error\": {\n return \"error\";\n }\n case \"onboarding\": {\n return \"onboarding\";\n }\n }\n};\n\nconst resolveCustomTargetUrl = (options: {\n target: { type: \"custom\", url: string, version: number },\n handlerName: keyof HandlerUrls,\n}): string => {\n const handlerName = options.handlerName;\n if (handlerName in customPagePrompts) {\n const customPagePrompt = customPagePrompts[handlerName as keyof typeof customPagePrompts];\n if (options.target.version === 0 || options.target.version in customPagePrompt.versions) {\n return options.target.url;\n }\n\n throw new Error(`Unsupported custom page version ${options.target.version} for ${options.handlerName} page at ${options.target.url}. The latest supported version of this page is ${Math.max(0, ...Object.keys(customPagePrompt.versions).map(Number))}. Please upgrade your Hexclave SDK to a version that supports this version.`);\n } else {\n throw new Error(`URL target ${options.handlerName} cannot be a custom page. Please specify the URL as a string instead.`);\n }\n};\n\nexport const getHostedHandlerUrl = (options: { projectId: string, pagePath: string }): string => {\n const normalizedPagePath = options.pagePath.replace(/^\\/+/, \"\");\n const hostedPath = normalizedPagePath.length > 0 ? `handler/${normalizedPagePath}` : \"handler\";\n return getHostedHandlerUrlFromConfig({\n projectId: options.projectId,\n hostedPath,\n hostedHandlerDomainSuffix: envVars.HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX,\n hostedHandlerUrlTemplate: envVars.HEXCLAVE_HOSTED_HANDLER_URL_TEMPLATE,\n hexclavePortPrefix: envVars.HEXCLAVE_PORT_PREFIX,\n });\n};\n\nconst isRelativeUrlString = (url: string): boolean => {\n if (url.startsWith(\"//\")) {\n return false;\n }\n return !schemePrefixRegex.test(url);\n};\n\nconst nonHostedHandlerNames = new Set<keyof HandlerUrls>([\n \"home\",\n \"afterSignIn\",\n \"afterSignUp\",\n \"afterSignOut\",\n]);\n\nexport const isLocalHandlerUrlTarget = (options: {\n targetUrl: string,\n handlerPath: string,\n currentOrigin?: string,\n}): boolean => {\n const urlObject = new URL(options.targetUrl, localUrlPlaceholderOrigin);\n const isHandlerPathTarget = urlObject.pathname === options.handlerPath\n || urlObject.pathname.startsWith(`${options.handlerPath}/`);\n if (!isHandlerPathTarget) {\n return false;\n }\n\n // On server we only have path information, so treat matching handler paths as local.\n if (options.currentOrigin == null) {\n return true;\n }\n\n return isRelativeUrlString(options.targetUrl) || urlObject.origin === options.currentOrigin;\n};\n\nconst resolveUrlTarget = (options: {\n target: HandlerUrlTarget,\n fallbackPath: string,\n handlerName: keyof HandlerUrls,\n projectId: string,\n}): string => {\n if (typeof options.target === \"string\") {\n return options.target;\n }\n\n switch (options.target.type) {\n case \"handler-component\": {\n return options.fallbackPath;\n }\n case \"hosted\": {\n if (nonHostedHandlerNames.has(options.handlerName)) {\n return options.fallbackPath;\n }\n return getHostedHandlerUrl({\n projectId: options.projectId,\n pagePath: getHostedPagePathForHandlerName(options.handlerName),\n });\n }\n case \"custom\": {\n return resolveCustomTargetUrl({\n target: options.target,\n handlerName: options.handlerName,\n });\n }\n }\n};\n\nconst assertOAuthCallbackTargetIsRelative = (target: HandlerUrlTarget): void => {\n const url = typeof target === \"string\"\n ? target\n : target.type === \"custom\"\n ? target.url\n : null;\n if (url != null && !isRelativeUrlString(url)) {\n throw new HexclaveAssertionError(\"OAuth callback URLs must be relative.\", {\n oauthCallbackUrl: url,\n hint: \"Use a relative URL like '/handler/oauth-callback', or use { type: 'hosted' } to let Stack use the current page for hosted callbacks.\",\n });\n }\n};\n\nexport const resolveHandlerUrls = (options: { urls: HandlerUrlOptions | undefined, projectId: string }): ResolvedHandlerUrls => {\n const configuredUrls = options.urls;\n const defaultTarget = configuredUrls?.default ?? { type: \"handler-component\" } as const;\n const oauthCallbackTarget: HandlerUrlTarget = configuredUrls?.oauthCallback ?? (\n defaultTarget.type === \"hosted\"\n ? defaultTarget\n : { type: \"handler-component\" }\n );\n assertOAuthCallbackTargetIsRelative(oauthCallbackTarget);\n let handlerComponentBasePath = \"/handler\";\n if (typeof configuredUrls?.handler === \"string\") {\n handlerComponentBasePath = configuredUrls.handler;\n } else if (configuredUrls?.handler != null && configuredUrls.handler.type === \"custom\") {\n handlerComponentBasePath = resolveCustomTargetUrl({\n target: configuredUrls.handler,\n handlerName: \"handler\",\n });\n }\n\n const homeTarget = configuredUrls?.home ?? defaultTarget;\n const localHome = resolveUrlTarget({\n target: typeof homeTarget !== \"string\" && homeTarget.type === \"hosted\"\n ? { type: \"handler-component\" }\n : homeTarget,\n fallbackPath: \"/\",\n handlerName: \"home\",\n projectId: options.projectId,\n });\n const home = resolveUrlTarget({\n target: homeTarget,\n fallbackPath: \"/\",\n handlerName: \"home\",\n projectId: options.projectId,\n });\n const afterSignIn = resolveUrlTarget({\n target: configuredUrls?.afterSignIn ?? defaultTarget,\n fallbackPath: localHome,\n handlerName: \"afterSignIn\",\n projectId: options.projectId,\n });\n\n return {\n handler: resolveUrlTarget({\n target: configuredUrls?.handler ?? defaultTarget,\n fallbackPath: handlerComponentBasePath,\n handlerName: \"handler\",\n projectId: options.projectId,\n }),\n signIn: resolveUrlTarget({\n target: configuredUrls?.signIn ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"sign-in\"),\n handlerName: \"signIn\",\n projectId: options.projectId,\n }),\n signUp: resolveUrlTarget({\n target: configuredUrls?.signUp ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"sign-up\"),\n handlerName: \"signUp\",\n projectId: options.projectId,\n }),\n afterSignIn,\n afterSignUp: resolveUrlTarget({\n target: configuredUrls?.afterSignUp ?? defaultTarget,\n fallbackPath: afterSignIn,\n handlerName: \"afterSignUp\",\n projectId: options.projectId,\n }),\n signOut: resolveUrlTarget({\n target: configuredUrls?.signOut ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"sign-out\"),\n handlerName: \"signOut\",\n projectId: options.projectId,\n }),\n afterSignOut: resolveUrlTarget({\n target: configuredUrls?.afterSignOut ?? defaultTarget,\n fallbackPath: localHome,\n handlerName: \"afterSignOut\",\n projectId: options.projectId,\n }),\n emailVerification: resolveUrlTarget({\n target: configuredUrls?.emailVerification ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"email-verification\"),\n handlerName: \"emailVerification\",\n projectId: options.projectId,\n }),\n passwordReset: resolveUrlTarget({\n target: configuredUrls?.passwordReset ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"password-reset\"),\n handlerName: \"passwordReset\",\n projectId: options.projectId,\n }),\n forgotPassword: resolveUrlTarget({\n target: configuredUrls?.forgotPassword ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"forgot-password\"),\n handlerName: \"forgotPassword\",\n projectId: options.projectId,\n }),\n home,\n oauthCallback: resolveUrlTarget({\n target: oauthCallbackTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"oauth-callback\"),\n handlerName: \"oauthCallback\",\n projectId: options.projectId,\n }),\n magicLinkCallback: resolveUrlTarget({\n target: configuredUrls?.magicLinkCallback ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"magic-link-callback\"),\n handlerName: \"magicLinkCallback\",\n projectId: options.projectId,\n }),\n accountSettings: resolveUrlTarget({\n target: configuredUrls?.accountSettings ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"account-settings\"),\n handlerName: \"accountSettings\",\n projectId: options.projectId,\n }),\n teamInvitation: resolveUrlTarget({\n target: configuredUrls?.teamInvitation ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"team-invitation\"),\n handlerName: \"teamInvitation\",\n projectId: options.projectId,\n }),\n cliAuthConfirm: resolveUrlTarget({\n target: configuredUrls?.cliAuthConfirm ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"cli-auth-confirm\"),\n handlerName: \"cliAuthConfirm\",\n projectId: options.projectId,\n }),\n mfa: resolveUrlTarget({\n target: configuredUrls?.mfa ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"mfa\"),\n handlerName: \"mfa\",\n projectId: options.projectId,\n }),\n error: resolveUrlTarget({\n target: configuredUrls?.error ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"error\"),\n handlerName: \"error\",\n projectId: options.projectId,\n }),\n onboarding: resolveUrlTarget({\n target: configuredUrls?.onboarding ?? defaultTarget,\n fallbackPath: joinHandlerComponentPath(handlerComponentBasePath, \"onboarding\"),\n handlerName: \"onboarding\",\n projectId: options.projectId,\n }),\n };\n};\n\nexport const buildCliAuthConfirmUrl = (options: {\n cliAuthConfirmUrl: string,\n /** Used as the base URL only when cliAuthConfirmUrl is relative. */\n appUrl: string,\n loginCode: string,\n}): string => {\n const url = new URL(options.cliAuthConfirmUrl, options.appUrl);\n url.searchParams.set(\"login_code\", options.loginCode);\n return url.toString();\n};\n\nexport const resolveUnknownHandlerPathFallbackUrl = (options: {\n defaultTarget: DefaultHandlerUrlTarget | undefined,\n projectId: string,\n unknownPath: string,\n}): string | null => {\n const defaultTarget = options.defaultTarget ?? { type: \"handler-component\" } satisfies DefaultHandlerUrlTarget;\n\n switch (defaultTarget.type) {\n case \"handler-component\": {\n return null;\n }\n case \"hosted\": {\n return getHostedHandlerUrl({\n projectId: options.projectId,\n pagePath: options.unknownPath,\n });\n }\n }\n};\n\nexport function getPagePrompt(pageName: string, currentVersion?: number): { title: string; fullPrompt: string; upgradePrompt: string | null; latestVersion: number } | null {\n if (!(pageName in customPagePrompts)) return null;\n const prompt = customPagePrompts[pageName as keyof typeof customPagePrompts];\n const versionKeys = Object.keys(prompt.versions).map(Number);\n const latestVersion = versionKeys.length > 0 ? Math.max(...versionKeys) : 0;\n\n let upgradePrompt: string | null = null;\n if (currentVersion != null) {\n const relevantVersions = versionKeys\n .filter(v => v > currentVersion)\n .sort((a, b) => a - b);\n const prompts = relevantVersions\n .map(v => prompt.versions[v].upgradePrompt)\n .filter(p => p.length > 0);\n upgradePrompt = prompts.length > 0 ? prompts.join(\"\\n\\n\") : null;\n } else {\n const upgradeEntry = latestVersion > 0 ? prompt.versions[latestVersion] : undefined;\n upgradePrompt = upgradeEntry?.upgradePrompt ?? null;\n }\n\n return { title: prompt.title, fullPrompt: prompt.fullPrompt, upgradePrompt, latestVersion };\n}\n\nexport const isHostedHandlerUrlForProject = (options: { url: string, projectId: string }): boolean => {\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(options.url);\n } catch {\n return false;\n }\n\n const hostedBaseUrl = new URL(getHostedHandlerUrl({ projectId: options.projectId, pagePath: \"\" }));\n return parsedUrl.origin === hostedBaseUrl.origin\n && (parsedUrl.pathname === hostedBaseUrl.pathname || parsedUrl.pathname.startsWith(`${hostedBaseUrl.pathname}/`));\n};\n"],"mappings":"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"}
@@ -97,6 +97,7 @@ let __url_targets_js = require("./url-targets.js");
97
97
  });
98
98
  (0, vitest.expect)(urls.signIn).toBe("https://project-id.example-stack-hosted.test/handler/sign-in");
99
99
  (0, vitest.expect)(urls.signOut).toBe("https://project-id.example-stack-hosted.test/handler/sign-out");
100
+ (0, vitest.expect)(urls.home).toBe("/");
100
101
  (0, vitest.expect)(urls.afterSignIn).toBe("/");
101
102
  (0, vitest.expect)(urls.afterSignUp).toBe("/");
102
103
  (0, vitest.expect)(urls.afterSignOut).toBe("/");
@@ -1 +1 @@
1
- {"version":3,"file":"url-targets.test.js","names":[],"sources":["../../../src/lib/hexclave-app/url-targets.test.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { buildCliAuthConfirmUrl, getPagePrompt, isLocalHandlerUrlTarget, resolveHandlerUrls, resolveUnknownHandlerPathFallbackUrl } from \"./url-targets\";\n\ndescribe(\"handler URL targets\", () => {\n afterEach(() => {\n vi.unstubAllEnvs();\n });\n\n it(\"treats handler-component targets the same as omitted values\", () => {\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: \"/custom-handler\",\n signIn: { type: \"handler-component\" },\n },\n });\n\n expect(urls.signIn).toBe(\"/custom-handler/sign-in\");\n });\n\n it(\"treats custom v0 page targets like legacy string targets\", () => {\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: \"/custom-handler\",\n signIn: { type: \"handler-component\" },\n signUp: { type: \"custom\", url: \"/sign-up-explicit\", version: 0 },\n },\n });\n\n expect(urls.signIn).toBe(\"/custom-handler/sign-in\");\n expect(urls.signUp).toBe(\"/sign-up-explicit\");\n });\n\n it(\"throws on v0 custom target for handler page\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: { type: \"custom\", url: \"/custom-handler\", version: 0 },\n },\n })).toThrowError(/cannot be a custom page/);\n });\n\n it(\"supports the latest documented custom target version\", () => {\n const signInPrompt = getPagePrompt(\"signIn\");\n if (signInPrompt == null) {\n throw new Error(\"Expected signIn prompt metadata to exist\");\n }\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n signIn: { type: \"custom\", url: \"/custom-sign-in\", version: signInPrompt.latestVersion },\n },\n });\n\n expect(urls.signIn).toBe(\"/custom-sign-in\");\n });\n\n it(\"throws on custom target versions newer than the latest supported version\", () => {\n const signInPrompt = getPagePrompt(\"signIn\");\n if (signInPrompt == null) {\n throw new Error(\"Expected signIn prompt metadata to exist\");\n }\n\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n signIn: { type: \"custom\", url: \"/custom-sign-in\", version: signInPrompt.latestVersion + 1 },\n },\n })).toThrowError(/Unsupported custom page version/);\n });\n\n it(\"throws on non-zero custom version for handler page\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: { type: \"custom\", url: \"/custom-handler\", version: 1 },\n },\n })).toThrowError(/cannot be a custom page/);\n });\n\n it(\"uses hosted defaults for unspecified URLs\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n signUp: \"/sign-up\",\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signUp).toBe(\"/sign-up\");\n expect(urls.signIn).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-in\");\n expect(urls.cliAuthConfirm).toBe(\"https://project-id.example-stack-hosted.test/handler/cli-auth-confirm\");\n });\n\n it(\"keeps redirect-only post-auth targets local even when the default target is hosted\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signIn).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-in\");\n expect(urls.signOut).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-out\");\n expect(urls.afterSignIn).toBe(\"/\");\n expect(urls.afterSignUp).toBe(\"/\");\n expect(urls.afterSignOut).toBe(\"/\");\n });\n\n it(\"rejects absolute OAuth callback string targets\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n oauthCallback: \"https://app.example.test/oauth-callback\",\n },\n })).toThrowErrorMatchingInlineSnapshot(`\n [HexclaveAssertionError: OAuth callback URLs must be relative.\n\n This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]\n `);\n });\n\n it(\"rejects absolute OAuth callback custom targets\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n oauthCallback: { type: \"custom\", url: \"https://app.example.test/oauth-callback\", version: 0 },\n },\n })).toThrowErrorMatchingInlineSnapshot(`\n [HexclaveAssertionError: OAuth callback URLs must be relative.\n\n This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]\n `);\n });\n\n it(\"inherits a hosted default target for the OAuth callback\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signIn).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-in\");\n expect(urls.oauthCallback).toBe(\"https://project-id.example-stack-hosted.test/handler/oauth-callback\");\n });\n\n it(\"supports custom CLI auth confirmation targets\", () => {\n const cliAuthConfirmPrompt = getPagePrompt(\"cliAuthConfirm\");\n if (cliAuthConfirmPrompt == null) {\n throw new Error(\"Expected cliAuthConfirm prompt metadata to exist\");\n }\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n cliAuthConfirm: { type: \"custom\", url: \"/cli/authorize\", version: cliAuthConfirmPrompt.latestVersion },\n },\n });\n\n expect(urls.cliAuthConfirm).toBe(\"/cli/authorize\");\n });\n\n it(\"builds CLI auth login URLs from the resolved confirmation target\", () => {\n expect(buildCliAuthConfirmUrl({\n cliAuthConfirmUrl: \"/cli/authorize\",\n appUrl: \"https://app.example.test/base\",\n loginCode: \"login-code\",\n })).toBe(\"https://app.example.test/cli/authorize?login_code=login-code\");\n });\n\n it(\"uses default target for unknown /handler/* pages\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const url = resolveUnknownHandlerPathFallbackUrl({\n defaultTarget: { type: \"hosted\" },\n projectId: \"project-id\",\n unknownPath: \"custom-page\",\n });\n\n expect(url).toBe(\"https://project-id.example-stack-hosted.test/handler/custom-page\");\n });\n\n it(\"uses the full hosted handler URL template when configured\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE\", \"http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}\");\n vi.stubEnv(\"NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX\", \"93\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signIn).toBe(\"http://project-id.localhost:9309/handler/sign-in\");\n expect(urls.accountSettings).toBe(\"http://project-id.localhost:9309/handler/account-settings\");\n });\n\n it(\"validates the hosted handler URL template placeholders\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE\", \"http://localhost:9309/{projectId}/handler\");\n\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n })).toThrowError(/\\{projectId\\} and \\{hostedPath\\}/);\n });\n\n it(\"rejects hosted handler URL templates that put the project ID in the path\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE\", \"http://localhost:9309/{projectId}/{hostedPath}\");\n\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n })).toThrowErrorMatchingInlineSnapshot(`\n [HexclaveAssertionError: The hosted handler URL template must put {projectId} in the hostname.\n\n This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]\n `);\n });\n});\n\ndescribe(\"isLocalHandlerUrlTarget\", () => {\n it(\"treats relative handler URLs as local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"/handler/sign-in\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(true);\n });\n\n it(\"treats same-origin absolute handler URLs as local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"http://p91.localhost:9101/handler/sign-in\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(true);\n });\n\n it(\"treats cross-origin absolute handler URLs as non-local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"https://project-id.built-with-hexclave.com/handler/sign-in\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(false);\n });\n\n it(\"treats non-handler paths as non-local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"/projects\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(false);\n });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"url-targets.test.js","names":[],"sources":["../../../src/lib/hexclave-app/url-targets.test.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { buildCliAuthConfirmUrl, getPagePrompt, isLocalHandlerUrlTarget, resolveHandlerUrls, resolveUnknownHandlerPathFallbackUrl } from \"./url-targets\";\n\ndescribe(\"handler URL targets\", () => {\n afterEach(() => {\n vi.unstubAllEnvs();\n });\n\n it(\"treats handler-component targets the same as omitted values\", () => {\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: \"/custom-handler\",\n signIn: { type: \"handler-component\" },\n },\n });\n\n expect(urls.signIn).toBe(\"/custom-handler/sign-in\");\n });\n\n it(\"treats custom v0 page targets like legacy string targets\", () => {\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: \"/custom-handler\",\n signIn: { type: \"handler-component\" },\n signUp: { type: \"custom\", url: \"/sign-up-explicit\", version: 0 },\n },\n });\n\n expect(urls.signIn).toBe(\"/custom-handler/sign-in\");\n expect(urls.signUp).toBe(\"/sign-up-explicit\");\n });\n\n it(\"throws on v0 custom target for handler page\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: { type: \"custom\", url: \"/custom-handler\", version: 0 },\n },\n })).toThrowError(/cannot be a custom page/);\n });\n\n it(\"supports the latest documented custom target version\", () => {\n const signInPrompt = getPagePrompt(\"signIn\");\n if (signInPrompt == null) {\n throw new Error(\"Expected signIn prompt metadata to exist\");\n }\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n signIn: { type: \"custom\", url: \"/custom-sign-in\", version: signInPrompt.latestVersion },\n },\n });\n\n expect(urls.signIn).toBe(\"/custom-sign-in\");\n });\n\n it(\"throws on custom target versions newer than the latest supported version\", () => {\n const signInPrompt = getPagePrompt(\"signIn\");\n if (signInPrompt == null) {\n throw new Error(\"Expected signIn prompt metadata to exist\");\n }\n\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n signIn: { type: \"custom\", url: \"/custom-sign-in\", version: signInPrompt.latestVersion + 1 },\n },\n })).toThrowError(/Unsupported custom page version/);\n });\n\n it(\"throws on non-zero custom version for handler page\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n handler: { type: \"custom\", url: \"/custom-handler\", version: 1 },\n },\n })).toThrowError(/cannot be a custom page/);\n });\n\n it(\"uses hosted defaults for unspecified URLs\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n signUp: \"/sign-up\",\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signUp).toBe(\"/sign-up\");\n expect(urls.signIn).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-in\");\n expect(urls.cliAuthConfirm).toBe(\"https://project-id.example-stack-hosted.test/handler/cli-auth-confirm\");\n });\n\n it(\"keeps redirect-only post-auth targets local even when the default target is hosted\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signIn).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-in\");\n expect(urls.signOut).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-out\");\n expect(urls.home).toBe(\"/\");\n expect(urls.afterSignIn).toBe(\"/\");\n expect(urls.afterSignUp).toBe(\"/\");\n expect(urls.afterSignOut).toBe(\"/\");\n });\n\n it(\"rejects absolute OAuth callback string targets\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n oauthCallback: \"https://app.example.test/oauth-callback\",\n },\n })).toThrowErrorMatchingInlineSnapshot(`\n [HexclaveAssertionError: OAuth callback URLs must be relative.\n\n This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]\n `);\n });\n\n it(\"rejects absolute OAuth callback custom targets\", () => {\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n oauthCallback: { type: \"custom\", url: \"https://app.example.test/oauth-callback\", version: 0 },\n },\n })).toThrowErrorMatchingInlineSnapshot(`\n [HexclaveAssertionError: OAuth callback URLs must be relative.\n\n This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]\n `);\n });\n\n it(\"inherits a hosted default target for the OAuth callback\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signIn).toBe(\"https://project-id.example-stack-hosted.test/handler/sign-in\");\n expect(urls.oauthCallback).toBe(\"https://project-id.example-stack-hosted.test/handler/oauth-callback\");\n });\n\n it(\"supports custom CLI auth confirmation targets\", () => {\n const cliAuthConfirmPrompt = getPagePrompt(\"cliAuthConfirm\");\n if (cliAuthConfirmPrompt == null) {\n throw new Error(\"Expected cliAuthConfirm prompt metadata to exist\");\n }\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n cliAuthConfirm: { type: \"custom\", url: \"/cli/authorize\", version: cliAuthConfirmPrompt.latestVersion },\n },\n });\n\n expect(urls.cliAuthConfirm).toBe(\"/cli/authorize\");\n });\n\n it(\"builds CLI auth login URLs from the resolved confirmation target\", () => {\n expect(buildCliAuthConfirmUrl({\n cliAuthConfirmUrl: \"/cli/authorize\",\n appUrl: \"https://app.example.test/base\",\n loginCode: \"login-code\",\n })).toBe(\"https://app.example.test/cli/authorize?login_code=login-code\");\n });\n\n it(\"uses default target for unknown /handler/* pages\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX\", \".example-stack-hosted.test\");\n\n const url = resolveUnknownHandlerPathFallbackUrl({\n defaultTarget: { type: \"hosted\" },\n projectId: \"project-id\",\n unknownPath: \"custom-page\",\n });\n\n expect(url).toBe(\"https://project-id.example-stack-hosted.test/handler/custom-page\");\n });\n\n it(\"uses the full hosted handler URL template when configured\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE\", \"http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}\");\n vi.stubEnv(\"NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX\", \"93\");\n\n const urls = resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n });\n\n expect(urls.signIn).toBe(\"http://project-id.localhost:9309/handler/sign-in\");\n expect(urls.accountSettings).toBe(\"http://project-id.localhost:9309/handler/account-settings\");\n });\n\n it(\"validates the hosted handler URL template placeholders\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE\", \"http://localhost:9309/{projectId}/handler\");\n\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n })).toThrowError(/\\{projectId\\} and \\{hostedPath\\}/);\n });\n\n it(\"rejects hosted handler URL templates that put the project ID in the path\", () => {\n vi.stubEnv(\"NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE\", \"http://localhost:9309/{projectId}/{hostedPath}\");\n\n expect(() => resolveHandlerUrls({\n projectId: \"project-id\",\n urls: {\n default: { type: \"hosted\" },\n },\n })).toThrowErrorMatchingInlineSnapshot(`\n [HexclaveAssertionError: The hosted handler URL template must put {projectId} in the hostname.\n\n This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]\n `);\n });\n});\n\ndescribe(\"isLocalHandlerUrlTarget\", () => {\n it(\"treats relative handler URLs as local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"/handler/sign-in\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(true);\n });\n\n it(\"treats same-origin absolute handler URLs as local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"http://p91.localhost:9101/handler/sign-in\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(true);\n });\n\n it(\"treats cross-origin absolute handler URLs as non-local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"https://project-id.built-with-hexclave.com/handler/sign-in\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(false);\n });\n\n it(\"treats non-handler paths as non-local targets\", () => {\n expect(isLocalHandlerUrlTarget({\n targetUrl: \"/projects\",\n handlerPath: \"/handler\",\n currentOrigin: \"http://p91.localhost:9101\",\n })).toBe(false);\n });\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
3
3
  "name": "@hexclave/next",
4
- "version": "1.0.9",
4
+ "version": "1.0.11",
5
5
  "repository": "https://github.com/hexclave/hexclave",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -16,6 +16,15 @@
16
16
  "default": "./dist/index.js"
17
17
  }
18
18
  },
19
+ "./config": {
20
+ "types": "./dist/config.d.ts",
21
+ "import": {
22
+ "default": "./dist/esm/config.js"
23
+ },
24
+ "require": {
25
+ "default": "./dist/config.js"
26
+ }
27
+ },
19
28
  "./convex.config": {
20
29
  "types": "./dist/integrations/convex/component/convex.config.d.ts",
21
30
  "import": {
@@ -66,9 +75,9 @@
66
75
  "rrweb": "^1.1.3",
67
76
  "tsx": "^4.21.0",
68
77
  "yup": "^1.7.1",
69
- "@hexclave/shared": "1.0.9",
70
- "@hexclave/ui": "1.0.9",
71
- "@hexclave/sc": "1.0.9"
78
+ "@hexclave/sc": "1.0.11",
79
+ "@hexclave/ui": "1.0.11",
80
+ "@hexclave/shared": "1.0.11"
72
81
  },
73
82
  "peerDependencies": {
74
83
  "@types/react": ">=18.0.0",
package/src/config.ts ADDED
@@ -0,0 +1,17 @@
1
+
2
+ //===========================================
3
+ // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
4
+ //===========================================
5
+ // Lightweight, side-effect-free entrypoint for authoring `hexclave.config.ts`
6
+ // files. Importing from here (e.g. `@hexclave/next/config`) gives you the
7
+ // `defineHexclaveConfig` helper and config types WITHOUT pulling in the
8
+ // framework runtime (React, server-only, Next.js internals). That matters
9
+ // because tooling such as the local dashboard evaluates your config file in a
10
+ // plain Node context — importing `defineHexclaveConfig` from the package root
11
+ // would drag in the whole SDK and fail to load.
12
+ //
13
+ // Hexclave aliases and legacy Stack* names — @deprecated JSDoc lives on the
14
+ // original declarations in @hexclave/shared/config so it survives dts bundling
15
+ // (per-specifier JSDoc on re-exports does not).
16
+ export type { HexclaveConfig, StackConfig } from "@hexclave/shared/config";
17
+ export { defineHexclaveConfig, defineStackConfig, showOnboardingHexclaveConfigValue } from "@hexclave/shared/config";
@@ -104,8 +104,12 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
104
104
  private readonly _svixTokenCache = createCache(async () => {
105
105
  return await this._interface.getSvixToken();
106
106
  });
107
- private readonly _metricsCache = createCache(async ([includeAnonymous]: [boolean]) => {
108
- return await this._interface.getMetrics(includeAnonymous);
107
+ // Cache key serializes filters via URLSearchParams (sorted keys) so
108
+ // DependenciesMap (identity-keyed per array slot) treats two equal
109
+ // filter objects as the same deterministic string entry.
110
+ private readonly _metricsCache = createCache(async ([includeAnonymous, filtersKey]: [boolean, string]) => {
111
+ const filters = filtersKey ? Object.fromEntries(new URLSearchParams(filtersKey)) : undefined;
112
+ return await this._interface.getMetrics(includeAnonymous, filters);
109
113
  });
110
114
  private readonly _userActivityCache = createCache(async ([userId]: [string]) => {
111
115
  return await this._interface.getUserActivity(userId);
@@ -556,8 +560,7 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
556
560
  protected override async _refreshUsers() {
557
561
  await Promise.all([
558
562
  super._refreshUsers(),
559
- this._metricsCache.refresh([false]),
560
- this._metricsCache.refresh([true]),
563
+ this._metricsCache.refreshWhere(() => true),
561
564
  this._metricsUserCountsCache.refresh([]),
562
565
  ]);
563
566
  }
@@ -565,8 +568,20 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
565
568
  get [hexclaveAppInternalsSymbol]() {
566
569
  return {
567
570
  ...super[hexclaveAppInternalsSymbol],
568
- useMetrics: (includeAnonymous: boolean = false): MetricsResponse => {
569
- return useAsyncCache(this._metricsCache, [includeAnonymous] as const, "adminApp.useMetrics()") as MetricsResponse;
571
+ useMetrics: (
572
+ includeAnonymous: boolean = false,
573
+ filters?: { country_code?: string, referrer?: string, browser?: string, os?: string, device?: string, since?: string, until?: string },
574
+ ): MetricsResponse => {
575
+ const filtersKey = (() => {
576
+ if (filters == null) return "";
577
+ const params = new URLSearchParams();
578
+ for (const key of ["browser", "country_code", "device", "os", "referrer", "since", "until"] as const) {
579
+ const v = filters[key];
580
+ if (v != null) params.set(key, v);
581
+ }
582
+ return params.toString();
583
+ })();
584
+ return useAsyncCache(this._metricsCache, [includeAnonymous, filtersKey] as const, "adminApp.useMetrics()") as MetricsResponse;
570
585
  },
571
586
  useUserActivity: (userId: string): UserActivityResponse => {
572
587
  return useAsyncCache(this._userActivityCache, [userId] as const, "adminApp.useUserActivity()") as UserActivityResponse;
@@ -1501,10 +1501,14 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1501
1501
  const tokenStore = this._getOrCreateTokenStore(await this._createCookieHelper());
1502
1502
  tokenStore.set(tokens);
1503
1503
 
1504
- // Pre-fetch the current user for the new session so the cache is already
1505
- // populated when useUser() re-renders, avoiding a stale-cache render cycle.
1506
- const newSession = this._getSessionFromTokenStore(tokenStore);
1507
- this._currentUserCache.getOrWait([newSession], "write-only").catch(() => {});
1504
+ // If these tokens resolve to a session we already have (eg. the RDE dashboard re-installing a freshly minted
1505
+ // access token for the same access-only session), push the new token into it in place; constructing a new
1506
+ // session here would cold-invalidate every session-scoped cache and suspend the UI on each refresh.
1507
+ const session = this._getSessionFromTokenStore(tokenStore);
1508
+ session.updateAccessToken(tokens);
1509
+
1510
+ // Pre-fetch the current user so the cache is warm when useUser() re-renders (write-only, so it never suspends).
1511
+ runAsynchronously(this._currentUserCache.getOrWait([session], "write-only"));
1508
1512
  }
1509
1513
 
1510
1514
  protected _getTokenStoreInitForFreshTokens(tokens: { accessToken: string | null, refreshToken: string }): TokenStoreInit | undefined {
@@ -128,6 +128,7 @@ export class EventTracker {
128
128
  viewport_height: window.innerHeight,
129
129
  screen_width: screenObject.width,
130
130
  screen_height: screenObject.height,
131
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : null,
131
132
  },
132
133
  });
133
134
  }
@@ -112,6 +112,7 @@ describe("handler URL targets", () => {
112
112
 
113
113
  expect(urls.signIn).toBe("https://project-id.example-stack-hosted.test/handler/sign-in");
114
114
  expect(urls.signOut).toBe("https://project-id.example-stack-hosted.test/handler/sign-out");
115
+ expect(urls.home).toBe("/");
115
116
  expect(urls.afterSignIn).toBe("/");
116
117
  expect(urls.afterSignUp).toBe("/");
117
118
  expect(urls.afterSignOut).toBe("/");
@@ -125,6 +125,7 @@ const isRelativeUrlString = (url: string): boolean => {
125
125
  };
126
126
 
127
127
  const nonHostedHandlerNames = new Set<keyof HandlerUrls>([
128
+ "home",
128
129
  "afterSignIn",
129
130
  "afterSignUp",
130
131
  "afterSignOut",