@guidelytech/electron 0.16.0 → 0.17.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.
package/README.md CHANGED
@@ -42,7 +42,12 @@ Sandboxed preloads (the default since Electron 20) can't `require` npm packages:
42
42
  (esbuild, Vite, webpack) or inline the equivalent:
43
43
 
44
44
  ```ts
45
- contextBridge.exposeInMainWorld("guidelyElectron", { getAppInfo: () => ipcRenderer.invoke("guidely:app-info") });
45
+ contextBridge.exposeInMainWorld("guidelyElectron", {
46
+ getAppInfo: () => ipcRenderer.invoke("guidely:app-info"),
47
+ // Only with main-process crash capture (see "Crashes and errors"):
48
+ takeMainErrors: () => ipcRenderer.invoke("guidely:main-errors").catch(() => []),
49
+ onMainErrors: (cb) => { const l = () => cb(); ipcRenderer.on("guidely:main-errors-ready", l); return () => ipcRenderer.removeListener("guidely:main-errors-ready", l); },
50
+ });
46
51
  ```
47
52
 
48
53
  ## 3. Renderer
@@ -72,9 +77,46 @@ const info = await getElectronAppInfo();
72
77
  | `getElectronAppInfo(timeoutMs = 1000)` | renderer | `{ appName, appVersion, platform, arch, osVersion, electronVersion, chromeVersion, locale, packaged }` or `null` |
73
78
  | `electronOptions(info)` | renderer | `{ pageUrl: normalizeAppUrl, appVersion, release }` |
74
79
  | `desktopTraits(info)` | renderer | `{ appVersion, desktopOs, desktopOsVersion, desktopArch, electronVersion }` |
75
- | `createGuidelyElectron(options)` | renderer | `createGuidely` with all of the above |
80
+ | `createGuidelyElectron(options)` | renderer | `createGuidely` with all of the above, Electron error contexts and main-process errors forwarded |
81
+ | `installGuidelyCrashHandlers({ app, ipcMain, … })` / `setupGuidelyMain({ errors })` | main | Main-process crash capture (below) |
82
+ | `forwardMainErrors(client)`, `setElectronErrorContexts(client, info)` | renderer | What `createGuidelyElectron` does, for clients made another way |
76
83
  | `normalizeAppUrl(href)` | anywhere | The URL mapping (also exported by `@guidelytech/sdk-core`) |
77
84
 
