@marktiderman/genesis-native 0.1.0 → 0.3.0

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 (87) hide show
  1. package/README.md +354 -10
  2. package/dist/block-C5-noMhB.d.ts +223 -0
  3. package/dist/blocks-a1YQ1Elf.d.ts +1540 -0
  4. package/dist/chunk-65P7RU4V.js +49 -0
  5. package/dist/chunk-65P7RU4V.js.map +1 -0
  6. package/dist/chunk-6BT4PTGH.js +37 -0
  7. package/dist/chunk-6BT4PTGH.js.map +1 -0
  8. package/dist/chunk-75YP72G6.js +374 -0
  9. package/dist/chunk-75YP72G6.js.map +1 -0
  10. package/dist/chunk-NGYNYJPL.js +90 -0
  11. package/dist/chunk-NGYNYJPL.js.map +1 -0
  12. package/dist/chunk-VKTIHNAV.js +95 -0
  13. package/dist/chunk-VKTIHNAV.js.map +1 -0
  14. package/dist/{chunk-MMTPWUWI.js → chunk-VTJDIKFC.js} +9 -6
  15. package/dist/chunk-VTJDIKFC.js.map +1 -0
  16. package/dist/chunk-XE7LC7K6.js +29 -0
  17. package/dist/chunk-XE7LC7K6.js.map +1 -0
  18. package/dist/dev-warnings.d.ts +34 -0
  19. package/dist/dev-warnings.js +15 -0
  20. package/dist/dev-warnings.js.map +1 -0
  21. package/dist/devtools.d.ts +144 -0
  22. package/dist/devtools.js +390 -0
  23. package/dist/devtools.js.map +1 -0
  24. package/dist/index.d.ts +12 -368
  25. package/dist/index.js +88 -191
  26. package/dist/index.js.map +1 -1
  27. package/dist/observability.d.ts +170 -0
  28. package/dist/observability.js +123 -0
  29. package/dist/observability.js.map +1 -0
  30. package/dist/persist.d.ts +1 -1
  31. package/dist/persist.js +1 -1
  32. package/dist/push.d.ts +169 -0
  33. package/dist/push.js +996 -0
  34. package/dist/push.js.map +1 -0
  35. package/dist/{query-DhH3lLFa.d.ts → query-DUmVAfsF.d.ts} +11 -6
  36. package/dist/updates.d.ts +8 -1
  37. package/dist/updates.js +44 -69
  38. package/dist/updates.js.map +1 -1
  39. package/package.json +222 -5
  40. package/src/app.tsx +491 -0
  41. package/src/auth.tsx +176 -0
  42. package/src/blocks.tsx +144 -0
  43. package/src/boot.tsx +188 -0
  44. package/src/dev-warnings.ts +43 -0
  45. package/src/devtools/block.tsx +488 -0
  46. package/src/devtools/gestures.ts +107 -0
  47. package/src/devtools/index.ts +22 -0
  48. package/src/devtools/info.ts +45 -0
  49. package/src/devtools/slots.ts +21 -0
  50. package/src/errors.tsx +195 -0
  51. package/src/held-link.ts +90 -0
  52. package/src/hydration-gate.ts +88 -0
  53. package/src/index.ts +83 -0
  54. package/src/links.ts +140 -0
  55. package/src/nativewind.d.ts +41 -0
  56. package/src/observability/block.tsx +300 -0
  57. package/src/observability/events.ts +89 -0
  58. package/src/observability/index.ts +44 -0
  59. package/src/observability/scrub.ts +166 -0
  60. package/src/observability/sentry.ts +222 -0
  61. package/src/observability/sink.ts +147 -0
  62. package/src/persist.ts +83 -0
  63. package/src/push/backend.ts +129 -0
  64. package/src/push/block.tsx +545 -0
  65. package/src/push/index.ts +102 -0
  66. package/src/push/modules.ts +111 -0
  67. package/src/push/permission.ts +191 -0
  68. package/src/push/preferences.ts +136 -0
  69. package/src/push/quiet-hours.ts +71 -0
  70. package/src/push/registration.ts +189 -0
  71. package/src/push/routing.ts +27 -0
  72. package/src/push/screens.tsx +46 -0
  73. package/src/push/settings.tsx +378 -0
  74. package/src/query.ts +100 -0
  75. package/src/updates/block.tsx +442 -0
  76. package/src/updates/channels.ts +131 -0
  77. package/src/updates/checker.ts +190 -0
  78. package/src/updates/index.ts +87 -0
  79. package/src/updates/modules.ts +64 -0
  80. package/src/updates/policy.ts +314 -0
  81. package/src/updates/screens.tsx +159 -0
  82. package/src/updates/signature.ts +80 -0
  83. package/src/updates/store.ts +33 -0
  84. package/src/updates/version.ts +39 -0
  85. package/src/updates/whats-new.ts +122 -0
  86. package/dist/block-Dh5ny06W.d.ts +0 -535
  87. package/dist/chunk-MMTPWUWI.js.map +0 -1