85
+ ## Crashes and errors
86
+
87
+ In the renderer the web SDK reports uncaught errors and rejections as in a browser (options under
88
+ `errors`, `captureException`, breadcrumbs, React/Svelte error boundaries). `createGuidelyElectron`
89
+ adds the `electron` (`version`, `chrome`, `node`), `os` (the real OS version, not the user agent's
90
+ frozen one) and `app` (`app_name`, `app_version`) contexts to every error.
91
+
92
+ The main process has no Guidely client. With `errors`, it records its own crashes in
93
+ `<userData>/guidely-main-errors.json` and a window's client sends them (each one once, the next
94
+ time a window loads, or right away when you pass `webContents`):
95
+
96
+ ```ts
97
+ import { app, crashReporter, ipcMain, webContents } from "electron";
98
+ import { installGuidelyCrashHandlers } from "@guidelytech/electron/main";
99
+
100
+ // First thing in main.ts, so startup errors are kept (or setupGuidelyMain({ app, ipcMain, errors: { … } })).
101
+ const guidelyMain = installGuidelyCrashHandlers({ app, ipcMain, webContents, nativeCrashes: { crashReporter } });
102
+ guidelyMain.captureException(err, { tags: { area: "updater" } }); // main-process code's own errors
103
+ ```
104
+
105
+ | What | `mechanism` | `level` |
106
+ |---|---|---|
107
+ | Uncaught exception (seen through `uncaughtExceptionMonitor`, so Electron's error dialog still shows) | `uncaughtException` | `fatal` |
108
+ | Unhandled rejection (logged, as Node's `warn` mode does) | `unhandledRejection` | `error` |
109
+ | `render-process-gone` (`contexts.electron`: `reason`, `exitCode`, `processType: "renderer"`) | `render-process-gone` | `fatal` for `crashed`, `oom`, `killed`; else `error` |
110
+ | `child-process-gone` (GPU, utility, … `processType` is the process type) | `child-process-gone` | as above |
111
+ | A new Crashpad minidump at the next start (`nativeCrashes`) | `minidump` | `fatal` |
112
+
113
+ `exitOnUncaught: true` logs an uncaught exception and exits with code 1, as plain Node does
114
+ (Electron's default is to show a dialog and keep running). `processHandlers: false` and
115
+ `processGone: false` turn those parts off. `nativeCrashes: { crashReporter }` starts Electron's
116
+ crash reporter with `uploadToServer: false` (pass `true` if you start it yourself); the minidump's
117
+ file name is reported, the dump itself is not uploaded or symbolicated yet. Events are sent through
118
+ the renderer's client, so the renderer's consent and error-tracking settings apply.
119
+
78
120
  ## Page URLs
79
121
 
80
122
  The SDK maps every non-http(s) URL (`file:`, custom protocols) with `normalizeAppUrl`, even without
@@ -1,4 +1,6 @@
1
1
  // src/types.ts
2
+ var MAIN_ERRORS_CHANNEL = "guidely:main-errors";
3
+ var MAIN_ERRORS_READY_CHANNEL = "guidely:main-errors-ready";
2
4
  var APP_INFO_CHANNEL = "guidely:app-info";
3
5
  var BRIDGE_KEY = "guidelyElectron";
4
6
  function osName(platform) {
@@ -9,8 +11,10 @@ function osName(platform) {
9
11
  }
10
12
 
11
13
  export {
14
+ MAIN_ERRORS_CHANNEL,
15
+ MAIN_ERRORS_READY_CHANNEL,
12
16
  APP_INFO_CHANNEL,
13
17
  BRIDGE_KEY,
14
18
  osName
15
19
  };
16
- //# sourceMappingURL=chunk-AGZOVF3N.js.map
20
+ //# sourceMappingURL=chunk-JP3V2XK5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["/** What the main process knows about the desktop app, sent to the renderer over IPC. */\nexport interface ElectronAppInfo {\n /** `app.getName()`. */\n appName: string;\n /** `app.getVersion()` (from package.json). */\n appVersion: string;\n /** `process.platform`: `darwin`, `win32`, `linux`. */\n platform: string;\n /** `process.arch`: `x64`, `arm64`, … */\n arch: string;\n /** `os.release()` / `process.getSystemVersion()`, when available. */\n osVersion?: string;\n electronVersion?: string;\n chromeVersion?: string;\n /** The main process's Node.js version. */\n nodeVersion?: string;\n /** `app.getLocale()`. */\n locale?: string;\n /** `app.isPackaged`: false while running from source (`electron .`). */\n packaged?: boolean;\n}\n\n/** The API the preload script exposes on `window.guidelyElectron`. */\nexport interface GuidelyElectronBridge {\n getAppInfo(): Promise<ElectronAppInfo>;\n /** Main-process errors and crashes waiting to be sent (each is handed to one window only). */\n takeMainErrors?(): Promise<MainErrorEvent[]>;\n /** Called when the main process has new errors to take; returns an unsubscribe function. */\n onMainErrors?(listener: () => void): () => void;\n}\n\n/** An error the main process caught (thrown, rejected, a process gone, a minidump), sent by a renderer's client. */\nexport interface MainErrorEvent {\n id: string;\n /** When it happened (ms since epoch); it may be sent much later, after a restart. */\n timestamp: number;\n type: string;\n message: string;\n stack?: string;\n /** The error's `cause`, serialized the same way. */\n cause?: Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\">;\n level: \"fatal\" | \"error\";\n /** `uncaughtException`, `unhandledRejection`, `render-process-gone`, `child-process-gone`, `minidump`, `generic`. */\n mechanism: string;\n handled: boolean;\n contexts: Record<string, Record<string, unknown>>;\n tags?: Record<string, string>;\n fingerprint?: string[];\n}\n\n/** IPC channel the preload bridge takes main-process errors from. */\nexport const MAIN_ERRORS_CHANNEL = \"guidely:main-errors\";\n/** IPC message the main process sends windows when it has errors to take. */\nexport const MAIN_ERRORS_READY_CHANNEL = \"guidely:main-errors-ready\";\n\n/** IPC channel used between the main-process handler and the preload bridge. */\nexport const APP_INFO_CHANNEL = \"guidely:app-info\";\n/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */\nexport const BRIDGE_KEY = \"guidelyElectron\";\n\n/** Desktop operating system names used in Guidely device properties. */\nexport function osName(platform: string): string {\n if (platform === \"darwin\") return \"macos\";\n if (platform === \"win32\") return \"windows\";\n if (platform === \"linux\") return \"linux\";\n return platform;\n}\n"],"mappings":";AAmDO,IAAM,sBAAsB;AAE5B,IAAM,4BAA4B;AAGlC,IAAM,mBAAmB;AAEzB,IAAM,aAAa;AAGnB,SAAS,OAAO,UAA0B;AAC/C,MAAI,aAAa,SAAU,QAAO;AAClC,MAAI,aAAa,QAAS,QAAO;AACjC,MAAI,aAAa,QAAS,QAAO;AACjC,SAAO;AACT;","names":[]}
package/dist/index.cjs CHANGED
@@ -22,17 +22,24 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  APP_INFO_CHANNEL: () => APP_INFO_CHANNEL,
24
24
  BRIDGE_KEY: () => BRIDGE_KEY,
25
+ MAIN_ERRORS_CHANNEL: () => MAIN_ERRORS_CHANNEL,
26
+ MAIN_ERRORS_READY_CHANNEL: () => MAIN_ERRORS_READY_CHANNEL,
25
27
  createGuidelyElectron: () => createGuidelyElectron,
26
28
  desktopTraits: () => desktopTraits,
29
+ electronErrorContexts: () => electronErrorContexts,
27
30
  electronOptions: () => electronOptions,
31
+ forwardMainErrors: () => forwardMainErrors,
28
32
  getElectronAppInfo: () => getElectronAppInfo,
29
33
  normalizeAppUrl: () => import_sdk_core2.normalizeAppUrl,
30
- osName: () => osName
34
+ osName: () => osName,
35
+ setElectronErrorContexts: () => setElectronErrorContexts
31
36
  });
32
37
  module.exports = __toCommonJS(index_exports);
33
38
  var import_sdk_core = require("@guidelytech/sdk-core");
34
39
 
35
40
  // src/types.ts
41
+ var MAIN_ERRORS_CHANNEL = "guidely:main-errors";
42
+ var MAIN_ERRORS_READY_CHANNEL = "guidely:main-errors-ready";
36
43
  var APP_INFO_CHANNEL = "guidely:app-info";
37
44
  var BRIDGE_KEY = "guidelyElectron";
38
45
  function osName(platform) {
@@ -42,15 +49,87 @@ function osName(platform) {
42
49
  return platform;
43
50
  }
44
51
 
52
+ // src/renderer-errors.ts
53
+ var OS_NAMES = { darwin: "macOS", win32: "Windows", linux: "Linux" };
54
+ function electronErrorContexts(info) {
55
+ if (!info) return {};
56
+ return {
57
+ electron: { ...info.electronVersion ? { version: info.electronVersion } : {}, ...info.chromeVersion ? { chrome: info.chromeVersion } : {}, ...info.nodeVersion ? { node: info.nodeVersion } : {}, processType: "renderer" },
58
+ os: { name: OS_NAMES[info.platform] ?? info.platform, ...info.osVersion ? { version: info.osVersion } : {} },
59
+ app: { app_name: info.appName, app_version: info.appVersion }
60
+ };
61
+ }
62
+ function setElectronErrorContexts(client, info) {
63
+ try {
64
+ for (const [name, context] of Object.entries(electronErrorContexts(info))) client.setContext(name, context);
65
+ } catch {
66
+ }
67
+ }
68
+ function toError(data) {
69
+ const error = new Error(data.message);
70
+ error.name = data.type;
71
+ error.stack = data.stack ?? `${data.type}: ${data.message}`;
72
+ if (data.cause) error.cause = toError(data.cause);
73
+ return error;
74
+ }
75
+ var bridge = () => {
76
+ const b = globalThis[BRIDGE_KEY];
77
+ return b && typeof b.takeMainErrors === "function" ? b : null;
78
+ };
79
+ function forwardMainErrors(client, from = bridge()) {
80
+ if (!from?.takeMainErrors) return () => {
81
+ };
82
+ let busy = false;
83
+ let again = false;
84
+ const take = async () => {
85
+ if (busy) {
86
+ again = true;
87
+ return;
88
+ }
89
+ busy = true;
90
+ try {
91
+ if (client.getState().destroyed) return;
92
+ for (const e of await from.takeMainErrors() ?? []) {
93
+ try {
94
+ client.captureException(toError(e), {
95
+ level: e.level,
96
+ mechanism: { type: e.mechanism, handled: e.handled },
97
+ contexts: e.contexts,
98
+ tags: { process: "main", ...e.tags ?? {} },
99
+ ...e.fingerprint?.length ? { fingerprint: e.fingerprint } : {},
100
+ extra: { occurredAt: new Date(e.timestamp).toISOString() }
101
+ });
102
+ } catch {
103
+ }
104
+ }
105
+ } catch {
106
+ } finally {
107
+ busy = false;
108
+ if (again) {
109
+ again = false;
110
+ void take();
111
+ }
112
+ }
113
+ };
114
+ void take();
115
+ let off = () => {
116
+ };
117
+ try {
118
+ off = from.onMainErrors?.(() => void take()) ?? off;
119
+ } catch {
120
+ }
121
+ return off;
122
+ }
123
+
45
124
  // src/index.ts
46
125
  var import_sdk_core2 = require("@guidelytech/sdk-core");
47
- var bridge = () => {
126
+ var bridge2 = () => {
48
127
  const g = globalThis;
49
128
  const b = g[BRIDGE_KEY];
50
129
  return b && typeof b.getAppInfo === "function" ? b : null;
51
130
  };
52
131
  async function getElectronAppInfo(timeoutMs = 1e3) {
53
- const b = bridge();
132
+ const b = bridge2();
54
133
  if (!b) return null;
55
134
  let timer;
56
135
  try {
@@ -81,17 +160,25 @@ async function createGuidelyElectron(options) {
81
160
  const { bridgeTimeoutMs, ...rest } = options;
82
161
  const info = await getElectronAppInfo(bridgeTimeoutMs);
83
162
  const user = rest.user ? { ...rest.user, traits: { ...desktopTraits(info), ...rest.user.traits ?? {} } } : void 0;
84
- return (0, import_sdk_core.createGuidely)({ ...electronOptions(info), ...rest, ...user ? { user } : {} });
163
+ const client = (0, import_sdk_core.createGuidely)({ ...electronOptions(info), ...rest, ...user ? { user } : {} });
164
+ setElectronErrorContexts(client, info);
165
+ forwardMainErrors(client);
166
+ return client;
85
167
  }
86
168
  // Annotate the CommonJS export names for ESM import in node:
87
169
  0 && (module.exports = {
88
170
  APP_INFO_CHANNEL,
89
171
  BRIDGE_KEY,
172
+ MAIN_ERRORS_CHANNEL,
173
+ MAIN_ERRORS_READY_CHANNEL,
90
174
  createGuidelyElectron,
91
175
  desktopTraits,
176
+ electronErrorContexts,
92
177
  electronOptions,
178
+ forwardMainErrors,
93
179
  getElectronAppInfo,
94
180
  normalizeAppUrl,
95
- osName
181
+ osName,
182
+ setElectronErrorContexts
96
183
  });
97
184
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts"],"sourcesContent":["import { createGuidely, normalizeAppUrl, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/sdk-core\";\nimport { BRIDGE_KEY, osName, type ElectronAppInfo, type GuidelyElectronBridge } from \"./types\";\n\nexport { APP_INFO_CHANNEL, BRIDGE_KEY, osName } from \"./types\";\nexport type { ElectronAppInfo, GuidelyElectronBridge } from \"./types\";\nexport { normalizeAppUrl } from \"@guidelytech/sdk-core\";\n\nconst bridge = (): GuidelyElectronBridge | null => {\n const g = globalThis as unknown as Record<string, unknown>;\n const b = g[BRIDGE_KEY] as GuidelyElectronBridge | undefined;\n return b && typeof b.getAppInfo === \"function\" ? b : null;\n};\n\n/**\n * Renderer: the app info exposed by the preload bridge, or null when it is missing (plain browser,\n * preload not installed) or does not answer within `timeoutMs`.\n */\nexport async function getElectronAppInfo(timeoutMs = 1000): Promise<ElectronAppInfo | null> {\n const b = bridge();\n if (!b) return null;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n b.getAppInfo().then((info) => (info && typeof info === \"object\" && typeof info.appVersion === \"string\" ? info : null)),\n new Promise<null>((resolve) => (timer = setTimeout(() => resolve(null), timeoutMs))),\n ]);\n } catch {\n return null;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * SDK options for a desktop app: app-relative page URLs for `file:` and custom protocols, the app\n * version for **App version** targeting rules and flag attributes, and the release for source-mapped\n * errors. Spread your own options after it to override.\n */\nexport function electronOptions(info: ElectronAppInfo | null): Pick<GuidelyOptions, \"pageUrl\" | \"appVersion\" | \"release\"> {\n return {\n pageUrl: normalizeAppUrl,\n ...(info?.appVersion ? { appVersion: info.appVersion, release: info.appVersion } : {}),\n };\n}\n\n/**\n * Desktop details as user traits (`desktopOs`, `desktopOsVersion`, `desktopArch`, `electronVersion`,\n * `appVersion`), for `identify(userId, { ...traits, ...desktopTraits(info) })` so targeting rules,\n * audiences and the Users list can use them.\n */\nexport function desktopTraits(info: ElectronAppInfo | null): Record<string, string> {\n if (!info) return {};\n const out: Record<string, string> = { appVersion: info.appVersion, desktopOs: osName(info.platform), desktopArch: info.arch };\n if (info.osVersion) out.desktopOsVersion = info.osVersion;\n if (info.electronVersion) out.electronVersion = info.electronVersion;\n return out;\n}\n\n/**\n * Renderer: creates the Guidely client for an Electron window, after reading the app info from the\n * preload bridge (at most `bridgeTimeoutMs`, default 1 s; without the bridge it still works, with\n * app-relative page URLs but no app version). `options.user.traits` get the desktop traits added.\n *\n * ```ts\n * import { createGuidelyElectron } from \"@guidelytech/electron\";\n * const guidely = await createGuidelyElectron({ projectId: \"pk_prod_…\", user: { id: me.id, traits: { plan: me.plan } } });\n * ```\n */\nexport async function createGuidelyElectron(options: GuidelyOptions & { bridgeTimeoutMs?: number }): Promise<GuidelyClient> {\n const { bridgeTimeoutMs, ...rest } = options;\n const info = await getElectronAppInfo(bridgeTimeoutMs);\n const user = rest.user ? { ...rest.user, traits: { ...desktopTraits(info), ...(rest.user.traits ?? {}) } } : undefined;\n return createGuidely({ ...electronOptions(info), ...rest, ...(user ? { user } : {}) });\n}\n","/** What the main process knows about the desktop app, sent to the renderer over IPC. */\nexport interface ElectronAppInfo {\n /** `app.getName()`. */\n appName: string;\n /** `app.getVersion()` (from package.json). */\n appVersion: string;\n /** `process.platform`: `darwin`, `win32`, `linux`. */\n platform: string;\n /** `process.arch`: `x64`, `arm64`, … */\n arch: string;\n /** `os.release()` / `process.getSystemVersion()`, when available. */\n osVersion?: string;\n electronVersion?: string;\n chromeVersion?: string;\n /** `app.getLocale()`. */\n locale?: string;\n /** `app.isPackaged`: false while running from source (`electron .`). */\n packaged?: boolean;\n}\n\n/** The API the preload script exposes on `window.guidelyElectron`. */\nexport interface GuidelyElectronBridge {\n getAppInfo(): Promise<ElectronAppInfo>;\n}\n\n/** IPC channel used between the main-process handler and the preload bridge. */\nexport const APP_INFO_CHANNEL = \"guidely:app-info\";\n/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */\nexport const BRIDGE_KEY = \"guidelyElectron\";\n\n/** Desktop operating system names used in Guidely device properties. */\nexport function osName(platform: string): string {\n if (platform === \"darwin\") return \"macos\";\n if (platform === \"win32\") return \"windows\";\n if (platform === \"linux\") return \"linux\";\n return platform;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAwF;;;AC0BjF,IAAM,mBAAmB;AAEzB,IAAM,aAAa;AAGnB,SAAS,OAAO,UAA0B;AAC/C,MAAI,aAAa,SAAU,QAAO;AAClC,MAAI,aAAa,QAAS,QAAO;AACjC,MAAI,aAAa,QAAS,QAAO;AACjC,SAAO;AACT;;;AD/BA,IAAAA,mBAAgC;AAEhC,IAAM,SAAS,MAAoC;AACjD,QAAM,IAAI;AACV,QAAM,IAAI,EAAE,UAAU;AACtB,SAAO,KAAK,OAAO,EAAE,eAAe,aAAa,IAAI;AACvD;AAMA,eAAsB,mBAAmB,YAAY,KAAuC;AAC1F,QAAM,IAAI,OAAO;AACjB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK;AAAA,MACxB,EAAE,WAAW,EAAE,KAAK,CAAC,SAAU,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,eAAe,WAAW,OAAO,IAAK;AAAA,MACrH,IAAI,QAAc,CAAC,YAAa,QAAQ,WAAW,MAAM,QAAQ,IAAI,GAAG,SAAS,CAAE;AAAA,IACrF,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,QAAI,MAAO,cAAa,KAAK;AAAA,EAC/B;AACF;AAOO,SAAS,gBAAgB,MAA0F;AACxH,SAAO;AAAA,IACL,SAAS;AAAA,IACT,GAAI,MAAM,aAAa,EAAE,YAAY,KAAK,YAAY,SAAS,KAAK,WAAW,IAAI,CAAC;AAAA,EACtF;AACF;AAOO,SAAS,cAAc,MAAsD;AAClF,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,MAA8B,EAAE,YAAY,KAAK,YAAY,WAAW,OAAO,KAAK,QAAQ,GAAG,aAAa,KAAK,KAAK;AAC5H,MAAI,KAAK,UAAW,KAAI,mBAAmB,KAAK;AAChD,MAAI,KAAK,gBAAiB,KAAI,kBAAkB,KAAK;AACrD,SAAO;AACT;AAYA,eAAsB,sBAAsB,SAAgF;AAC1H,QAAM,EAAE,iBAAiB,GAAG,KAAK,IAAI;AACrC,QAAM,OAAO,MAAM,mBAAmB,eAAe;AACrD,QAAM,OAAO,KAAK,OAAO,EAAE,GAAG,KAAK,MAAM,QAAQ,EAAE,GAAG,cAAc,IAAI,GAAG,GAAI,KAAK,KAAK,UAAU,CAAC,EAAG,EAAE,IAAI;AAC7G,aAAO,+BAAc,EAAE,GAAG,gBAAgB,IAAI,GAAG,GAAG,MAAM,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,CAAC;AACvF;","names":["import_sdk_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/renderer-errors.ts"],"sourcesContent":["import { createGuidely, normalizeAppUrl, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/sdk-core\";\nimport { forwardMainErrors, setElectronErrorContexts } from \"./renderer-errors\";\nimport { BRIDGE_KEY, osName, type ElectronAppInfo, type GuidelyElectronBridge } from \"./types\";\n\nexport { APP_INFO_CHANNEL, BRIDGE_KEY, MAIN_ERRORS_CHANNEL, MAIN_ERRORS_READY_CHANNEL, osName } from \"./types\";\nexport type { ElectronAppInfo, GuidelyElectronBridge, MainErrorEvent } from \"./types\";\nexport { electronErrorContexts, forwardMainErrors, setElectronErrorContexts } from \"./renderer-errors\";\nexport { normalizeAppUrl } from \"@guidelytech/sdk-core\";\n\nconst bridge = (): GuidelyElectronBridge | null => {\n const g = globalThis as unknown as Record<string, unknown>;\n const b = g[BRIDGE_KEY] as GuidelyElectronBridge | undefined;\n return b && typeof b.getAppInfo === \"function\" ? b : null;\n};\n\n/**\n * Renderer: the app info exposed by the preload bridge, or null when it is missing (plain browser,\n * preload not installed) or does not answer within `timeoutMs`.\n */\nexport async function getElectronAppInfo(timeoutMs = 1000): Promise<ElectronAppInfo | null> {\n const b = bridge();\n if (!b) return null;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n b.getAppInfo().then((info) => (info && typeof info === \"object\" && typeof info.appVersion === \"string\" ? info : null)),\n new Promise<null>((resolve) => (timer = setTimeout(() => resolve(null), timeoutMs))),\n ]);\n } catch {\n return null;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * SDK options for a desktop app: app-relative page URLs for `file:` and custom protocols, the app\n * version for **App version** targeting rules and flag attributes, and the release for source-mapped\n * errors. Spread your own options after it to override.\n */\nexport function electronOptions(info: ElectronAppInfo | null): Pick<GuidelyOptions, \"pageUrl\" | \"appVersion\" | \"release\"> {\n return {\n pageUrl: normalizeAppUrl,\n ...(info?.appVersion ? { appVersion: info.appVersion, release: info.appVersion } : {}),\n };\n}\n\n/**\n * Desktop details as user traits (`desktopOs`, `desktopOsVersion`, `desktopArch`, `electronVersion`,\n * `appVersion`), for `identify(userId, { ...traits, ...desktopTraits(info) })` so targeting rules,\n * audiences and the Users list can use them.\n */\nexport function desktopTraits(info: ElectronAppInfo | null): Record<string, string> {\n if (!info) return {};\n const out: Record<string, string> = { appVersion: info.appVersion, desktopOs: osName(info.platform), desktopArch: info.arch };\n if (info.osVersion) out.desktopOsVersion = info.osVersion;\n if (info.electronVersion) out.electronVersion = info.electronVersion;\n return out;\n}\n\n/**\n * Renderer: creates the Guidely client for an Electron window, after reading the app info from the\n * preload bridge (at most `bridgeTimeoutMs`, default 1 s; without the bridge it still works, with\n * app-relative page URLs but no app version). `options.user.traits` get the desktop traits added.\n * Errors carry the `electron`, `os` and `app` contexts, and the main process's errors and crashes\n * (`setupGuidelyMain({ errors })`) are sent through this client. `errors` options pass through.\n *\n * ```ts\n * import { createGuidelyElectron } from \"@guidelytech/electron\";\n * const guidely = await createGuidelyElectron({ projectId: \"pk_prod_…\", user: { id: me.id, traits: { plan: me.plan } } });\n * ```\n */\nexport async function createGuidelyElectron(options: GuidelyOptions & { bridgeTimeoutMs?: number }): Promise<GuidelyClient> {\n const { bridgeTimeoutMs, ...rest } = options;\n const info = await getElectronAppInfo(bridgeTimeoutMs);\n const user = rest.user ? { ...rest.user, traits: { ...desktopTraits(info), ...(rest.user.traits ?? {}) } } : undefined;\n const client = createGuidely({ ...electronOptions(info), ...rest, ...(user ? { user } : {}) });\n setElectronErrorContexts(client, info);\n forwardMainErrors(client);\n return client;\n}\n","/** What the main process knows about the desktop app, sent to the renderer over IPC. */\nexport interface ElectronAppInfo {\n /** `app.getName()`. */\n appName: string;\n /** `app.getVersion()` (from package.json). */\n appVersion: string;\n /** `process.platform`: `darwin`, `win32`, `linux`. */\n platform: string;\n /** `process.arch`: `x64`, `arm64`, … */\n arch: string;\n /** `os.release()` / `process.getSystemVersion()`, when available. */\n osVersion?: string;\n electronVersion?: string;\n chromeVersion?: string;\n /** The main process's Node.js version. */\n nodeVersion?: string;\n /** `app.getLocale()`. */\n locale?: string;\n /** `app.isPackaged`: false while running from source (`electron .`). */\n packaged?: boolean;\n}\n\n/** The API the preload script exposes on `window.guidelyElectron`. */\nexport interface GuidelyElectronBridge {\n getAppInfo(): Promise<ElectronAppInfo>;\n /** Main-process errors and crashes waiting to be sent (each is handed to one window only). */\n takeMainErrors?(): Promise<MainErrorEvent[]>;\n /** Called when the main process has new errors to take; returns an unsubscribe function. */\n onMainErrors?(listener: () => void): () => void;\n}\n\n/** An error the main process caught (thrown, rejected, a process gone, a minidump), sent by a renderer's client. */\nexport interface MainErrorEvent {\n id: string;\n /** When it happened (ms since epoch); it may be sent much later, after a restart. */\n timestamp: number;\n type: string;\n message: string;\n stack?: string;\n /** The error's `cause`, serialized the same way. */\n cause?: Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\">;\n level: \"fatal\" | \"error\";\n /** `uncaughtException`, `unhandledRejection`, `render-process-gone`, `child-process-gone`, `minidump`, `generic`. */\n mechanism: string;\n handled: boolean;\n contexts: Record<string, Record<string, unknown>>;\n tags?: Record<string, string>;\n fingerprint?: string[];\n}\n\n/** IPC channel the preload bridge takes main-process errors from. */\nexport const MAIN_ERRORS_CHANNEL = \"guidely:main-errors\";\n/** IPC message the main process sends windows when it has errors to take. */\nexport const MAIN_ERRORS_READY_CHANNEL = \"guidely:main-errors-ready\";\n\n/** IPC channel used between the main-process handler and the preload bridge. */\nexport const APP_INFO_CHANNEL = \"guidely:app-info\";\n/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */\nexport const BRIDGE_KEY = \"guidelyElectron\";\n\n/** Desktop operating system names used in Guidely device properties. */\nexport function osName(platform: string): string {\n if (platform === \"darwin\") return \"macos\";\n if (platform === \"win32\") return \"windows\";\n if (platform === \"linux\") return \"linux\";\n return platform;\n}\n","import type { GuidelyClient } from \"@guidelytech/sdk-core\";\nimport { BRIDGE_KEY, type ElectronAppInfo, type GuidelyElectronBridge, type MainErrorEvent } from \"./types\";\n\n/**\n * Renderer side of Electron error monitoring: Electron contexts on every error the window's client\n * reports, and the main process's errors and crashes, sent by this window's client. Kept out of\n * index.ts so the renderer entry only wires it in.\n */\n\nconst OS_NAMES: Record<string, string> = { darwin: \"macOS\", win32: \"Windows\", linux: \"Linux\" };\n\n/** `electron`, `os` and `app` contexts (Sentry's names) from the app info. */\nexport function electronErrorContexts(info: ElectronAppInfo | null): Record<string, Record<string, unknown>> {\n if (!info) return {};\n return {\n electron: { ...(info.electronVersion ? { version: info.electronVersion } : {}), ...(info.chromeVersion ? { chrome: info.chromeVersion } : {}), ...(info.nodeVersion ? { node: info.nodeVersion } : {}), processType: \"renderer\" },\n os: { name: OS_NAMES[info.platform] ?? info.platform, ...(info.osVersion ? { version: info.osVersion } : {}) },\n app: { app_name: info.appName, app_version: info.appVersion },\n };\n}\n\n/** Puts the Electron contexts on every later error of `client` (the user agent's frozen OS version is replaced by the real one). */\nexport function setElectronErrorContexts(client: GuidelyClient, info: ElectronAppInfo | null): void {\n try {\n for (const [name, context] of Object.entries(electronErrorContexts(info))) client.setContext(name, context);\n } catch {\n // an older client without setContext\n }\n}\n\n/** A main-process error back as an `Error`, its main-process stack (or none) kept as is. */\nfunction toError(data: Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\">): Error {\n const error = new Error(data.message);\n error.name = data.type;\n // Never the renderer's own stack: crashes without frames stay without frames.\n error.stack = data.stack ?? `${data.type}: ${data.message}`;\n if (data.cause) (error as Error & { cause?: unknown }).cause = toError(data.cause);\n return error;\n}\n\nconst bridge = (): GuidelyElectronBridge | null => {\n const b = (globalThis as unknown as Record<string, unknown>)[BRIDGE_KEY] as GuidelyElectronBridge | undefined;\n return b && typeof b.takeMainErrors === \"function\" ? b : null;\n};\n\n/**\n * Sends the main process's waiting errors (`setupGuidelyMain({ errors })`) through `client` now and\n * whenever the main process has new ones. `createGuidelyElectron` calls it; call it yourself when\n * you create the client another way. Returns a function that stops listening.\n */\nexport function forwardMainErrors(client: GuidelyClient, from: GuidelyElectronBridge | null = bridge()): () => void {\n if (!from?.takeMainErrors) return () => {};\n let busy = false;\n let again = false;\n const take = async (): Promise<void> => {\n if (busy) {\n again = true;\n return;\n }\n busy = true;\n try {\n // A destroyed client would take the events and drop them: leave them for another window.\n if (client.getState().destroyed) return;\n for (const e of (await from.takeMainErrors!()) ?? []) {\n try {\n client.captureException(toError(e), {\n level: e.level,\n mechanism: { type: e.mechanism, handled: e.handled },\n contexts: e.contexts,\n tags: { process: \"main\", ...(e.tags ?? {}) },\n ...(e.fingerprint?.length ? { fingerprint: e.fingerprint } : {}),\n extra: { occurredAt: new Date(e.timestamp).toISOString() },\n });\n } catch {\n // keep sending the rest\n }\n }\n } catch {\n // no main-process handler: nothing to send\n } finally {\n busy = false;\n if (again) {\n again = false;\n void take();\n }\n }\n };\n void take();\n let off: () => void = () => {};\n try {\n off = from.onMainErrors?.(() => void take()) ?? off;\n } catch {\n // a bridge without push: events are taken when windows load\n }\n return off;\n}\n"],"mappings":"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","names":["import_sdk_core","bridge"]}
package/dist/index.d.cts CHANGED
@@ -1,7 +1,18 @@
1
- import { GuidelyOptions, GuidelyClient } from '@guidelytech/sdk-core';
1
+ import { GuidelyClient, GuidelyOptions } from '@guidelytech/sdk-core';
2
2
  export { normalizeAppUrl } from '@guidelytech/sdk-core';
3
- import { E as ElectronAppInfo } from './types-BEnWCecg.cjs';
4
- export { A as APP_INFO_CHANNEL, B as BRIDGE_KEY, G as GuidelyElectronBridge, o as osName } from './types-BEnWCecg.cjs';
3
+ import { E as ElectronAppInfo, G as GuidelyElectronBridge } from './types-jhX9pTdK.cjs';
4
+ export { A as APP_INFO_CHANNEL, B as BRIDGE_KEY, M as MAIN_ERRORS_CHANNEL, a as MAIN_ERRORS_READY_CHANNEL, b as MainErrorEvent, o as osName } from './types-jhX9pTdK.cjs';
5
+
6
+ /** `electron`, `os` and `app` contexts (Sentry's names) from the app info. */
7
+ declare function electronErrorContexts(info: ElectronAppInfo | null): Record<string, Record<string, unknown>>;
8
+ /** Puts the Electron contexts on every later error of `client` (the user agent's frozen OS version is replaced by the real one). */
9
+ declare function setElectronErrorContexts(client: GuidelyClient, info: ElectronAppInfo | null): void;
10
+ /**
11
+ * Sends the main process's waiting errors (`setupGuidelyMain({ errors })`) through `client` now and
12
+ * whenever the main process has new ones. `createGuidelyElectron` calls it; call it yourself when
13
+ * you create the client another way. Returns a function that stops listening.
14
+ */
15
+ declare function forwardMainErrors(client: GuidelyClient, from?: GuidelyElectronBridge | null): () => void;
5
16
 
6
17
  /**
7
18
  * Renderer: the app info exposed by the preload bridge, or null when it is missing (plain browser,
@@ -24,6 +35,8 @@ declare function desktopTraits(info: ElectronAppInfo | null): Record<string, str
24
35
  * Renderer: creates the Guidely client for an Electron window, after reading the app info from the
25
36
  * preload bridge (at most `bridgeTimeoutMs`, default 1 s; without the bridge it still works, with
26
37
  * app-relative page URLs but no app version). `options.user.traits` get the desktop traits added.
38
+ * Errors carry the `electron`, `os` and `app` contexts, and the main process's errors and crashes
39
+ * (`setupGuidelyMain({ errors })`) are sent through this client. `errors` options pass through.
27
40
  *
28
41
  * ```ts
29
42
  * import { createGuidelyElectron } from "@guidelytech/electron";
@@ -34,4 +47,4 @@ declare function createGuidelyElectron(options: GuidelyOptions & {
34
47
  bridgeTimeoutMs?: number;
35
48
  }): Promise<GuidelyClient>;
36
49
 
37
- export { ElectronAppInfo, createGuidelyElectron, desktopTraits, electronOptions, getElectronAppInfo };
50
+ export { ElectronAppInfo, GuidelyElectronBridge, createGuidelyElectron, desktopTraits, electronErrorContexts, electronOptions, forwardMainErrors, getElectronAppInfo, setElectronErrorContexts };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,18 @@
1
- import { GuidelyOptions, GuidelyClient } from '@guidelytech/sdk-core';
1
+ import { GuidelyClient, GuidelyOptions } from '@guidelytech/sdk-core';
2
2
  export { normalizeAppUrl } from '@guidelytech/sdk-core';
3
- import { E as ElectronAppInfo } from './types-BEnWCecg.js';
4
- export { A as APP_INFO_CHANNEL, B as BRIDGE_KEY, G as GuidelyElectronBridge, o as osName } from './types-BEnWCecg.js';
3
+ import { E as ElectronAppInfo, G as GuidelyElectronBridge } from './types-jhX9pTdK.js';
4
+ export { A as APP_INFO_CHANNEL, B as BRIDGE_KEY, M as MAIN_ERRORS_CHANNEL, a as MAIN_ERRORS_READY_CHANNEL, b as MainErrorEvent, o as osName } from './types-jhX9pTdK.js';
5
+
6
+ /** `electron`, `os` and `app` contexts (Sentry's names) from the app info. */
7
+ declare function electronErrorContexts(info: ElectronAppInfo | null): Record<string, Record<string, unknown>>;
8
+ /** Puts the Electron contexts on every later error of `client` (the user agent's frozen OS version is replaced by the real one). */
9
+ declare function setElectronErrorContexts(client: GuidelyClient, info: ElectronAppInfo | null): void;
10
+ /**
11
+ * Sends the main process's waiting errors (`setupGuidelyMain({ errors })`) through `client` now and
12
+ * whenever the main process has new ones. `createGuidelyElectron` calls it; call it yourself when
13
+ * you create the client another way. Returns a function that stops listening.
14
+ */
15
+ declare function forwardMainErrors(client: GuidelyClient, from?: GuidelyElectronBridge | null): () => void;
5
16
 
6
17
  /**
7
18
  * Renderer: the app info exposed by the preload bridge, or null when it is missing (plain browser,
@@ -24,6 +35,8 @@ declare function desktopTraits(info: ElectronAppInfo | null): Record<string, str
24
35
  * Renderer: creates the Guidely client for an Electron window, after reading the app info from the
25
36
  * preload bridge (at most `bridgeTimeoutMs`, default 1 s; without the bridge it still works, with
26
37
  * app-relative page URLs but no app version). `options.user.traits` get the desktop traits added.
38
+ * Errors carry the `electron`, `os` and `app` contexts, and the main process's errors and crashes
39
+ * (`setupGuidelyMain({ errors })`) are sent through this client. `errors` options pass through.
27
40
  *
28
41
  * ```ts
29
42
  * import { createGuidelyElectron } from "@guidelytech/electron";
@@ -34,4 +47,4 @@ declare function createGuidelyElectron(options: GuidelyOptions & {
34
47
  bridgeTimeoutMs?: number;
35
48
  }): Promise<GuidelyClient>;
36
49
 
37
- export { ElectronAppInfo, createGuidelyElectron, desktopTraits, electronOptions, getElectronAppInfo };
50
+ export { ElectronAppInfo, GuidelyElectronBridge, createGuidelyElectron, desktopTraits, electronErrorContexts, electronOptions, forwardMainErrors, getElectronAppInfo, setElectronErrorContexts };
package/dist/index.js CHANGED
@@ -1,19 +1,95 @@
1
1
  import {
2
2
  APP_INFO_CHANNEL,
3
3
  BRIDGE_KEY,
4
+ MAIN_ERRORS_CHANNEL,
5
+ MAIN_ERRORS_READY_CHANNEL,
4
6
  osName
5
- } from "./chunk-AGZOVF3N.js";
7
+ } from "./chunk-JP3V2XK5.js";
6
8
 
7
9
  // src/index.ts
8
10
  import { createGuidely, normalizeAppUrl } from "@guidelytech/sdk-core";
9
- import { normalizeAppUrl as normalizeAppUrl2 } from "@guidelytech/sdk-core";
11
+
12
+ // src/renderer-errors.ts
13
+ var OS_NAMES = { darwin: "macOS", win32: "Windows", linux: "Linux" };
14
+ function electronErrorContexts(info) {
15
+ if (!info) return {};
16
+ return {
17
+ electron: { ...info.electronVersion ? { version: info.electronVersion } : {}, ...info.chromeVersion ? { chrome: info.chromeVersion } : {}, ...info.nodeVersion ? { node: info.nodeVersion } : {}, processType: "renderer" },
18
+ os: { name: OS_NAMES[info.platform] ?? info.platform, ...info.osVersion ? { version: info.osVersion } : {} },
19
+ app: { app_name: info.appName, app_version: info.appVersion }
20
+ };
21
+ }
22
+ function setElectronErrorContexts(client, info) {
23
+ try {
24
+ for (const [name, context] of Object.entries(electronErrorContexts(info))) client.setContext(name, context);
25
+ } catch {
26
+ }
27
+ }
28
+ function toError(data) {
29
+ const error = new Error(data.message);
30
+ error.name = data.type;
31
+ error.stack = data.stack ?? `${data.type}: ${data.message}`;
32
+ if (data.cause) error.cause = toError(data.cause);
33
+ return error;
34
+ }
10
35
  var bridge = () => {
36
+ const b = globalThis[BRIDGE_KEY];
37
+ return b && typeof b.takeMainErrors === "function" ? b : null;
38
+ };
39
+ function forwardMainErrors(client, from = bridge()) {
40
+ if (!from?.takeMainErrors) return () => {
41
+ };
42
+ let busy = false;
43
+ let again = false;
44
+ const take = async () => {
45
+ if (busy) {
46
+ again = true;
47
+ return;
48
+ }
49
+ busy = true;
50
+ try {
51
+ if (client.getState().destroyed) return;
52
+ for (const e of await from.takeMainErrors() ?? []) {
53
+ try {
54
+ client.captureException(toError(e), {
55
+ level: e.level,
56
+ mechanism: { type: e.mechanism, handled: e.handled },
57
+ contexts: e.contexts,
58
+ tags: { process: "main", ...e.tags ?? {} },
59
+ ...e.fingerprint?.length ? { fingerprint: e.fingerprint } : {},
60
+ extra: { occurredAt: new Date(e.timestamp).toISOString() }
61
+ });
62
+ } catch {
63
+ }
64
+ }
65
+ } catch {
66
+ } finally {
67
+ busy = false;
68
+ if (again) {
69
+ again = false;
70
+ void take();
71
+ }
72
+ }
73
+ };
74
+ void take();
75
+ let off = () => {
76
+ };
77
+ try {
78
+ off = from.onMainErrors?.(() => void take()) ?? off;
79
+ } catch {
80
+ }
81
+ return off;
82
+ }
83
+
84
+ // src/index.ts
85
+ import { normalizeAppUrl as normalizeAppUrl2 } from "@guidelytech/sdk-core";
86
+ var bridge2 = () => {
11
87
  const g = globalThis;
12
88
  const b = g[BRIDGE_KEY];
13
89
  return b && typeof b.getAppInfo === "function" ? b : null;
14
90
  };
15
91
  async function getElectronAppInfo(timeoutMs = 1e3) {
16
- const b = bridge();
92
+ const b = bridge2();
17
93
  if (!b) return null;
18
94
  let timer;
19
95
  try {
@@ -44,16 +120,24 @@ async function createGuidelyElectron(options) {
44
120
  const { bridgeTimeoutMs, ...rest } = options;
45
121
  const info = await getElectronAppInfo(bridgeTimeoutMs);
46
122
  const user = rest.user ? { ...rest.user, traits: { ...desktopTraits(info), ...rest.user.traits ?? {} } } : void 0;
47
- return createGuidely({ ...electronOptions(info), ...rest, ...user ? { user } : {} });
123
+ const client = createGuidely({ ...electronOptions(info), ...rest, ...user ? { user } : {} });
124
+ setElectronErrorContexts(client, info);
125
+ forwardMainErrors(client);
126
+ return client;
48
127
  }
49
128
  export {
50
129
  APP_INFO_CHANNEL,
51
130
  BRIDGE_KEY,
131
+ MAIN_ERRORS_CHANNEL,
132
+ MAIN_ERRORS_READY_CHANNEL,
52
133
  createGuidelyElectron,
53
134
  desktopTraits,
135
+ electronErrorContexts,
54
136
  electronOptions,
137
+ forwardMainErrors,
55
138
  getElectronAppInfo,
56
139
  normalizeAppUrl2 as normalizeAppUrl,
57
- osName
140
+ osName,
141
+ setElectronErrorContexts
58
142
  };
59
143
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createGuidely, normalizeAppUrl, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/sdk-core\";\nimport { BRIDGE_KEY, osName, type ElectronAppInfo, type GuidelyElectronBridge } from \"./types\";\n\nexport { APP_INFO_CHANNEL, BRIDGE_KEY, osName } from \"./types\";\nexport type { ElectronAppInfo, GuidelyElectronBridge } from \"./types\";\nexport { normalizeAppUrl } from \"@guidelytech/sdk-core\";\n\nconst bridge = (): GuidelyElectronBridge | null => {\n const g = globalThis as unknown as Record<string, unknown>;\n const b = g[BRIDGE_KEY] as GuidelyElectronBridge | undefined;\n return b && typeof b.getAppInfo === \"function\" ? b : null;\n};\n\n/**\n * Renderer: the app info exposed by the preload bridge, or null when it is missing (plain browser,\n * preload not installed) or does not answer within `timeoutMs`.\n */\nexport async function getElectronAppInfo(timeoutMs = 1000): Promise<ElectronAppInfo | null> {\n const b = bridge();\n if (!b) return null;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n b.getAppInfo().then((info) => (info && typeof info === \"object\" && typeof info.appVersion === \"string\" ? info : null)),\n new Promise<null>((resolve) => (timer = setTimeout(() => resolve(null), timeoutMs))),\n ]);\n } catch {\n return null;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * SDK options for a desktop app: app-relative page URLs for `file:` and custom protocols, the app\n * version for **App version** targeting rules and flag attributes, and the release for source-mapped\n * errors. Spread your own options after it to override.\n */\nexport function electronOptions(info: ElectronAppInfo | null): Pick<GuidelyOptions, \"pageUrl\" | \"appVersion\" | \"release\"> {\n return {\n pageUrl: normalizeAppUrl,\n ...(info?.appVersion ? { appVersion: info.appVersion, release: info.appVersion } : {}),\n };\n}\n\n/**\n * Desktop details as user traits (`desktopOs`, `desktopOsVersion`, `desktopArch`, `electronVersion`,\n * `appVersion`), for `identify(userId, { ...traits, ...desktopTraits(info) })` so targeting rules,\n * audiences and the Users list can use them.\n */\nexport function desktopTraits(info: ElectronAppInfo | null): Record<string, string> {\n if (!info) return {};\n const out: Record<string, string> = { appVersion: info.appVersion, desktopOs: osName(info.platform), desktopArch: info.arch };\n if (info.osVersion) out.desktopOsVersion = info.osVersion;\n if (info.electronVersion) out.electronVersion = info.electronVersion;\n return out;\n}\n\n/**\n * Renderer: creates the Guidely client for an Electron window, after reading the app info from the\n * preload bridge (at most `bridgeTimeoutMs`, default 1 s; without the bridge it still works, with\n * app-relative page URLs but no app version). `options.user.traits` get the desktop traits added.\n *\n * ```ts\n * import { createGuidelyElectron } from \"@guidelytech/electron\";\n * const guidely = await createGuidelyElectron({ projectId: \"pk_prod_…\", user: { id: me.id, traits: { plan: me.plan } } });\n * ```\n */\nexport async function createGuidelyElectron(options: GuidelyOptions & { bridgeTimeoutMs?: number }): Promise<GuidelyClient> {\n const { bridgeTimeoutMs, ...rest } = options;\n const info = await getElectronAppInfo(bridgeTimeoutMs);\n const user = rest.user ? { ...rest.user, traits: { ...desktopTraits(info), ...(rest.user.traits ?? {}) } } : undefined;\n return createGuidely({ ...electronOptions(info), ...rest, ...(user ? { user } : {}) });\n}\n"],"mappings":";;;;;;;AAAA,SAAS,eAAe,uBAAgE;AAKxF,SAAS,mBAAAA,wBAAuB;AAEhC,IAAM,SAAS,MAAoC;AACjD,QAAM,IAAI;AACV,QAAM,IAAI,EAAE,UAAU;AACtB,SAAO,KAAK,OAAO,EAAE,eAAe,aAAa,IAAI;AACvD;AAMA,eAAsB,mBAAmB,YAAY,KAAuC;AAC1F,QAAM,IAAI,OAAO;AACjB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK;AAAA,MACxB,EAAE,WAAW,EAAE,KAAK,CAAC,SAAU,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,eAAe,WAAW,OAAO,IAAK;AAAA,MACrH,IAAI,QAAc,CAAC,YAAa,QAAQ,WAAW,MAAM,QAAQ,IAAI,GAAG,SAAS,CAAE;AAAA,IACrF,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,QAAI,MAAO,cAAa,KAAK;AAAA,EAC/B;AACF;AAOO,SAAS,gBAAgB,MAA0F;AACxH,SAAO;AAAA,IACL,SAAS;AAAA,IACT,GAAI,MAAM,aAAa,EAAE,YAAY,KAAK,YAAY,SAAS,KAAK,WAAW,IAAI,CAAC;AAAA,EACtF;AACF;AAOO,SAAS,cAAc,MAAsD;AAClF,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,MAA8B,EAAE,YAAY,KAAK,YAAY,WAAW,OAAO,KAAK,QAAQ,GAAG,aAAa,KAAK,KAAK;AAC5H,MAAI,KAAK,UAAW,KAAI,mBAAmB,KAAK;AAChD,MAAI,KAAK,gBAAiB,KAAI,kBAAkB,KAAK;AACrD,SAAO;AACT;AAYA,eAAsB,sBAAsB,SAAgF;AAC1H,QAAM,EAAE,iBAAiB,GAAG,KAAK,IAAI;AACrC,QAAM,OAAO,MAAM,mBAAmB,eAAe;AACrD,QAAM,OAAO,KAAK,OAAO,EAAE,GAAG,KAAK,MAAM,QAAQ,EAAE,GAAG,cAAc,IAAI,GAAG,GAAI,KAAK,KAAK,UAAU,CAAC,EAAG,EAAE,IAAI;AAC7G,SAAO,cAAc,EAAE,GAAG,gBAAgB,IAAI,GAAG,GAAG,MAAM,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,CAAC;AACvF;","names":["normalizeAppUrl"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/renderer-errors.ts"],"sourcesContent":["import { createGuidely, normalizeAppUrl, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/sdk-core\";\nimport { forwardMainErrors, setElectronErrorContexts } from \"./renderer-errors\";\nimport { BRIDGE_KEY, osName, type ElectronAppInfo, type GuidelyElectronBridge } from \"./types\";\n\nexport { APP_INFO_CHANNEL, BRIDGE_KEY, MAIN_ERRORS_CHANNEL, MAIN_ERRORS_READY_CHANNEL, osName } from \"./types\";\nexport type { ElectronAppInfo, GuidelyElectronBridge, MainErrorEvent } from \"./types\";\nexport { electronErrorContexts, forwardMainErrors, setElectronErrorContexts } from \"./renderer-errors\";\nexport { normalizeAppUrl } from \"@guidelytech/sdk-core\";\n\nconst bridge = (): GuidelyElectronBridge | null => {\n const g = globalThis as unknown as Record<string, unknown>;\n const b = g[BRIDGE_KEY] as GuidelyElectronBridge | undefined;\n return b && typeof b.getAppInfo === \"function\" ? b : null;\n};\n\n/**\n * Renderer: the app info exposed by the preload bridge, or null when it is missing (plain browser,\n * preload not installed) or does not answer within `timeoutMs`.\n */\nexport async function getElectronAppInfo(timeoutMs = 1000): Promise<ElectronAppInfo | null> {\n const b = bridge();\n if (!b) return null;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n b.getAppInfo().then((info) => (info && typeof info === \"object\" && typeof info.appVersion === \"string\" ? info : null)),\n new Promise<null>((resolve) => (timer = setTimeout(() => resolve(null), timeoutMs))),\n ]);\n } catch {\n return null;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * SDK options for a desktop app: app-relative page URLs for `file:` and custom protocols, the app\n * version for **App version** targeting rules and flag attributes, and the release for source-mapped\n * errors. Spread your own options after it to override.\n */\nexport function electronOptions(info: ElectronAppInfo | null): Pick<GuidelyOptions, \"pageUrl\" | \"appVersion\" | \"release\"> {\n return {\n pageUrl: normalizeAppUrl,\n ...(info?.appVersion ? { appVersion: info.appVersion, release: info.appVersion } : {}),\n };\n}\n\n/**\n * Desktop details as user traits (`desktopOs`, `desktopOsVersion`, `desktopArch`, `electronVersion`,\n * `appVersion`), for `identify(userId, { ...traits, ...desktopTraits(info) })` so targeting rules,\n * audiences and the Users list can use them.\n */\nexport function desktopTraits(info: ElectronAppInfo | null): Record<string, string> {\n if (!info) return {};\n const out: Record<string, string> = { appVersion: info.appVersion, desktopOs: osName(info.platform), desktopArch: info.arch };\n if (info.osVersion) out.desktopOsVersion = info.osVersion;\n if (info.electronVersion) out.electronVersion = info.electronVersion;\n return out;\n}\n\n/**\n * Renderer: creates the Guidely client for an Electron window, after reading the app info from the\n * preload bridge (at most `bridgeTimeoutMs`, default 1 s; without the bridge it still works, with\n * app-relative page URLs but no app version). `options.user.traits` get the desktop traits added.\n * Errors carry the `electron`, `os` and `app` contexts, and the main process's errors and crashes\n * (`setupGuidelyMain({ errors })`) are sent through this client. `errors` options pass through.\n *\n * ```ts\n * import { createGuidelyElectron } from \"@guidelytech/electron\";\n * const guidely = await createGuidelyElectron({ projectId: \"pk_prod_…\", user: { id: me.id, traits: { plan: me.plan } } });\n * ```\n */\nexport async function createGuidelyElectron(options: GuidelyOptions & { bridgeTimeoutMs?: number }): Promise<GuidelyClient> {\n const { bridgeTimeoutMs, ...rest } = options;\n const info = await getElectronAppInfo(bridgeTimeoutMs);\n const user = rest.user ? { ...rest.user, traits: { ...desktopTraits(info), ...(rest.user.traits ?? {}) } } : undefined;\n const client = createGuidely({ ...electronOptions(info), ...rest, ...(user ? { user } : {}) });\n setElectronErrorContexts(client, info);\n forwardMainErrors(client);\n return client;\n}\n","import type { GuidelyClient } from \"@guidelytech/sdk-core\";\nimport { BRIDGE_KEY, type ElectronAppInfo, type GuidelyElectronBridge, type MainErrorEvent } from \"./types\";\n\n/**\n * Renderer side of Electron error monitoring: Electron contexts on every error the window's client\n * reports, and the main process's errors and crashes, sent by this window's client. Kept out of\n * index.ts so the renderer entry only wires it in.\n */\n\nconst OS_NAMES: Record<string, string> = { darwin: \"macOS\", win32: \"Windows\", linux: \"Linux\" };\n\n/** `electron`, `os` and `app` contexts (Sentry's names) from the app info. */\nexport function electronErrorContexts(info: ElectronAppInfo | null): Record<string, Record<string, unknown>> {\n if (!info) return {};\n return {\n electron: { ...(info.electronVersion ? { version: info.electronVersion } : {}), ...(info.chromeVersion ? { chrome: info.chromeVersion } : {}), ...(info.nodeVersion ? { node: info.nodeVersion } : {}), processType: \"renderer\" },\n os: { name: OS_NAMES[info.platform] ?? info.platform, ...(info.osVersion ? { version: info.osVersion } : {}) },\n app: { app_name: info.appName, app_version: info.appVersion },\n };\n}\n\n/** Puts the Electron contexts on every later error of `client` (the user agent's frozen OS version is replaced by the real one). */\nexport function setElectronErrorContexts(client: GuidelyClient, info: ElectronAppInfo | null): void {\n try {\n for (const [name, context] of Object.entries(electronErrorContexts(info))) client.setContext(name, context);\n } catch {\n // an older client without setContext\n }\n}\n\n/** A main-process error back as an `Error`, its main-process stack (or none) kept as is. */\nfunction toError(data: Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\">): Error {\n const error = new Error(data.message);\n error.name = data.type;\n // Never the renderer's own stack: crashes without frames stay without frames.\n error.stack = data.stack ?? `${data.type}: ${data.message}`;\n if (data.cause) (error as Error & { cause?: unknown }).cause = toError(data.cause);\n return error;\n}\n\nconst bridge = (): GuidelyElectronBridge | null => {\n const b = (globalThis as unknown as Record<string, unknown>)[BRIDGE_KEY] as GuidelyElectronBridge | undefined;\n return b && typeof b.takeMainErrors === \"function\" ? b : null;\n};\n\n/**\n * Sends the main process's waiting errors (`setupGuidelyMain({ errors })`) through `client` now and\n * whenever the main process has new ones. `createGuidelyElectron` calls it; call it yourself when\n * you create the client another way. Returns a function that stops listening.\n */\nexport function forwardMainErrors(client: GuidelyClient, from: GuidelyElectronBridge | null = bridge()): () => void {\n if (!from?.takeMainErrors) return () => {};\n let busy = false;\n let again = false;\n const take = async (): Promise<void> => {\n if (busy) {\n again = true;\n return;\n }\n busy = true;\n try {\n // A destroyed client would take the events and drop them: leave them for another window.\n if (client.getState().destroyed) return;\n for (const e of (await from.takeMainErrors!()) ?? []) {\n try {\n client.captureException(toError(e), {\n level: e.level,\n mechanism: { type: e.mechanism, handled: e.handled },\n contexts: e.contexts,\n tags: { process: \"main\", ...(e.tags ?? {}) },\n ...(e.fingerprint?.length ? { fingerprint: e.fingerprint } : {}),\n extra: { occurredAt: new Date(e.timestamp).toISOString() },\n });\n } catch {\n // keep sending the rest\n }\n }\n } catch {\n // no main-process handler: nothing to send\n } finally {\n busy = false;\n if (again) {\n again = false;\n void take();\n }\n }\n };\n void take();\n let off: () => void = () => {};\n try {\n off = from.onMainErrors?.(() => void take()) ?? off;\n } catch {\n // a bridge without push: events are taken when windows load\n }\n return off;\n}\n"],"mappings":"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","names":["normalizeAppUrl","bridge"]}