@@ -0,0 +1,49 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ // src/blocks.tsx
4
+ function reportToBlocks(blocks, error, report) {
5
+ for (const block of blocks) {
6
+ if (!block.onError) continue;
7
+ try {
8
+ block.onError(error, report);
9
+ } catch {
10
+ }
11
+ }
12
+ }
13
+ var BlocksContext = createContext(null);
14
+ var BlocksProvider = BlocksContext.Provider;
15
+ function useBlocks() {
16
+ const blocks = useContext(BlocksContext);
17
+ if (!blocks) throw new Error("useBlocks: no blocks. Render inside <GenesisNativeApp>.");
18
+ return blocks;
19
+ }
20
+ function useBlockSettings(sections, options = {}) {
21
+ const blocks = useContext(BlocksContext);
22
+ const rows = blocks?.settings ?? [];
23
+ const subtitles = rows.map((row) => row.useSubtitle?.());
24
+ return mergeBlockSettings(sections, rows, subtitles, options);
25
+ }
26
+ function mergeBlockSettings(sections, rows, subtitles = [], options = {}) {
27
+ const out = sections.map((s) => ({ ...s, rows: [...s.rows] }));
28
+ rows.forEach((row, i) => {
29
+ let section = out.find((s) => s.id === row.section);
30
+ if (!section) {
31
+ section = { id: row.section, title: options.sectionTitles?.[row.section] ?? row.section, rows: [] };
32
+ out.push(section);
33
+ }
34
+ const { navigate } = options;
35
+ const href = row.href;
36
+ section.rows.push({
37
+ id: row.id,
38
+ title: row.title,
39
+ subtitle: subtitles[i] ?? row.subtitle,
40
+ testID: row.testID,
41
+ ...href && navigate ? { showChevron: true, onPress: () => navigate(href) } : {}
42
+ });
43
+ });
44
+ return out;
45
+ }
46
+
47
+ export { BlocksProvider, mergeBlockSettings, reportToBlocks, useBlockSettings, useBlocks };
48
+ //# sourceMappingURL=chunk-65P7RU4V.js.map
49
+ //# sourceMappingURL=chunk-65P7RU4V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/blocks.tsx"],"names":[],"mappings":";;;AA0CO,SAAS,cAAA,CAAe,MAAA,EAAgC,KAAA,EAAc,MAAA,EAA2B;AACtG,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AACpB,IAAA,IAAI;AACF,MAAA,KAAA,CAAM,OAAA,CAAQ,OAAO,MAAM,CAAA;AAAA,IAC7B,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,IAAM,aAAA,GAAgB,cAAkD,IAAI,CAAA;AAGrE,IAAM,iBAAiB,aAAA,CAAc;AAQrC,SAAS,SAAA,GAAyC;AACvD,EAAA,MAAM,MAAA,GAAS,WAAW,aAAa,CAAA;AACvC,EAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,MAAM,yDAAyD,CAAA;AACtF,EAAA,OAAO,MAAA;AACT;AAuCO,SAAS,gBAAA,CACd,QAAA,EACA,OAAA,GAAmC,EAAC,EACS;AAC7C,EAAA,MAAM,MAAA,GAAS,WAAW,aAAa,CAAA;AACvC,EAAA,MAAM,IAAA,GAAO,MAAA,EAAQ,QAAA,IAAY,EAAC;AAElC,EAAA,MAAM,YAAY,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,GAAA,CAAI,eAAe,CAAA;AACvD,EAAA,OAAO,kBAAA,CAAmB,QAAA,EAAU,IAAA,EAAM,SAAA,EAAW,OAAO,CAAA;AAC9D;AAGO,SAAS,kBAAA,CACd,UACA,IAAA,EACA,SAAA,GAAoD,EAAC,EACrD,OAAA,GAAmC,EAAC,EACS;AAC7C,EAAA,MAAM,GAAA,GAAmD,QAAA,CAAS,GAAA,CAAI,CAAC,OAAO,EAAE,GAAG,CAAA,EAAG,IAAA,EAAM,CAAC,GAAG,CAAA,CAAE,IAAI,GAAE,CAAE,CAAA;AAC1G,EAAA,IAAA,CAAK,OAAA,CAAQ,CAAC,GAAA,EAAK,CAAA,KAAM;AACvB,IAAA,IAAI,OAAA,GAAU,IAAI,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,IAAI,OAAO,CAAA;AAClD,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,OAAA,GAAU,EAAE,EAAA,EAAI,GAAA,CAAI,OAAA,EAAS,OAAO,OAAA,CAAQ,aAAA,GAAgB,GAAA,CAAI,OAAO,CAAA,IAAK,GAAA,CAAI,OAAA,EAAS,IAAA,EAAM,EAAC,EAAE;AAClG,MAAA,GAAA,CAAI,KAAK,OAAO,CAAA;AAAA,IAClB;AACA,IAAA,MAAM,EAAE,UAAS,GAAI,OAAA;AACrB,IAAA,MAAM,OAAO,GAAA,CAAI,IAAA;AACjB,IAAA,OAAA,CAAQ,KAAK,IAAA,CAAK;AAAA,MAChB,IAAI,GAAA,CAAI,EAAA;AAAA,MACR,OAAO,GAAA,CAAI,KAAA;AAAA,MACX,QAAA,EAAU,SAAA,CAAU,CAAC,CAAA,IAAK,GAAA,CAAI,QAAA;AAAA,MAC9B,QAAQ,GAAA,CAAI,MAAA;AAAA,MACZ,GAAI,IAAA,IAAQ,QAAA,GAAW,EAAE,WAAA,EAAa,IAAA,EAAM,OAAA,EAAS,MAAM,QAAA,CAAS,IAAI,CAAA,EAAE,GAAI;AAAC,KAChF,CAAA;AAAA,EACH,CAAC,CAAA;AACD,EAAA,OAAO,GAAA;AACT","file":"chunk-65P7RU4V.js","sourcesContent":["/**\n * Blocks on native: `defineBlock` declarations (from\n * `@marktiderman/genesis-features/blocks`) with the two things only a native\n * shell can run, boot steps behind the splash and a root around the screens.\n * `<GenesisNativeApp blocks={[…]}>` resolves them once, runs their steps,\n * mounts their roots, puts their slot fills under the app's own `slots`, and\n * hands their Settings rows to `useBlockSettings`.\n */\nimport { createContext, useContext, type ComponentType, type ReactNode } from \"react\";\nimport type { SlotPropsFor, Slots } from \"@marktiderman/genesis-core/slots\";\nimport type { BlockDef, BlockSettingsRow, Contributed, ResolvedBlocks } from \"@marktiderman/genesis-features/blocks\";\nimport type { BootStep } from \"./boot\";\nimport type { ErrorReport } from \"./errors\";\nimport type { NativeAppSlots } from \"./app\";\n\n/** What a block's `Root` is given: the app's slots, with every block's fills merged under them. @stability Beta */\nexport interface NativeBlockRootProps {\n slots?: Slots<NativeAppSlots>;\n slotProps?: SlotPropsFor<NativeAppSlots>;\n children?: ReactNode;\n}\n\n/**\n * A block a native shell can run. `contributes.slots` fills `GenesisNativeApp`\n * slots by name (the app's own `slots` win).\n *\n * @stability Beta\n */\nexport interface NativeBlock<Id extends string = string> extends BlockDef<Id, ComponentType<never>> {\n /** Run behind the splash after the app's own `steps`, before the session. A step that fails fails the boot, so keep them bounded. */\n readonly bootSteps?: readonly BootStep[];\n /** Mounted around the screens, inside the shell's error boundary. The first block's root is the outermost. */\n readonly Root?: ComponentType<NativeBlockRootProps>;\n /**\n * Every error the shell catches (render, global, boot, rejection), before\n * the app's own `onError`. The observability block reports them here. A\n * throw is ignored.\n */\n readonly onError?: (error: Error, report: ErrorReport) => void;\n}\n\n/** @internal Hand an error to each block's `onError`; one that throws never stops the rest. */\nexport function reportToBlocks(blocks: readonly NativeBlock[], error: Error, report: ErrorReport): void {\n for (const block of blocks) {\n if (!block.onError) continue;\n try {\n block.onError(error, report);\n } catch {\n // A failing reporter must not take the error path down with it.\n }\n }\n}\n\nconst BlocksContext = createContext<ResolvedBlocks<NativeBlock> | null>(null);\n\n/** @internal Provided by GenesisNativeApp. */\nexport const BlocksProvider = BlocksContext.Provider;\n\n/**\n * The resolved blocks: their ids, nav entries, Settings rows, routes and the\n * rest, each tagged with its FeatureId. Throws outside `GenesisNativeApp`.\n *\n * @stability Beta\n */\nexport function useBlocks(): ResolvedBlocks<NativeBlock> {\n const blocks = useContext(BlocksContext);\n if (!blocks) throw new Error(\"useBlocks: no blocks. Render inside <GenesisNativeApp>.\");\n return blocks;\n}\n\n/** A Settings section as `SettingsPage` from `@marktiderman/genesis-ui-native` takes it. @stability Beta */\nexport interface BlockSettingsSection<R extends { id: string; title: string } = SettingsRowLike> {\n id: string;\n title?: string;\n footnote?: string;\n rows: R[];\n}\n\n/** The row fields a block can fill. @stability Beta */\nexport interface SettingsRowLike {\n id: string;\n title: string;\n subtitle?: string;\n showChevron?: boolean;\n onPress?: () => void;\n testID?: string;\n}\n\n/** @stability Beta */\nexport interface UseBlockSettingsOptions {\n /** Open a row's `href` (expo-router's `router.push`). Without it, rows with an `href` aren't pressable. */\n navigate?: (href: string) => void;\n /** Titles for sections a block names that `sections` doesn't have. Default the section id. */\n sectionTitles?: Readonly<Record<string, string>>;\n}\n\n/**\n * The app's Settings sections with every block's rows added: each row goes\n * at the end of the section its `section` names (by `order`, then block\n * order), and a section no one has yet is added after the rest.\n *\n * Calls each row's `useSubtitle` hook, so call it unconditionally at the top\n * of the Settings screen. The blocks are fixed for the life of the app, so\n * the hooks run in the same order on every render.\n *\n * @stability Beta\n */\nexport function useBlockSettings<R extends SettingsRowLike>(\n sections: readonly BlockSettingsSection<R>[],\n options: UseBlockSettingsOptions = {},\n): BlockSettingsSection<R | SettingsRowLike>[] {\n const blocks = useContext(BlocksContext);\n const rows = blocks?.settings ?? [];\n // The same hooks in the same order every render: `rows` never changes once the shell has resolved it.\n const subtitles = rows.map((row) => row.useSubtitle?.());\n return mergeBlockSettings(sections, rows, subtitles, options);\n}\n\n/** The merge `useBlockSettings` does, without hooks (subtitles already read). @stability Beta */\nexport function mergeBlockSettings<R extends SettingsRowLike>(\n sections: readonly BlockSettingsSection<R>[],\n rows: readonly Contributed<BlockSettingsRow>[],\n subtitles: readonly (string | null | undefined)[] = [],\n options: UseBlockSettingsOptions = {},\n): BlockSettingsSection<R | SettingsRowLike>[] {\n const out: BlockSettingsSection<R | SettingsRowLike>[] = sections.map((s) => ({ ...s, rows: [...s.rows] }));\n rows.forEach((row, i) => {\n let section = out.find((s) => s.id === row.section);\n if (!section) {\n section = { id: row.section, title: options.sectionTitles?.[row.section] ?? row.section, rows: [] };\n out.push(section);\n }\n const { navigate } = options;\n const href = row.href;\n section.rows.push({\n id: row.id,\n title: row.title,\n subtitle: subtitles[i] ?? row.subtitle,\n testID: row.testID,\n ...(href && navigate ? { showChevron: true, onPress: () => navigate(href) } : {}),\n });\n });\n return out;\n}\n"]}
@@ -0,0 +1,37 @@
1
+ // src/updates/signature.ts
2
+ var UNKNOWN = "unknown";
3
+ var given = (value) => typeof value === "string" && value.trim() !== "" ? value : null;
4
+ function versionSignature({ application, updates, platform, requestedChannel = null }) {
5
+ const version = given(application?.nativeApplicationVersion);
6
+ const build = given(application?.nativeBuildVersion);
7
+ const on = Boolean(updates?.isEnabled);
8
+ const channel = on ? given(updates?.channel) : null;
9
+ const updateId = on ? given(updates?.updateId) : null;
10
+ const runtimeVersion = given(updates?.runtimeVersion);
11
+ const launch = !on ? "off" : updates?.isEmbeddedLaunch ? "embedded" : "update";
12
+ const emergency = on && updates?.isEmergencyLaunch === true;
13
+ const shownChannel = requestedChannel ?? channel;
14
+ const head = `${version ?? UNKNOWN} (${build ?? UNKNOWN})`;
15
+ const parts = [head];
16
+ if (launch === "off") {
17
+ parts.push("updates off");
18
+ } else {
19
+ parts.push(shownChannel ?? "no channel");
20
+ parts.push(launch === "embedded" ? "embedded" : updateId?.slice(0, 8) ?? UNKNOWN);
21
+ }
22
+ if (emergency) parts.push("emergency launch");
23
+ const short = parts.join(" \xB7 ");
24
+ const lines = [
25
+ `Version ${head}`,
26
+ `Platform ${platform}`,
27
+ `Channel ${channel ?? (on ? UNKNOWN : "none")}${requestedChannel && requestedChannel !== channel ? ` (switched to ${requestedChannel})` : ""}`,
28
+ `Runtime ${runtimeVersion ?? (on ? UNKNOWN : "none")}`,
29
+ `Update ${launch === "off" ? "updates off" : launch === "embedded" ? "embedded" : updateId ?? UNKNOWN}`
30
+ ];
31
+ if (emergency) lines.push(`Emergency launch: ${updates?.emergencyLaunchReason ?? UNKNOWN}`);
32
+ return { version, build, platform, channel, requestedChannel, runtimeVersion, updateId, launch, emergency, short, text: lines.join("\n") };
33
+ }
34
+
35
+ export { versionSignature };
36
+ //# sourceMappingURL=chunk-6BT4PTGH.js.map
37
+ //# sourceMappingURL=chunk-6BT4PTGH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/updates/signature.ts"],"names":[],"mappings":";AA2CA,IAAM,OAAA,GAAU,SAAA;AAGhB,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAqD,OAAO,KAAA,KAAU,YAAY,KAAA,CAAM,IAAA,EAAK,KAAM,EAAA,GAAK,KAAA,GAAQ,IAAA;AAExH,SAAS,iBAAiB,EAAE,WAAA,EAAa,SAAS,QAAA,EAAU,gBAAA,GAAmB,MAAK,EAA4C;AACrI,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,WAAA,EAAa,wBAAwB,CAAA;AAC3D,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,WAAA,EAAa,kBAAkB,CAAA;AACnD,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,OAAA,EAAS,SAAS,CAAA;AACrC,EAAA,MAAM,OAAA,GAAU,EAAA,GAAK,KAAA,CAAM,OAAA,EAAS,OAAO,CAAA,GAAI,IAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,EAAA,GAAK,KAAA,CAAM,OAAA,EAAS,QAAQ,CAAA,GAAI,IAAA;AACjD,EAAA,MAAM,cAAA,GAAiB,KAAA,CAAM,OAAA,EAAS,cAAc,CAAA;AACpD,EAAA,MAAM,SAAqC,CAAC,EAAA,GAAK,KAAA,GAAQ,OAAA,EAAS,mBAAmB,UAAA,GAAa,QAAA;AAClG,EAAA,MAAM,SAAA,GAAY,EAAA,IAAM,OAAA,EAAS,iBAAA,KAAsB,IAAA;AACvD,EAAA,MAAM,eAAe,gBAAA,IAAoB,OAAA;AAEzC,EAAA,MAAM,OAAO,CAAA,EAAG,OAAA,IAAW,OAAO,CAAA,EAAA,EAAK,SAAS,OAAO,CAAA,CAAA,CAAA;AACvD,EAAA,MAAM,KAAA,GAAQ,CAAC,IAAI,CAAA;AACnB,EAAA,IAAI,WAAW,KAAA,EAAO;AACpB,IAAA,KAAA,CAAM,KAAK,aAAa,CAAA;AAAA,EAC1B,CAAA,MAAO;AACL,IAAA,KAAA,CAAM,IAAA,CAAK,gBAAgB,YAAY,CAAA;AACvC,IAAA,KAAA,CAAM,IAAA,CAAK,WAAW,UAAA,GAAa,UAAA,GAAc,UAAU,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,IAAK,OAAQ,CAAA;AAAA,EACpF;AACA,EAAA,IAAI,SAAA,EAAW,KAAA,CAAM,IAAA,CAAK,kBAAkB,CAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,QAAK,CAAA;AAE9B,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,WAAW,IAAI,CAAA,CAAA;AAAA,IACf,YAAY,QAAQ,CAAA,CAAA;AAAA,IACpB,CAAA,QAAA,EAAW,OAAA,KAAY,EAAA,GAAK,OAAA,GAAU,MAAA,CAAO,CAAA,EAAG,gBAAA,IAAoB,gBAAA,KAAqB,OAAA,GAAU,CAAA,cAAA,EAAiB,gBAAgB,CAAA,CAAA,CAAA,GAAM,EAAE,CAAA,CAAA;AAAA,IAC5I,CAAA,QAAA,EAAW,cAAA,KAAmB,EAAA,GAAK,OAAA,GAAU,MAAA,CAAO,CAAA,CAAA;AAAA,IACpD,CAAA,OAAA,EAAU,WAAW,KAAA,GAAQ,aAAA,GAAgB,WAAW,UAAA,GAAa,UAAA,GAAc,YAAY,OAAQ,CAAA;AAAA,GACzG;AACA,EAAA,IAAI,WAAW,KAAA,CAAM,IAAA,CAAK,qBAAqB,OAAA,EAAS,qBAAA,IAAyB,OAAO,CAAA,CAAE,CAAA;AAC1F,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,QAAA,EAAU,SAAS,gBAAA,EAAkB,cAAA,EAAgB,QAAA,EAAU,MAAA,EAAQ,WAAW,KAAA,EAAO,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,EAAE;AAC3I","file":"chunk-6BT4PTGH.js","sourcesContent":["/**\n * The version signature: which binary and which update this is, in one\n * line for Settings \"About\" and in a few lines for a support report.\n *\n * 1.4.0 (37) · preview · 3f2a9c1b\n * 1.4.0 (37) · production · embedded\n * 1.4.0 (37) · updates off\n *\n * A value the modules can't give is printed as `unknown`, never left out\n * (an empty string counts as not given: expo-updates gives `\"\"` for the\n * runtime version in a development build); one that doesn't apply, with\n * updates off, as `none`.\n */\nimport type { ApplicationLike, UpdatesModuleLike } from \"./modules\";\n\nexport interface VersionSignature {\n version: string | null;\n build: string | null;\n platform: string;\n /** The build's channel (expo-updates). */\n channel: string | null;\n /** A channel picked on this device (a test build switched channels); `null` when it follows the build. */\n requestedChannel: string | null;\n runtimeVersion: string | null;\n updateId: string | null;\n /** `update`: a downloaded update runs. `embedded`: the binary's own code. `off`: updates are off (development, Expo Go). */\n launch: \"update\" | \"embedded\" | \"off\";\n /** expo-updates fell back to the embedded code after a failed launch. */\n emergency: boolean;\n /** One line, for an About row. */\n short: string;\n /** Several lines, for a support report. */\n text: string;\n}\n\nexport interface VersionSignatureInput {\n application?: ApplicationLike;\n updates?: UpdatesModuleLike;\n /** `Platform.OS`. */\n platform: string;\n requestedChannel?: string | null;\n}\n\nconst UNKNOWN = \"unknown\";\n\n/** A module's value, or null when it gave none (undefined, null or an empty string). */\nconst given = (value: string | null | undefined): string | null => (typeof value === \"string\" && value.trim() !== \"\" ? value : null);\n\nexport function versionSignature({ application, updates, platform, requestedChannel = null }: VersionSignatureInput): VersionSignature {\n const version = given(application?.nativeApplicationVersion);\n const build = given(application?.nativeBuildVersion);\n const on = Boolean(updates?.isEnabled);\n const channel = on ? given(updates?.channel) : null;\n const updateId = on ? given(updates?.updateId) : null;\n const runtimeVersion = given(updates?.runtimeVersion);\n const launch: VersionSignature[\"launch\"] = !on ? \"off\" : updates?.isEmbeddedLaunch ? \"embedded\" : \"update\";\n const emergency = on && updates?.isEmergencyLaunch === true;\n const shownChannel = requestedChannel ?? channel;\n\n const head = `${version ?? UNKNOWN} (${build ?? UNKNOWN})`;\n const parts = [head];\n if (launch === \"off\") {\n parts.push(\"updates off\");\n } else {\n parts.push(shownChannel ?? \"no channel\");\n parts.push(launch === \"embedded\" ? \"embedded\" : (updateId?.slice(0, 8) ?? UNKNOWN));\n }\n if (emergency) parts.push(\"emergency launch\");\n const short = parts.join(\" · \");\n\n const lines = [\n `Version ${head}`,\n `Platform ${platform}`,\n `Channel ${channel ?? (on ? UNKNOWN : \"none\")}${requestedChannel && requestedChannel !== channel ? ` (switched to ${requestedChannel})` : \"\"}`,\n `Runtime ${runtimeVersion ?? (on ? UNKNOWN : \"none\")}`,\n `Update ${launch === \"off\" ? \"updates off\" : launch === \"embedded\" ? \"embedded\" : (updateId ?? UNKNOWN)}`,\n ];\n if (emergency) lines.push(`Emergency launch: ${updates?.emergencyLaunchReason ?? UNKNOWN}`);\n return { version, build, platform, channel, requestedChannel, runtimeVersion, updateId, launch, emergency, short, text: lines.join(\"\\n\") };\n}\n"]}
@@ -0,0 +1,374 @@
1
+ import { useSession } from './chunk-NGYNYJPL.js';
2
+ import { createContext, useContext, useSyncExternalStore, useEffect, useRef } from 'react';
3
+ import { defineBlock } from '@marktiderman/genesis-features/blocks';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/observability/events.ts
7
+ var EVENT_NAME = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/;
8
+ var PROP_NAME = /^[a-z][a-z0-9_]*$/;
9
+ var MAX_PROPS = 25;
10
+ var MAX_LITERAL = 64;
11
+ function defineEvents(schema) {
12
+ const problems = [];
13
+ for (const [name, props] of Object.entries(schema)) {
14
+ if (!EVENT_NAME.test(name)) problems.push(`"${name}": an event name is lower-case words joined by "." (auth.signed_in)`);
15
+ const keys = Object.keys(props);
16
+ if (keys.length > MAX_PROPS) problems.push(`"${name}": at most ${MAX_PROPS} props`);
17
+ for (const key of keys) {
18
+ const spec = props[key];
19
+ if (!PROP_NAME.test(key)) problems.push(`"${name}".${key}: a prop name is lower-case letters, digits and "_"`);
20
+ if (Array.isArray(spec)) {
21
+ if (spec.length === 0) problems.push(`"${name}".${key}: an empty list allows nothing`);
22
+ for (const v of spec) if (typeof v !== "string" || v.length === 0 || v.length > MAX_LITERAL) problems.push(`"${name}".${key}: "${String(v)}" must be 1-${MAX_LITERAL} characters`);
23
+ } else if (spec !== "number" && spec !== "boolean") {
24
+ problems.push(`"${name}".${key}: a prop is a list of strings, "number" or "boolean" (free text is not allowed)`);
25
+ }
26
+ }
27
+ }
28
+ if (problems.length) throw new Error(`Invalid events:
29
+ ${problems.map((p) => ` - ${p}`).join("\n")}`);
30
+ return Object.freeze(schema);
31
+ }
32
+ function checkEvent(schema, name, props) {
33
+ if (!Object.prototype.hasOwnProperty.call(schema, name)) return { ok: false, problem: `"${name}" is not in the event schema` };
34
+ const specs = schema[name] ?? {};
35
+ const out = {};
36
+ for (const [key, value] of Object.entries(props ?? {})) {
37
+ if (value === void 0) continue;
38
+ if (!Object.prototype.hasOwnProperty.call(specs, key)) return { ok: false, problem: `"${name}" has no prop "${key}"` };
39
+ const spec = specs[key];
40
+ const fits = spec === "number" ? typeof value === "number" && Number.isFinite(value) : spec === "boolean" ? typeof value === "boolean" : typeof value === "string" && spec.includes(value);
41
+ if (!fits) return { ok: false, problem: `"${name}".${key}: ${JSON.stringify(value)} is not allowed` };
42
+ out[key] = value;
43
+ }
44
+ return { ok: true, props: out };
45
+ }
46
+
47
+ // src/observability/scrub.ts
48
+ var FILTERED = "[Filtered]";
49
+ var SECRET_KEY_EXACT = /^(session|sessionid|dsn|otp|code|verifier|codeverifier|privatekey|refresh|pwd|key)$/;
50
+ var SECRET_KEY_PART = /(authoriz|cookie|passw|secret|token|apikey|credential|jwt|email|phone|servicerole)/;
51
+ function isSecretKey(key) {
52
+ const k = key.replace(/[-_.\s]/g, "").toLowerCase();
53
+ return SECRET_KEY_EXACT.test(k) || SECRET_KEY_PART.test(k);
54
+ }
55
+ var ID_KEY = /^(event_id|trace_id|span_id|parent_span_id|profile_id|replay_id|debug_id|code_id|public_key|segment_id|sampled|release|dist|environment)$/;
56
+ var DROPPED_KEY = /^(query_string|http\.query|http\.fragment|url\.query|url\.fragment|request_body|response_body|body|cookies|details)$/i;
57
+ var MAX_STRING = 4096;
58
+ var PATTERNS = [
59
+ // The query string and fragment of a URL with a scheme anywhere in text (a span's
60
+ // "GET https://…?id=eq.42", a deep link "myapp://reset?token_hash=…").
61
+ [/\b([a-z][a-z0-9+.-]{0,31}:\/\/[^\s?#"'<>]{0,2048})[?#][^\s"'<>]*/gi, "$1"],
62
+ // …and of a path ("POST /rest/v1/rpc/find?email=eq.…").
63
+ [/(^|[\s"'(=])(\/[^\s?#"'<>]{0,2048})\?[^\s"'<>]*/g, "$1$2"],
64
+ // JWTs: Supabase access tokens and the legacy anon / service_role keys.
65
+ [/\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]*/g, "[jwt]"],
66
+ // Supabase's API keys (sb_secret_…, sb_publishable_…).
67
+ [/\bsb_(?:secret|publishable)_[A-Za-z0-9_-]{8,}/g, "[supabase-key]"],
68
+ // `Authorization: Bearer …` copied into a message.
69
+ [/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 [token]"],
70
+ // Tokens in a query string or a fragment left without a scheme.
71
+ [
72
+ /([?&#](?:access_token|refresh_token|id_token|provider_token|provider_refresh_token|token|token_hash|code|apikey|api_key|key|password|secret|email|phone)=)[^&#\s"']+/gi,
73
+ "$1[Filtered]"
74
+ ],
75
+ // Row values in PostgreSQL errors: `Failing row contains (…)`, `Key (email)=(…)`.
76
+ [/(Failing row contains )\([^)]*\)/g, "$1(\u2026)"],
77
+ [/(Key \([^)]{0,256}\)=)\([^)]*\)/g, "$1(\u2026)"],
78
+ // Email addresses, also percent-encoded (`a%40b.co`). Bounded, so a long run of letters can't backtrack.
79
+ [/[A-Za-z0-9._%+-]{1,64}(?:@|%40)(?:[A-Za-z0-9-]{1,63}\.){1,8}[A-Za-z]{2,24}/g, "[email]"],
80
+ // Phone numbers: international (`+1 555 555 0101`) and North American (`555-555-0101`).
81
+ [/\+\d[\d\s().-]{6,18}\d/g, "[phone]"],
82
+ [/\b\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/g, "[phone]"],
83
+ // Long opaque hex runs: API keys, refresh tokens, DSN secrets. Linear: a run starts only after a non-hex character.
84
+ [/(^|[^A-Fa-f0-9])[A-Fa-f0-9]{32,}(?![A-Fa-f0-9])/g, "$1[hex]"]
85
+ ];
86
+ function scrubString(value) {
87
+ let out = value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}\u2026[truncated]` : value;
88
+ for (const [pattern, replacement] of PATTERNS) out = out.replace(pattern, replacement);
89
+ return out;
90
+ }
91
+ function scrubUrl(value) {
92
+ const cut = value.search(/[?#]/);
93
+ return scrubString(cut === -1 ? value : value.slice(0, cut));
94
+ }
95
+ var URL_KEY = /^(url|href|from|to|uri|description|http\.url|url\.full)$/i;
96
+ var MAX_DEPTH = 8;
97
+ function scrubValue(value, key = "", depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
98
+ if (key && isSecretKey(key)) return value === void 0 || value === null || value === "" ? value : FILTERED;
99
+ if (typeof value === "string") {
100
+ if (key && ID_KEY.test(key)) return value;
101
+ return URL_KEY.test(key) ? scrubUrl(value) : scrubString(value);
102
+ }
103
+ if (value === null || typeof value !== "object") return value;
104
+ if (seen.has(value)) return "[Circular]";
105
+ if (depth >= MAX_DEPTH) return "[Truncated]";
106
+ seen.add(value);
107
+ if (value instanceof Error) return { name: value.name, message: scrubString(value.message) };
108
+ if (Array.isArray(value)) {
109
+ if (value.length === 2 && typeof value[0] === "string" && isSecretKey(value[0])) return [value[0], FILTERED];
110
+ return value.map((item) => scrubValue(item, "", depth + 1, seen));
111
+ }
112
+ const out = {};
113
+ for (const [k, v] of Object.entries(value)) {
114
+ if (DROPPED_KEY.test(k)) continue;
115
+ out[k] = scrubValue(v, k, depth + 1, seen);
116
+ }
117
+ return out;
118
+ }
119
+ function scrubEvent(event) {
120
+ try {
121
+ const { user, request, sdkProcessingMetadata, ...rest } = event;
122
+ const out = scrubValue(rest);
123
+ if (sdkProcessingMetadata !== void 0) out.sdkProcessingMetadata = sdkProcessingMetadata;
124
+ if (user !== void 0) {
125
+ out.user = user && user.id !== void 0 && user.id !== null ? { id: scrubString(String(user.id)) } : null;
126
+ }
127
+ if (request && typeof request === "object") {
128
+ const safe = {};
129
+ if (typeof request.url === "string") safe.url = scrubUrl(request.url);
130
+ if (typeof request.method === "string") safe.method = request.method;
131
+ if (request.headers && typeof request.headers === "object") safe.headers = scrubValue(request.headers);
132
+ out.request = safe;
133
+ }
134
+ return out;
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+ function scrubRecord(record) {
140
+ try {
141
+ return scrubValue(record);
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ // src/observability/sink.ts
148
+ function scrubbedError(error) {
149
+ const copy = new Error(scrubString(error.message));
150
+ copy.name = error.name;
151
+ if (error.stack) copy.stack = scrubString(error.stack);
152
+ return copy;
153
+ }
154
+ function consoleSink(options = {}) {
155
+ const out = options.console ?? console;
156
+ return {
157
+ name: "console",
158
+ // `warn` for what the app survives: `console.error` would raise a LogBox error for every one in development.
159
+ captureError: (error, context) => {
160
+ const line = `[observability] ${context.fatal ? "fatal " : ""}${context.source}: ${error.message}`;
161
+ if (context.fatal) out.error(line);
162
+ else out.warn(line);
163
+ },
164
+ breadcrumb: options.breadcrumbs ? (crumb) => out.log(`[observability] \xB7 ${crumb.category} ${crumb.message ?? ""}`) : void 0,
165
+ setUser: (id) => out.log(`[observability] user ${id ?? "(signed out)"}`),
166
+ event: (name, props) => out.log(`[observability] event ${name}`, props),
167
+ timing: (name, ms, tags) => out.log(`[observability] timing ${name} ${Math.round(ms)}ms`, tags ?? {})
168
+ };
169
+ }
170
+ function observeSink(observe, options = {}) {
171
+ return {
172
+ name: "eas-observe",
173
+ event: (name, props) => observe.logEvent(name, { attributes: { ...props } }),
174
+ timing: (name, ms, tags) => observe.logEvent(`timing.${name}`, { severity: "info", attributes: { duration_ms: Math.round(ms), ...tags ?? {} } }),
175
+ captureError: options.errors && observe.reportError ? (error) => observe.reportError?.(error) : void 0
176
+ };
177
+ }
178
+ function scrubBreadcrumb(crumb) {
179
+ return {
180
+ category: scrubString(crumb.category),
181
+ level: crumb.level,
182
+ timestamp: crumb.timestamp,
183
+ ...crumb.message !== void 0 ? { message: scrubString(crumb.message) } : {},
184
+ ...crumb.data ? { data: scrubValue(crumb.data) } : {}
185
+ };
186
+ }
187
+ var OBSERVABILITY_BLOCK_ID = "observability";
188
+ var TIMING_NAME = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/;
189
+ function createRecentStore(maxCrumbs, maxErrors) {
190
+ let state = { breadcrumbs: [], errors: [] };
191
+ const listeners = /* @__PURE__ */ new Set();
192
+ const emit = () => listeners.forEach((l) => l());
193
+ const push = (list, item, max) => list.length >= max ? [...list.slice(list.length - max + 1), item] : [...list, item];
194
+ return {
195
+ getState: () => state,
196
+ subscribe(listener) {
197
+ listeners.add(listener);
198
+ return () => listeners.delete(listener);
199
+ },
200
+ clear() {
201
+ state = { breadcrumbs: [], errors: [] };
202
+ emit();
203
+ },
204
+ addCrumb(crumb) {
205
+ state = { ...state, breadcrumbs: push(state.breadcrumbs, crumb, maxCrumbs) };
206
+ emit();
207
+ },
208
+ addError(error) {
209
+ state = { ...state, errors: push(state.errors, error, maxErrors) };
210
+ emit();
211
+ }
212
+ };
213
+ }
214
+ function toError(value) {
215
+ return value instanceof Error ? value : new Error(typeof value === "string" ? value : "Unknown error");
216
+ }
217
+ function createObservability(options) {
218
+ const sinks = options.sinks.filter((s) => Boolean(s));
219
+ const now = options.now ?? Date.now;
220
+ const problem = options.onProblem ?? ((message, error) => {
221
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.warn(`[observability] ${message}`, error ?? "");
222
+ });
223
+ const recent = createRecentStore(options.recent?.breadcrumbs ?? 50, options.recent?.errors ?? 20);
224
+ let route = null;
225
+ let user;
226
+ const each = (method, call) => {
227
+ for (const sink of sinks) {
228
+ try {
229
+ call(sink);
230
+ } catch (error) {
231
+ try {
232
+ problem(`${sink.name}.${method} threw`, error);
233
+ } catch {
234
+ }
235
+ }
236
+ }
237
+ };
238
+ const breadcrumb = (input) => {
239
+ let crumb;
240
+ try {
241
+ crumb = scrubBreadcrumb({ ...input, timestamp: input.timestamp ?? now() });
242
+ } catch (error) {
243
+ problem("breadcrumb dropped: it could not be scrubbed", error);
244
+ return;
245
+ }
246
+ recent.addCrumb(crumb);
247
+ each("breadcrumb", (s) => s.breadcrumb?.(crumb));
248
+ };
249
+ const captureError = (value, context = {}) => {
250
+ const error = toError(value);
251
+ const full = { fatal: context.fatal ?? false, source: context.source ?? "app", route: context.route ?? route, componentStack: context.componentStack };
252
+ let safe;
253
+ const scrubbed = () => safe ??= scrubbedError(error);
254
+ try {
255
+ recent.addError({ at: now(), name: error.name, message: scrubString(error.message), source: full.source, fatal: full.fatal });
256
+ } catch {
257
+ }
258
+ const forSinks = full.componentStack ? { ...full, componentStack: scrubString(full.componentStack) } : full;
259
+ each("captureError", (s) => s.captureError?.(s.scrubsErrors ? error : scrubbed(), s.scrubsErrors ? full : forSinks));
260
+ };
261
+ const setUser = (id) => {
262
+ if (id === user) return;
263
+ user = id;
264
+ each("setUser", (s) => s.setUser?.(id));
265
+ };
266
+ const track = (name, props) => {
267
+ const check = options.events ? checkEvent(options.events, name, props) : { ok: false, problem: "no event schema: pass `events: defineEvents({\u2026})`" };
268
+ if (!check.ok) {
269
+ problem(`track dropped: ${check.problem}`);
270
+ return;
271
+ }
272
+ recent.addCrumb({ category: "event", message: name, level: "info", data: check.props, timestamp: now() });
273
+ each("event", (s) => s.event?.(name, check.props));
274
+ };
275
+ const timing = (name, ms, tags) => {
276
+ if (!Number.isFinite(ms) || ms < 0) return;
277
+ if (!TIMING_NAME.test(name)) {
278
+ problem(`timing dropped: "${scrubString(name)}" is not lower-case words joined by "."`);
279
+ return;
280
+ }
281
+ let safeTags;
282
+ try {
283
+ safeTags = tags ? scrubValue(tags) : void 0;
284
+ } catch (error) {
285
+ problem("timing dropped: its tags could not be scrubbed", error);
286
+ return;
287
+ }
288
+ each("timing", (s) => s.timing?.(name, ms, safeTags));
289
+ };
290
+ const declaration = defineBlock({
291
+ id: OBSERVABILITY_BLOCK_ID,
292
+ platforms: ["native"],
293
+ contributes: {
294
+ privacy: {
295
+ collects: [
296
+ // genesis-cli's PRIVACY_BLOCKS.observability mirrors this (with events) for the iOS
297
+ // privacy manifest; observability/block.test.tsx keeps the two the same.
298
+ { type: "crash data", purposes: ["app functionality"], linked: true, tracking: false },
299
+ { type: "performance data", purposes: ["app functionality"], linked: true, tracking: false },
300
+ // Sentry's device and OS details, breadcrumbs and sessions.
301
+ { type: "other diagnostic data", purposes: ["app functionality"], linked: true, tracking: false },
302
+ { type: "user id", purposes: options.events ? ["app functionality", "analytics"] : ["app functionality"], linked: true, tracking: false },
303
+ ...options.events ? [{ type: "product interaction", purposes: ["analytics"], linked: true, tracking: false }] : []
304
+ ],
305
+ notes: [
306
+ "Crash reports, timings and analytics events carry the login's id only; emails, tokens, keys and query strings are removed on the device before anything is sent."
307
+ ]
308
+ }
309
+ }
310
+ });
311
+ const useSegments = options.useSegments;
312
+ function RouteTracker() {
313
+ const segments = useSegments();
314
+ const path = `/${segments.join("/")}`;
315
+ const last = useRef(null);
316
+ useEffect(() => {
317
+ const at = now();
318
+ const previous = last.current;
319
+ if (previous?.path === path) return;
320
+ route = path;
321
+ breadcrumb({ category: "navigation", message: path, data: previous ? { from: previous.path } : void 0, timestamp: at });
322
+ if (previous) timing("route.time_on_screen", at - previous.at, { route: previous.path });
323
+ last.current = { path, at };
324
+ }, [path]);
325
+ return null;
326
+ }
327
+ function UserTracker() {
328
+ const { userId } = useSession();
329
+ useEffect(() => setUser(userId ?? null), [userId]);
330
+ return null;
331
+ }
332
+ const block = {
333
+ ...declaration,
334
+ sinks,
335
+ events: options.events,
336
+ captureError,
337
+ breadcrumb,
338
+ setUser,
339
+ track,
340
+ timing,
341
+ recent,
342
+ wrap(root) {
343
+ return sinks.reduce((wrapped, sink) => sink.wrap ? sink.wrap(wrapped) : wrapped, root);
344
+ },
345
+ onError: (error, report) => captureError(error, { fatal: report.fatal, source: report.source, componentStack: report.componentStack }),
346
+ Root: function ObservabilityRoot({ children }) {
347
+ return /* @__PURE__ */ jsxs(ObservabilityContext.Provider, { value: block, children: [
348
+ /* @__PURE__ */ jsx(UserTracker, {}),
349
+ useSegments ? /* @__PURE__ */ jsx(RouteTracker, {}) : null,
350
+ children
351
+ ] });
352
+ }
353
+ };
354
+ return block;
355
+ }
356
+ var ObservabilityContext = createContext(null);
357
+ function ObservabilityProvider({ block, children }) {
358
+ return /* @__PURE__ */ jsx(ObservabilityContext.Provider, { value: block, children });
359
+ }
360
+ function useObservability() {
361
+ return useContext(ObservabilityContext);
362
+ }
363
+ function useRecentActivity(block) {
364
+ const fromContext = useObservability();
365
+ const store = (block ?? fromContext)?.recent ?? EMPTY_STORE;
366
+ return useSyncExternalStore(store.subscribe, store.getState, store.getState);
367
+ }
368
+ var EMPTY = { breadcrumbs: [], errors: [] };
369
+ var EMPTY_STORE = { getState: () => EMPTY, subscribe: () => () => {
370
+ }};
371
+
372
+ export { FILTERED, OBSERVABILITY_BLOCK_ID, ObservabilityProvider, checkEvent, consoleSink, createObservability, defineEvents, isSecretKey, observeSink, scrubBreadcrumb, scrubEvent, scrubRecord, scrubString, scrubUrl, scrubValue, scrubbedError, useObservability, useRecentActivity };
373
+ //# sourceMappingURL=chunk-75YP72G6.js.map
374
+ //# sourceMappingURL=chunk-75YP72G6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/observability/events.ts","../src/observability/scrub.ts","../src/observability/sink.ts","../src/observability/block.tsx"],"names":[],"mappings":"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","file":"chunk-75YP72G6.js","sourcesContent":["/**\n * Typed analytics events: the app lists every event and the props each may\n * carry once, and `track(name, props)` accepts nothing else. A prop is a\n * closed list of strings, a number or a boolean, never free text, so an\n * event can't carry a name, an email or a message someone typed.\n *\n * ```ts\n * export const events = defineEvents({\n * \"auth.signed_in\": { method: [\"email\", \"apple\"] },\n * \"settings.opened\": {},\n * \"checkin.saved\": { mood: \"number\", first: \"boolean\" },\n * });\n * ```\n *\n * At run time an unknown event, an unknown prop or a value outside its list\n * is dropped and reported (a `console.warn` in development, `onInvalid`\n * always), so a bad call is loud and still sends nothing.\n */\n\n/** A closed list of strings, `\"number\"` (finite) or `\"boolean\"`. */\nexport type EventPropSpec = readonly string[] | \"number\" | \"boolean\";\n\n/** Every event, by name, with the props it may carry. */\nexport type EventSchema = Readonly<Record<string, Readonly<Record<string, EventPropSpec>>>>;\n\ntype PropValue<S> = S extends readonly (infer L)[] ? L : S extends \"number\" ? number : S extends \"boolean\" ? boolean : never;\n\n/** The props of one event: every prop optional, each its spec's type. */\nexport type EventProps<S extends Readonly<Record<string, EventPropSpec>>> = { readonly [K in keyof S]?: PropValue<S[K]> };\n\n/** What a sink receives: the checked props. */\nexport type EventValues = Readonly<Record<string, string | number | boolean>>;\n\n// Lower-case words joined by `.` or `_`: `auth.signed_in`, `checkin.saved`.\nconst EVENT_NAME = /^[a-z][a-z0-9_]*(?:\\.[a-z][a-z0-9_]*)*$/;\nconst PROP_NAME = /^[a-z][a-z0-9_]*$/;\nconst MAX_PROPS = 25;\nconst MAX_LITERAL = 64;\n\n/**\n * Declare the app's events. Checks names (lower-case, dot-separated), prop\n * names, and that each list has at least one short value, and throws listing\n * every problem, so a bad schema fails where it is written.\n *\n * @stability Beta\n */\nexport function defineEvents<const E extends EventSchema>(schema: E): E {\n const problems: string[] = [];\n for (const [name, props] of Object.entries(schema)) {\n if (!EVENT_NAME.test(name)) problems.push(`\"${name}\": an event name is lower-case words joined by \".\" (auth.signed_in)`);\n const keys = Object.keys(props);\n if (keys.length > MAX_PROPS) problems.push(`\"${name}\": at most ${MAX_PROPS} props`);\n for (const key of keys) {\n const spec = props[key];\n if (!PROP_NAME.test(key)) problems.push(`\"${name}\".${key}: a prop name is lower-case letters, digits and \"_\"`);\n if (Array.isArray(spec)) {\n if (spec.length === 0) problems.push(`\"${name}\".${key}: an empty list allows nothing`);\n for (const v of spec) if (typeof v !== \"string\" || v.length === 0 || v.length > MAX_LITERAL) problems.push(`\"${name}\".${key}: \"${String(v)}\" must be 1-${MAX_LITERAL} characters`);\n } else if (spec !== \"number\" && spec !== \"boolean\") {\n problems.push(`\"${name}\".${key}: a prop is a list of strings, \"number\" or \"boolean\" (free text is not allowed)`);\n }\n }\n }\n if (problems.length) throw new Error(`Invalid events:\\n${problems.map((p) => ` - ${p}`).join(\"\\n\")}`);\n return Object.freeze(schema);\n}\n\nexport type EventCheck = { ok: true; props: EventValues } | { ok: false; problem: string };\n\n/** Check one call against the schema: the props to send, or why nothing is sent. */\nexport function checkEvent(schema: EventSchema, name: string, props: Readonly<Record<string, unknown>> | undefined): EventCheck {\n if (!Object.prototype.hasOwnProperty.call(schema, name)) return { ok: false, problem: `\"${name}\" is not in the event schema` };\n const specs = schema[name] ?? {};\n const out: Record<string, string | number | boolean> = {};\n for (const [key, value] of Object.entries(props ?? {})) {\n if (value === undefined) continue;\n if (!Object.prototype.hasOwnProperty.call(specs, key)) return { ok: false, problem: `\"${name}\" has no prop \"${key}\"` };\n const spec = specs[key];\n const fits =\n spec === \"number\"\n ? typeof value === \"number\" && Number.isFinite(value)\n : spec === \"boolean\"\n ? typeof value === \"boolean\"\n : typeof value === \"string\" && (spec as readonly string[]).includes(value);\n if (!fits) return { ok: false, problem: `\"${name}\".${key}: ${JSON.stringify(value)} is not allowed` };\n out[key] = value as string | number | boolean;\n }\n return { ok: true, props: out };\n}\n","/**\n * The PII scrubber every sink's input goes through by default: text is\n * rewritten by pattern (an email, a phone number, a JWT, a Supabase key, a\n * bearer token, a token in a query string, row values in a database error),\n * values under a key that names a secret or a contact detail are replaced\n * whole, and URLs lose their query and fragment (PostgREST filters carry\n * record ids, auth callbacks carry codes). Strings are cut at 4 KB first, so\n * a huge log line can't stall the JS thread.\n *\n * What it can't know: a person's name in free text. Keep names out of error\n * messages and breadcrumbs; touch breadcrumbs lose their labels in\n * `sentrySink` for this reason.\n *\n * Pattern-based on values, not an allowlist of keys: a key allowlist misses\n * `refresh failed: eyJhbGciOi…`, which is the shape that actually occurs.\n *\n * Fail closed: `scrubEvent` and `scrubRecord` return `null` (drop it) when\n * scrubbing itself throws, never the unscrubbed input.\n */\n\n/** What replaces a value under a secret-looking key. */\nexport const FILTERED = \"[Filtered]\";\n\n// Keys whose value is a secret (or a contact detail) whatever it looks like.\n// Matched case-insensitively with `-`, `_`, `.` and spaces removed: exactly\n// for the short, ambiguous ones, as a part of the key for the rest\n// (`x-supabase-api-key`, `user.email`, `refresh_token_hash`).\nconst SECRET_KEY_EXACT = /^(session|sessionid|dsn|otp|code|verifier|codeverifier|privatekey|refresh|pwd|key)$/;\nconst SECRET_KEY_PART = /(authoriz|cookie|passw|secret|token|apikey|credential|jwt|email|phone|servicerole)/;\n\n/** True when a value under `key` is a secret (or contact detail) whatever it looks like. */\nexport function isSecretKey(key: string): boolean {\n const k = key.replace(/[-_.\\s]/g, \"\").toLowerCase();\n return SECRET_KEY_EXACT.test(k) || SECRET_KEY_PART.test(k);\n}\n\n// Sentry's own ids and envelope fields: 32-hex values the hex rule would break, never PII.\nconst ID_KEY = /^(event_id|trace_id|span_id|parent_span_id|profile_id|replay_id|debug_id|code_id|public_key|segment_id|sampled|release|dist|environment)$/;\n\n// Fields dropped whole: query strings travel as their own fields too (Sentry's http breadcrumbs,\n// spans and requests), and bodies and PostgREST `details` carry row values.\nconst DROPPED_KEY = /^(query_string|http\\.query|http\\.fragment|url\\.query|url\\.fragment|request_body|response_body|body|cookies|details)$/i;\n\n/** Longest string scrubbed in full; the rest is cut off (the patterns stay fast on huge logs). */\nexport const MAX_STRING = 4096;\n\nconst PATTERNS: readonly [RegExp, string][] = [\n // The query string and fragment of a URL with a scheme anywhere in text (a span's\n // \"GET https://…?id=eq.42\", a deep link \"myapp://reset?token_hash=…\").\n [/\\b([a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s?#\"'<>]{0,2048})[?#][^\\s\"'<>]*/gi, \"$1\"],\n // …and of a path (\"POST /rest/v1/rpc/find?email=eq.…\").\n [/(^|[\\s\"'(=])(\\/[^\\s?#\"'<>]{0,2048})\\?[^\\s\"'<>]*/g, \"$1$2\"],\n // JWTs: Supabase access tokens and the legacy anon / service_role keys.\n [/\\beyJ[A-Za-z0-9_-]{5,}\\.[A-Za-z0-9_-]{5,}\\.[A-Za-z0-9_-]*/g, \"[jwt]\"],\n // Supabase's API keys (sb_secret_…, sb_publishable_…).\n [/\\bsb_(?:secret|publishable)_[A-Za-z0-9_-]{8,}/g, \"[supabase-key]\"],\n // `Authorization: Bearer …` copied into a message.\n [/\\b(Bearer|Basic)\\s+[A-Za-z0-9._~+/=-]+/gi, \"$1 [token]\"],\n // Tokens in a query string or a fragment left without a scheme.\n [\n /([?&#](?:access_token|refresh_token|id_token|provider_token|provider_refresh_token|token|token_hash|code|apikey|api_key|key|password|secret|email|phone)=)[^&#\\s\"']+/gi,\n \"$1[Filtered]\",\n ],\n // Row values in PostgreSQL errors: `Failing row contains (…)`, `Key (email)=(…)`.\n [/(Failing row contains )\\([^)]*\\)/g, \"$1(…)\"],\n [/(Key \\([^)]{0,256}\\)=)\\([^)]*\\)/g, \"$1(…)\"],\n // Email addresses, also percent-encoded (`a%40b.co`). Bounded, so a long run of letters can't backtrack.\n [/[A-Za-z0-9._%+-]{1,64}(?:@|%40)(?:[A-Za-z0-9-]{1,63}\\.){1,8}[A-Za-z]{2,24}/g, \"[email]\"],\n // Phone numbers: international (`+1 555 555 0101`) and North American (`555-555-0101`).\n [/\\+\\d[\\d\\s().-]{6,18}\\d/g, \"[phone]\"],\n [/\\b\\(?\\d{3}\\)?[-.\\s]\\d{3}[-.\\s]\\d{4}\\b/g, \"[phone]\"],\n // Long opaque hex runs: API keys, refresh tokens, DSN secrets. Linear: a run starts only after a non-hex character.\n [/(^|[^A-Fa-f0-9])[A-Fa-f0-9]{32,}(?![A-Fa-f0-9])/g, \"$1[hex]\"],\n];\n\n/** A string with every URL query, email, phone number, JWT, Supabase key, bearer token and query-string token replaced. */\nexport function scrubString(value: string): string {\n let out = value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}…[truncated]` : value;\n for (const [pattern, replacement] of PATTERNS) out = out.replace(pattern, replacement);\n return out;\n}\n\n/** A URL without its query string and fragment, then scrubbed (an email in a path). */\nexport function scrubUrl(value: string): string {\n const cut = value.search(/[?#]/);\n return scrubString(cut === -1 ? value : value.slice(0, cut));\n}\n\n// Keys whose string value is cut at `?` when it is a URL.\nconst URL_KEY = /^(url|href|from|to|uri|description|http\\.url|url\\.full)$/i;\nconst MAX_DEPTH = 8;\n\n/**\n * A deep copy of `value` with every string scrubbed and every value under a\n * secret key replaced. Cycles become `\"[Circular]\"`; past 8 levels,\n * `\"[Truncated]\"`. Errors become `{ name, message }`. Header lists\n * (`[[\"authorization\", \"…\"]]`) are treated like objects.\n */\nexport function scrubValue(value: unknown, key = \"\", depth = 0, seen: WeakSet<object> = new WeakSet()): unknown {\n if (key && isSecretKey(key)) return value === undefined || value === null || value === \"\" ? value : FILTERED;\n if (typeof value === \"string\") {\n if (key && ID_KEY.test(key)) return value;\n return URL_KEY.test(key) ? scrubUrl(value) : scrubString(value);\n }\n if (value === null || typeof value !== \"object\") return value;\n if (seen.has(value)) return \"[Circular]\";\n if (depth >= MAX_DEPTH) return \"[Truncated]\";\n seen.add(value);\n if (value instanceof Error) return { name: value.name, message: scrubString(value.message) };\n if (Array.isArray(value)) {\n // A `[name, value]` pair (fetch's header lists): the name decides, as for an object key.\n if (value.length === 2 && typeof value[0] === \"string\" && isSecretKey(value[0])) return [value[0], FILTERED];\n return value.map((item) => scrubValue(item, \"\", depth + 1, seen));\n }\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n if (DROPPED_KEY.test(k)) continue;\n out[k] = scrubValue(v, k, depth + 1, seen);\n }\n return out;\n}\n\n/** The fields of a Sentry-shaped event `scrubEvent` treats specially. Anything else is scrubbed by value. */\nexport interface ScrubbableEvent {\n user?: { id?: unknown; [key: string]: unknown } | null;\n request?: { url?: unknown; method?: unknown; headers?: unknown; [key: string]: unknown };\n [key: string]: unknown;\n}\n\n/**\n * The event with the user reduced to its id, the request reduced to its\n * method, URL (cut at `?`) and scrubbed headers, and every other value\n * scrubbed. `null` when scrubbing threw: fail closed, send nothing rather\n * than PII.\n */\nexport function scrubEvent<E extends ScrubbableEvent>(event: E): E | null {\n try {\n // Sentry's processing metadata (scopes, the sampling context its envelope header is built\n // from) is never sent as part of the event: pass it through, or the envelope breaks.\n const { user, request, sdkProcessingMetadata, ...rest } = event;\n const out = scrubValue(rest) as E;\n if (sdkProcessingMetadata !== undefined) (out as ScrubbableEvent).sdkProcessingMetadata = sdkProcessingMetadata;\n if (user !== undefined) {\n (out as ScrubbableEvent).user = user && user.id !== undefined && user.id !== null ? { id: scrubString(String(user.id)) } : null;\n }\n if (request && typeof request === \"object\") {\n const safe: Record<string, unknown> = {};\n if (typeof request.url === \"string\") safe.url = scrubUrl(request.url);\n if (typeof request.method === \"string\") safe.method = request.method;\n if (request.headers && typeof request.headers === \"object\") safe.headers = scrubValue(request.headers);\n (out as ScrubbableEvent).request = safe;\n }\n return out;\n } catch {\n return null;\n }\n}\n\n/** A breadcrumb, span or any record, scrubbed by value. `null` when scrubbing threw (fail closed). */\nexport function scrubRecord<R extends object>(record: R): R | null {\n try {\n return scrubValue(record) as R;\n } catch {\n return null;\n }\n}\n","/**\n * The vendor-neutral seam: a sink is where errors, breadcrumbs, the user id,\n * analytics events and timings go. `createObservability` fans every call out\n * to its sinks, scrubbed first; a sink that throws is reported to\n * `onSinkError` and never takes the others (or the app) down with it.\n *\n * Shipped sinks: `sentrySink` (Sentry), `observeSink` (EAS Observe) and\n * `consoleSink` (development). A vendor adapter (PostHog, Datadog…) is one\n * object with the methods it supports.\n */\nimport type { ComponentType } from \"react\";\nimport { scrubString, scrubValue } from \"./scrub\";\nimport type { EventValues } from \"./events\";\n\n/** Where a caught error came from and whether the app could go on. */\nexport interface ErrorContext {\n /** True when the app could not go on (a render error, a failed boot, a fatal global error). */\n fatal: boolean;\n /** `boundary`, `global`, `boot`, `rejection` (the shell's), or the app's own (`updates:policy`, `sync`). */\n source: string;\n componentStack?: string;\n /** The route pattern on screen (`/(app)/settings`), when known. */\n route?: string | null;\n}\n\nexport type BreadcrumbLevel = \"debug\" | \"info\" | \"warning\" | \"error\";\n\n/** One step that led up to an error: a navigation, a tap, a request. */\nexport interface Breadcrumb {\n /** `navigation`, `ui`, `http`, `event`, or the app's own. */\n category: string;\n message?: string;\n level?: BreadcrumbLevel;\n data?: Readonly<Record<string, unknown>>;\n /** Milliseconds since the epoch. Default now. */\n timestamp?: number;\n}\n\n/** Low-cardinality labels on a timing (`route`, `step`), never ids or text someone typed. */\nexport type TimingTags = Readonly<Record<string, string>>;\n\n/**\n * One destination. Every method is optional: a sink takes what its vendor\n * supports. Errors arrive as an `Error` whose message is already scrubbed,\n * unless the sink sets `scrubsErrors` (it scrubs the whole event itself and\n * needs the original object, as Sentry does to deduplicate).\n *\n * @stability Beta\n */\nexport interface ObservabilitySink {\n readonly name: string;\n /** The sink scrubs error events itself; give it the original `Error`. */\n readonly scrubsErrors?: boolean;\n captureError?(error: Error, context: ErrorContext): void;\n breadcrumb?(crumb: Breadcrumb): void;\n /** The signed-in login's id, or `null` when signed out. Never an email or a name. */\n setUser?(id: string | null): void;\n /** An analytics event, its props checked against the schema. */\n event?(name: string, props: EventValues): void;\n timing?(name: string, ms: number, tags?: TimingTags): void;\n /** Wraps the root layout (Sentry's `wrap`, EAS Observe's `ObserveRoot.wrap`). */\n wrap?<P extends Record<string, unknown>>(root: ComponentType<P>): ComponentType<P>;\n}\n\n/** A copy of `error` whose message and stack have been scrubbed (for sinks that don't scrub themselves). */\nexport function scrubbedError(error: Error): Error {\n const copy = new Error(scrubString(error.message));\n copy.name = error.name;\n if (error.stack) copy.stack = scrubString(error.stack);\n return copy;\n}\n\nexport interface ConsoleSinkOptions {\n /** Default `console`. */\n console?: Pick<Console, \"log\" | \"warn\" | \"error\">;\n /** Also log breadcrumbs (noisy). Default false. */\n breadcrumbs?: boolean;\n}\n\n/**\n * Logs what would be sent, for development: fatal errors with `console.error`, others with `console.warn`,\n * events and timings with `console.log`. Already scrubbed, so what you read\n * is what a vendor sink would get.\n *\n * @stability Beta\n */\nexport function consoleSink(options: ConsoleSinkOptions = {}): ObservabilitySink {\n const out = options.console ?? console;\n return {\n name: \"console\",\n // `warn` for what the app survives: `console.error` would raise a LogBox error for every one in development.\n captureError: (error, context) => {\n const line = `[observability] ${context.fatal ? \"fatal \" : \"\"}${context.source}: ${error.message}`;\n if (context.fatal) out.error(line);\n else out.warn(line);\n },\n breadcrumb: options.breadcrumbs ? (crumb) => out.log(`[observability] · ${crumb.category} ${crumb.message ?? \"\"}`) : undefined,\n setUser: (id) => out.log(`[observability] user ${id ?? \"(signed out)\"}`),\n event: (name, props) => out.log(`[observability] event ${name}`, props),\n timing: (name, ms, tags) => out.log(`[observability] timing ${name} ${Math.round(ms)}ms`, tags ?? {}),\n };\n}\n\n/**\n * `expo-observe`'s `Observe` (SDK 56+), or a stand-in: the calls `observeSink`\n * uses. Pass `import { Observe } from \"expo-observe\"`.\n */\nexport interface ObserveLike {\n logEvent(name: string, options?: { severity?: \"debug\" | \"info\" | \"warn\" | \"error\"; body?: string; attributes?: Record<string, unknown> }): void;\n /** Error reporting, in preview on SDK 57+. */\n reportError?(error: unknown): void;\n}\n\nexport interface ObserveSinkOptions {\n /** Report caught errors with `Observe.reportError` (in preview). Default false: Sentry is the error tracker. */\n errors?: boolean;\n}\n\n/**\n * EAS Observe. It measures launch (cold, warm, bundle load, first render),\n * time to interactive and, with its Expo Router integration, per-route\n * render times on its own; it has no API for a custom timing metric, so\n * `timing` is logged as an event (`timing.<name>` with `duration_ms`) and\n * shows on the Events page, not as a metric. Analytics events go to\n * `Observe.logEvent` as they are.\n *\n * @stability Beta\n */\nexport function observeSink(observe: ObserveLike, options: ObserveSinkOptions = {}): ObservabilitySink {\n return {\n name: \"eas-observe\",\n event: (name, props) => observe.logEvent(name, { attributes: { ...props } }),\n timing: (name, ms, tags) => observe.logEvent(`timing.${name}`, { severity: \"info\", attributes: { duration_ms: Math.round(ms), ...(tags ?? {}) } }),\n captureError: options.errors && observe.reportError ? (error) => observe.reportError?.(error) : undefined,\n };\n}\n\n/** Breadcrumb data scrubbed and made plain (for sinks and the devtools overlay). */\nexport function scrubBreadcrumb(crumb: Breadcrumb): Breadcrumb {\n return {\n category: scrubString(crumb.category),\n level: crumb.level,\n timestamp: crumb.timestamp,\n ...(crumb.message !== undefined ? { message: scrubString(crumb.message) } : {}),\n ...(crumb.data ? { data: scrubValue(crumb.data) as Record<string, unknown> } : {}),\n };\n}\n","/**\n * `createObservability(options)`: the observability block. Build it once at\n * module scope (it starts the sinks, Sentry's `init` included), then:\n *\n * <GenesisNativeApp blocks={[observability]}> every error the shell catches → captureError\n * export default observability.wrap(RootLayout) Sentry's `wrap` (touch and profiler boundaries)\n * observability.track(\"auth.signed_in\", { method: \"email\" })\n *\n * Its root (inside the shell) sets the user to the signed-in login's id and,\n * with `useSegments` from expo-router, leaves a breadcrumb per route change\n * and a `route.time_on_screen` timing for the route left. Route patterns\n * (`/(app)/clients/[id]`) are used, never the values in them.\n *\n * Everything a sink receives has been scrubbed (see `scrub.ts`), and the\n * last breadcrumbs and errors are kept, scrubbed, for the devtools overlay\n * (`recent`).\n */\nimport { createContext, useContext, useEffect, useRef, useSyncExternalStore, type ComponentType, type ReactNode } from \"react\";\nimport { defineBlock } from \"@marktiderman/genesis-features/blocks\";\nimport { useSession } from \"../auth\";\nimport type { NativeBlock, NativeBlockRootProps } from \"../blocks\";\nimport type { ErrorReport } from \"../errors\";\nimport { checkEvent, type EventProps, type EventSchema } from \"./events\";\nimport { scrubString, scrubValue } from \"./scrub\";\nimport { scrubBreadcrumb, scrubbedError, type Breadcrumb, type ErrorContext, type ObservabilitySink, type TimingTags } from \"./sink\";\n\n/** The FeatureId of the observability block. */\nexport const OBSERVABILITY_BLOCK_ID = \"observability\";\n\n/** A caught error as the devtools overlay shows it (scrubbed). */\nexport interface RecentError {\n at: number;\n name: string;\n message: string;\n source: string;\n fatal: boolean;\n}\n\nexport interface RecentActivity {\n /** Oldest first. Navigation, events and the app's breadcrumbs. */\n breadcrumbs: readonly Breadcrumb[];\n /** Oldest first. */\n errors: readonly RecentError[];\n}\n\nexport interface RecentStore {\n getState(): RecentActivity;\n subscribe(listener: () => void): () => void;\n clear(): void;\n}\n\nexport interface ObservabilityOptions<E extends EventSchema = EventSchema> {\n /** Where everything goes: `sentrySink(Sentry, …)`, `consoleSink()` in development, `observeSink(Observe)`. */\n sinks: readonly (ObservabilitySink | null | undefined | false)[];\n /** The app's analytics events (`defineEvents`). Without it, `track` accepts nothing. */\n events?: E;\n /** `useSegments` from expo-router: route breadcrumbs and `route.time_on_screen` timings. */\n useSegments?: () => readonly string[];\n /** How many breadcrumbs and errors `recent` keeps. Default 50 and 20. */\n recent?: { breadcrumbs?: number; errors?: number };\n /** A sink threw, or `track` was called with something the schema doesn't allow. Default `console.warn` in development. */\n onProblem?: (problem: string, error?: unknown) => void;\n now?: () => number;\n}\n\nexport interface ObservabilityBlock<E extends EventSchema = EventSchema> extends NativeBlock<typeof OBSERVABILITY_BLOCK_ID> {\n readonly sinks: readonly ObservabilitySink[];\n readonly events: E | undefined;\n /** Report an error. The shell calls it for every error it catches. */\n captureError(error: unknown, context?: Partial<ErrorContext>): void;\n breadcrumb(crumb: Breadcrumb): void;\n /** The login's id only. The block's root sets it from the session. */\n setUser(id: string | null): void;\n /** An analytics event from the schema. Anything else is dropped and reported. */\n track<N extends keyof E & string>(name: N, props?: EventProps<E[N]>): void;\n timing(name: string, ms: number, tags?: TimingTags): void;\n /** The root layout wrapped by each sink that wraps (Sentry's `wrap`). Use once: `export default observability.wrap(RootLayout)`. */\n wrap<P extends Record<string, unknown>>(root: ComponentType<P>): ComponentType<P>;\n /** The last breadcrumbs and errors, scrubbed, for the devtools overlay. */\n readonly recent: RecentStore;\n /** For the shell: every error it catches. */\n readonly onError: (error: Error, report: ErrorReport) => void;\n readonly Root: ComponentType<NativeBlockRootProps>;\n}\n\ndeclare const __DEV__: boolean | undefined;\n\nconst TIMING_NAME = /^[a-z][a-z0-9_]*(?:\\.[a-z][a-z0-9_]*)*$/;\n\nfunction createRecentStore(maxCrumbs: number, maxErrors: number): RecentStore & { addCrumb(c: Breadcrumb): void; addError(e: RecentError): void } {\n let state: RecentActivity = { breadcrumbs: [], errors: [] };\n const listeners = new Set<() => void>();\n const emit = () => listeners.forEach((l) => l());\n const push = <T,>(list: readonly T[], item: T, max: number) => (list.length >= max ? [...list.slice(list.length - max + 1), item] : [...list, item]);\n return {\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n clear() {\n state = { breadcrumbs: [], errors: [] };\n emit();\n },\n addCrumb(crumb) {\n state = { ...state, breadcrumbs: push(state.breadcrumbs, crumb, maxCrumbs) };\n emit();\n },\n addError(error) {\n state = { ...state, errors: push(state.errors, error, maxErrors) };\n emit();\n },\n };\n}\n\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(typeof value === \"string\" ? value : \"Unknown error\");\n}\n\n/** Build the block once, at module scope, before the root layout renders. @stability Beta */\nexport function createObservability<const E extends EventSchema = EventSchema>(options: ObservabilityOptions<E>): ObservabilityBlock<E> {\n const sinks = options.sinks.filter((s): s is ObservabilitySink => Boolean(s));\n const now = options.now ?? Date.now;\n const problem =\n options.onProblem ??\n ((message: string, error?: unknown) => {\n if (typeof __DEV__ !== \"undefined\" && __DEV__) console.warn(`[observability] ${message}`, error ?? \"\");\n });\n const recent = createRecentStore(options.recent?.breadcrumbs ?? 50, options.recent?.errors ?? 20);\n let route: string | null = null;\n let user: string | null | undefined;\n\n // One sink failing never stops the others, and never throws into the caller (an error handler, a render).\n const each = (method: string, call: (sink: ObservabilitySink) => void) => {\n for (const sink of sinks) {\n try {\n call(sink);\n } catch (error) {\n try {\n problem(`${sink.name}.${method} threw`, error);\n } catch {\n // ignore a failing reporter\n }\n }\n }\n };\n\n // Never throws into the caller (an effect, a handler): a record that can't be scrubbed is dropped.\n const breadcrumb = (input: Breadcrumb) => {\n let crumb: Breadcrumb;\n try {\n crumb = scrubBreadcrumb({ ...input, timestamp: input.timestamp ?? now() });\n } catch (error) {\n problem(\"breadcrumb dropped: it could not be scrubbed\", error);\n return;\n }\n recent.addCrumb(crumb);\n each(\"breadcrumb\", (s) => s.breadcrumb?.(crumb));\n };\n\n const captureError = (value: unknown, context: Partial<ErrorContext> = {}) => {\n const error = toError(value);\n const full: ErrorContext = { fatal: context.fatal ?? false, source: context.source ?? \"app\", route: context.route ?? route, componentStack: context.componentStack };\n let safe: Error | undefined;\n const scrubbed = () => (safe ??= scrubbedError(error));\n try {\n recent.addError({ at: now(), name: error.name, message: scrubString(error.message), source: full.source, fatal: full.fatal });\n } catch {\n // never let the overlay's list stop a report\n }\n const forSinks = full.componentStack ? { ...full, componentStack: scrubString(full.componentStack) } : full;\n each(\"captureError\", (s) => s.captureError?.(s.scrubsErrors ? error : scrubbed(), s.scrubsErrors ? full : forSinks));\n };\n\n const setUser = (id: string | null) => {\n if (id === user) return;\n user = id;\n each(\"setUser\", (s) => s.setUser?.(id));\n };\n\n const track = (name: string, props?: Readonly<Record<string, unknown>>) => {\n const check = options.events ? checkEvent(options.events, name, props) : ({ ok: false, problem: \"no event schema: pass `events: defineEvents({…})`\" } as const);\n if (!check.ok) {\n problem(`track dropped: ${check.problem}`);\n return;\n }\n recent.addCrumb({ category: \"event\", message: name, level: \"info\", data: check.props, timestamp: now() });\n each(\"event\", (s) => s.event?.(name, check.props));\n };\n\n const timing = (name: string, ms: number, tags?: TimingTags) => {\n if (!Number.isFinite(ms) || ms < 0) return;\n // A name like an event's (`route.time_on_screen`), never text with values in it.\n if (!TIMING_NAME.test(name)) {\n problem(`timing dropped: \"${scrubString(name)}\" is not lower-case words joined by \".\"`);\n return;\n }\n let safeTags: TimingTags | undefined;\n try {\n safeTags = tags ? (scrubValue(tags) as TimingTags) : undefined;\n } catch (error) {\n problem(\"timing dropped: its tags could not be scrubbed\", error);\n return;\n }\n each(\"timing\", (s) => s.timing?.(name, ms, safeTags));\n };\n\n const declaration = defineBlock<typeof OBSERVABILITY_BLOCK_ID, ComponentType<never>>({\n id: OBSERVABILITY_BLOCK_ID,\n platforms: [\"native\"],\n contributes: {\n privacy: {\n collects: [\n // genesis-cli's PRIVACY_BLOCKS.observability mirrors this (with events) for the iOS\n // privacy manifest; observability/block.test.tsx keeps the two the same.\n { type: \"crash data\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"performance data\", purposes: [\"app functionality\"], linked: true, tracking: false },\n // Sentry's device and OS details, breadcrumbs and sessions.\n { type: \"other diagnostic data\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"user id\", purposes: options.events ? [\"app functionality\", \"analytics\"] : [\"app functionality\"], linked: true, tracking: false },\n ...(options.events ? [{ type: \"product interaction\", purposes: [\"analytics\"], linked: true, tracking: false }] : []),\n ],\n notes: [\n \"Crash reports, timings and analytics events carry the login's id only; emails, tokens, keys and query strings are removed on the device before anything is sent.\",\n ],\n },\n },\n });\n\n const useSegments = options.useSegments;\n function RouteTracker() {\n const segments = (useSegments as () => readonly string[])();\n const path = `/${segments.join(\"/\")}`;\n const last = useRef<{ path: string; at: number } | null>(null);\n useEffect(() => {\n const at = now();\n const previous = last.current;\n if (previous?.path === path) return;\n route = path;\n breadcrumb({ category: \"navigation\", message: path, data: previous ? { from: previous.path } : undefined, timestamp: at });\n if (previous) timing(\"route.time_on_screen\", at - previous.at, { route: previous.path });\n last.current = { path, at };\n }, [path]);\n return null;\n }\n\n function UserTracker() {\n const { userId } = useSession();\n useEffect(() => setUser(userId ?? null), [userId]);\n return null;\n }\n\n const block: ObservabilityBlock<E> = {\n ...declaration,\n sinks,\n events: options.events,\n captureError,\n breadcrumb,\n setUser,\n track: track as ObservabilityBlock<E>[\"track\"],\n timing,\n recent,\n wrap(root) {\n return sinks.reduce((wrapped, sink) => (sink.wrap ? sink.wrap(wrapped) : wrapped), root);\n },\n onError: (error, report) => captureError(error, { fatal: report.fatal, source: report.source, componentStack: report.componentStack }),\n Root: function ObservabilityRoot({ children }: NativeBlockRootProps) {\n return (\n <ObservabilityContext.Provider value={block as ObservabilityBlock}>\n <UserTracker />\n {useSegments ? <RouteTracker /> : null}\n {children}\n </ObservabilityContext.Provider>\n );\n },\n };\n return block;\n}\n\nconst ObservabilityContext = createContext<ObservabilityBlock | null>(null);\n\n/** Provides a block to the hooks without its root (rung 4). @stability Beta */\nexport function ObservabilityProvider({ block, children }: { block: ObservabilityBlock; children?: ReactNode }) {\n return <ObservabilityContext.Provider value={block}>{children}</ObservabilityContext.Provider>;\n}\n\n/** The block, or `null` when it is off (so a screen can call it whether or not the block is on). @stability Beta */\nexport function useObservability(): ObservabilityBlock | null {\n return useContext(ObservabilityContext);\n}\n\n/** The last breadcrumbs and errors, scrubbed; empty when the block is off. @stability Beta */\nexport function useRecentActivity(block?: ObservabilityBlock | null): RecentActivity {\n const fromContext = useObservability();\n const store = (block ?? fromContext)?.recent ?? EMPTY_STORE;\n return useSyncExternalStore(store.subscribe, store.getState, store.getState);\n}\n\nconst EMPTY: RecentActivity = { breadcrumbs: [], errors: [] };\nconst EMPTY_STORE: RecentStore = { getState: () => EMPTY, subscribe: () => () => {}, clear: () => {} };\n"]}