@guidelytech/electron 0.15.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 +44 -2
- package/dist/{chunk-AGZOVF3N.js → chunk-JP3V2XK5.js} +5 -1
- package/dist/chunk-JP3V2XK5.js.map +1 -0
- package/dist/index.cjs +92 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +89 -5
- package/dist/index.js.map +1 -1
- package/dist/main.cjs +182 -6
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.cts +114 -4
- package/dist/main.d.ts +114 -4
- package/dist/main.js +169 -8
- package/dist/main.js.map +1 -1
- package/dist/preload.cjs +10 -1
- package/dist/preload.cjs.map +1 -1
- package/dist/preload.d.cts +4 -1
- package/dist/preload.d.ts +4 -1
- package/dist/preload.js +12 -3
- package/dist/preload.js.map +1 -1
- package/dist/types-jhX9pTdK.d.cts +59 -0
- package/dist/types-jhX9pTdK.d.ts +59 -0
- package/package.json +4 -4
- package/dist/chunk-AGZOVF3N.js.map +0 -1
- package/dist/types-BEnWCecg.d.cts +0 -31
- package/dist/types-BEnWCecg.d.ts +0 -31
package/dist/main.js
CHANGED
|
@@ -1,26 +1,180 @@
|
|
|
1
1
|
import {
|
|
2
|
-
APP_INFO_CHANNEL
|
|
3
|
-
|
|
2
|
+
APP_INFO_CHANNEL,
|
|
3
|
+
MAIN_ERRORS_CHANNEL,
|
|
4
|
+
MAIN_ERRORS_READY_CHANNEL
|
|
5
|
+
} from "./chunk-JP3V2XK5.js";
|
|
6
|
+
|
|
7
|
+
// src/main-crashes.ts
|
|
8
|
+
import * as nodeFs from "fs";
|
|
9
|
+
import { basename, join } from "path";
|
|
10
|
+
var MAX_EVENTS = 30;
|
|
11
|
+
var OS_NAMES = { darwin: "macOS", win32: "Windows", linux: "Linux" };
|
|
12
|
+
var FATAL_REASONS = /* @__PURE__ */ new Set(["crashed", "oom", "killed"]);
|
|
13
|
+
var safe = (fn) => {
|
|
14
|
+
try {
|
|
15
|
+
return fn();
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var newId = () => Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join("");
|
|
21
|
+
function serialize(error, depth = 0) {
|
|
22
|
+
const e = error;
|
|
23
|
+
const isErr = !!e && typeof e === "object" && typeof e.message === "string";
|
|
24
|
+
const out = {
|
|
25
|
+
type: isErr && typeof e.name === "string" && e.name || (isErr ? "Error" : "UnhandledRejection"),
|
|
26
|
+
message: (isErr ? String(e.message) : safe(() => typeof error === "string" ? error : JSON.stringify(error)) ?? String(error)).slice(0, 1e3),
|
|
27
|
+
...isErr && typeof e.stack === "string" ? { stack: e.stack.slice(0, 16e3) } : {}
|
|
28
|
+
};
|
|
29
|
+
if (isErr && e.cause !== void 0 && e.cause !== null && depth < 5) out.cause = serialize(e.cause, depth + 1);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function installGuidelyCrashHandlers(opts) {
|
|
33
|
+
const proc = opts.process ?? process;
|
|
34
|
+
const fs = opts.fs ?? nodeFs;
|
|
35
|
+
const { app } = opts;
|
|
36
|
+
const userData = safe(() => app.getPath?.("userData"));
|
|
37
|
+
const storePath = opts.storePath === void 0 ? userData ? join(userData, "guidely-main-errors.json") : null : opts.storePath;
|
|
38
|
+
const cleanups = [];
|
|
39
|
+
let events = [];
|
|
40
|
+
if (storePath) {
|
|
41
|
+
const saved = safe(() => JSON.parse(fs.readFileSync(storePath, "utf8")));
|
|
42
|
+
if (Array.isArray(saved)) events = saved.filter((e) => !!e && typeof e === "object" && typeof e.id === "string").slice(-MAX_EVENTS);
|
|
43
|
+
}
|
|
44
|
+
const persist = () => {
|
|
45
|
+
if (storePath) safe(() => fs.writeFileSync(storePath, JSON.stringify(events)));
|
|
46
|
+
};
|
|
47
|
+
const notify = () => {
|
|
48
|
+
safe(() => {
|
|
49
|
+
for (const wc of opts.webContents?.getAllWebContents() ?? []) if (!safe(() => wc.isDestroyed?.())) safe(() => wc.send(MAIN_ERRORS_READY_CHANNEL));
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const baseContexts = () => {
|
|
53
|
+
const osVersion = safe(() => proc.getSystemVersion?.());
|
|
54
|
+
return {
|
|
55
|
+
electron: { version: proc.versions.electron, chrome: proc.versions.chrome, node: proc.versions.node, processType: "main" },
|
|
56
|
+
os: { name: OS_NAMES[proc.platform] ?? proc.platform, ...osVersion ? { version: osVersion } : {} },
|
|
57
|
+
app: { app_name: safe(() => app.getName?.()), app_version: safe(() => app.getVersion?.()) }
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const record = (data, extra) => {
|
|
61
|
+
try {
|
|
62
|
+
const contexts = baseContexts();
|
|
63
|
+
for (const [k, v] of Object.entries(extra.contexts ?? {})) contexts[k] = { ...contexts[k] ?? {}, ...v };
|
|
64
|
+
const event = { id: newId(), timestamp: Date.now(), ...data, level: extra.level, mechanism: extra.mechanism, handled: extra.handled ?? false, contexts, ...extra.tags ? { tags: extra.tags } : {}, ...extra.fingerprint ? { fingerprint: extra.fingerprint } : {} };
|
|
65
|
+
events.push(event);
|
|
66
|
+
if (events.length > MAX_EVENTS) events.splice(0, events.length - MAX_EVENTS);
|
|
67
|
+
persist();
|
|
68
|
+
notify();
|
|
69
|
+
return event.id;
|
|
70
|
+
} catch {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const on = (target, event, listener) => {
|
|
75
|
+
if (typeof target.on !== "function") return;
|
|
76
|
+
safe(() => target.on(event, listener));
|
|
77
|
+
cleanups.push(() => target.removeListener?.(event, listener));
|
|
78
|
+
};
|
|
79
|
+
if (opts.processHandlers !== false) {
|
|
80
|
+
on(proc, "uncaughtExceptionMonitor", (error, origin) => {
|
|
81
|
+
record(serialize(error), { level: "fatal", mechanism: origin === "unhandledRejection" ? "unhandledRejection" : "uncaughtException" });
|
|
82
|
+
if (opts.exitOnUncaught) {
|
|
83
|
+
safe(() => console.error(error));
|
|
84
|
+
if (typeof app.exit === "function") app.exit(1);
|
|
85
|
+
else proc.exit(1);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
const onRejection = (reason) => {
|
|
89
|
+
record(serialize(reason), { level: "error", mechanism: "unhandledRejection" });
|
|
90
|
+
if (proc.listenerCount("unhandledRejection") <= 1) safe(() => console.error("Unhandled promise rejection in the main process:", reason));
|
|
91
|
+
};
|
|
92
|
+
on(proc, "unhandledRejection", onRejection);
|
|
93
|
+
}
|
|
94
|
+
if (opts.processGone !== false) {
|
|
95
|
+
on(app, "render-process-gone", (_event, _webContents, details = {}) => {
|
|
96
|
+
if (!details.reason || details.reason === "clean-exit") return;
|
|
97
|
+
record({ type: "RenderProcessGone", message: `Renderer process gone: ${details.reason}` }, { level: FATAL_REASONS.has(details.reason) ? "fatal" : "error", mechanism: "render-process-gone", contexts: { electron: { reason: details.reason, exitCode: details.exitCode, processType: "renderer" } } });
|
|
98
|
+
});
|
|
99
|
+
on(app, "child-process-gone", (_event, details = {}) => {
|
|
100
|
+
if (!details.reason || details.reason === "clean-exit") return;
|
|
101
|
+
const kind = details.type ?? "Child";
|
|
102
|
+
record({ type: "ChildProcessGone", message: `${kind} process gone: ${details.reason}` }, { level: FATAL_REASONS.has(details.reason) ? "fatal" : "error", mechanism: "child-process-gone", contexts: { electron: { reason: details.reason, exitCode: details.exitCode, processType: kind, ...details.serviceName ? { serviceName: details.serviceName } : {}, ...details.name ? { name: details.name } : {} } } });
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (opts.nativeCrashes) {
|
|
106
|
+
const reporter = typeof opts.nativeCrashes === "object" ? opts.nativeCrashes.crashReporter : void 0;
|
|
107
|
+
safe(() => reporter?.start({ uploadToServer: false }));
|
|
108
|
+
reportNewMinidumps(
|
|
109
|
+
app,
|
|
110
|
+
fs,
|
|
111
|
+
userData,
|
|
112
|
+
(file, stat) => record({ type: "NativeCrash", message: `Native crash (minidump ${basename(file)})` }, { level: "fatal", mechanism: "minidump", contexts: { electron: { minidump: basename(file), minidumpSize: stat.size, crashedAt: new Date(stat.mtimeMs).toISOString() } } })
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
safe(() => {
|
|
116
|
+
opts.ipcMain.removeHandler?.(MAIN_ERRORS_CHANNEL);
|
|
117
|
+
opts.ipcMain.handle(MAIN_ERRORS_CHANNEL, () => handle.take());
|
|
118
|
+
cleanups.push(() => opts.ipcMain.removeHandler?.(MAIN_ERRORS_CHANNEL));
|
|
119
|
+
});
|
|
120
|
+
const handle = {
|
|
121
|
+
captureException: (error, context = {}) => record(serialize(error), { level: context.level ?? "error", mechanism: "generic", handled: true, contexts: context.contexts, tags: context.tags, fingerprint: context.fingerprint }),
|
|
122
|
+
take: () => {
|
|
123
|
+
const out = events;
|
|
124
|
+
events = [];
|
|
125
|
+
if (out.length) persist();
|
|
126
|
+
return out;
|
|
127
|
+
},
|
|
128
|
+
dispose: () => {
|
|
129
|
+
for (const off of cleanups.splice(0)) safe(off);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
return handle;
|
|
133
|
+
}
|
|
134
|
+
function reportNewMinidumps(app, fs, userData, report) {
|
|
135
|
+
const dir = safe(() => app.getPath?.("crashDumps"));
|
|
136
|
+
if (!dir || !userData) return;
|
|
137
|
+
const seenPath = join(userData, "guidely-minidumps.json");
|
|
138
|
+
const saved = safe(() => JSON.parse(fs.readFileSync(seenPath, "utf8")));
|
|
139
|
+
const firstRun = !Array.isArray(saved);
|
|
140
|
+
const seen = new Set(Array.isArray(saved) ? saved.filter((s) => typeof s === "string") : []);
|
|
141
|
+
const found = [];
|
|
142
|
+
const walk = (d, depth) => {
|
|
143
|
+
for (const entry of safe(() => fs.readdirSync(d, { withFileTypes: true })) ?? []) {
|
|
144
|
+
const path = join(d, entry.name);
|
|
145
|
+
if (entry.isDirectory() && depth < 2) walk(path, depth + 1);
|
|
146
|
+
else if (entry.isFile() && entry.name.endsWith(".dmp")) found.push(path);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
walk(dir, 0);
|
|
150
|
+
for (const file of found) {
|
|
151
|
+
if (seen.has(file) || firstRun) continue;
|
|
152
|
+
const stat = safe(() => fs.statSync(file));
|
|
153
|
+
if (stat) report(file, stat);
|
|
154
|
+
}
|
|
155
|
+
safe(() => fs.writeFileSync(seenPath, JSON.stringify(found.slice(-500))));
|
|
156
|
+
}
|
|
4
157
|
|
|
5
158
|
// src/main.ts
|
|
6
159
|
function appInfo(app, proc = process, osRelease) {
|
|
7
|
-
const
|
|
160
|
+
const safe2 = (fn) => {
|
|
8
161
|
try {
|
|
9
162
|
return fn();
|
|
10
163
|
} catch {
|
|
11
164
|
return void 0;
|
|
12
165
|
}
|
|
13
166
|
};
|
|
14
|
-
const osVersion =
|
|
167
|
+
const osVersion = safe2(() => proc.getSystemVersion?.()) || safe2(() => osRelease?.());
|
|
15
168
|
return {
|
|
16
|
-
appName:
|
|
17
|
-
appVersion:
|
|
169
|
+
appName: safe2(() => app.getName()) ?? "app",
|
|
170
|
+
appVersion: safe2(() => app.getVersion()) ?? "0.0.0",
|
|
18
171
|
platform: proc.platform,
|
|
19
172
|
arch: proc.arch,
|
|
20
173
|
...osVersion ? { osVersion } : {},
|
|
21
174
|
...proc.versions.electron ? { electronVersion: proc.versions.electron } : {},
|
|
22
175
|
...proc.versions.chrome ? { chromeVersion: proc.versions.chrome } : {},
|
|
23
|
-
...
|
|
176
|
+
...proc.versions.node ? { nodeVersion: proc.versions.node } : {},
|
|
177
|
+
...app.getLocale ? { locale: safe2(() => app.getLocale()) } : {},
|
|
24
178
|
...typeof app.isPackaged === "boolean" ? { packaged: app.isPackaged } : {}
|
|
25
179
|
};
|
|
26
180
|
}
|
|
@@ -28,11 +182,18 @@ function setupGuidelyMain(opts) {
|
|
|
28
182
|
const info = appInfo(opts.app, opts.process, opts.osRelease);
|
|
29
183
|
opts.ipcMain.removeHandler?.(APP_INFO_CHANNEL);
|
|
30
184
|
opts.ipcMain.handle(APP_INFO_CHANNEL, () => info);
|
|
31
|
-
|
|
185
|
+
const crashes = opts.errors ? installGuidelyCrashHandlers({ app: opts.app, ipcMain: opts.ipcMain, ...typeof opts.errors === "object" ? opts.errors : {} }) : null;
|
|
186
|
+
return () => {
|
|
187
|
+
opts.ipcMain.removeHandler?.(APP_INFO_CHANNEL);
|
|
188
|
+
crashes?.dispose();
|
|
189
|
+
};
|
|
32
190
|
}
|
|
33
191
|
export {
|
|
34
192
|
APP_INFO_CHANNEL,
|
|
193
|
+
MAIN_ERRORS_CHANNEL,
|
|
194
|
+
MAIN_ERRORS_READY_CHANNEL,
|
|
35
195
|
appInfo,
|
|
196
|
+
installGuidelyCrashHandlers,
|
|
36
197
|
setupGuidelyMain
|
|
37
198
|
};
|
|
38
199
|
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { APP_INFO_CHANNEL, type ElectronAppInfo } from \"./types\";\n\nexport { APP_INFO_CHANNEL } from \"./types\";\nexport type { ElectronAppInfo } from \"./types\";\n\n/** The parts of Electron's `app` the helper reads (pass `import { app } from \"electron\"`). */\nexport interface ElectronAppLike {\n getName(): string;\n getVersion(): string;\n getLocale?(): string;\n isPackaged?: boolean;\n}\n\n/** The parts of Electron's `ipcMain` the helper uses. */\nexport interface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: unknown[]) => unknown): void;\n removeHandler?(channel: string): void;\n}\n\n/** Node's `process` fields read for the app info (overridable for tests). */\nexport interface ProcessLike {\n platform: string;\n arch: string;\n versions: Record<string, string | undefined>;\n getSystemVersion?: () => string;\n}\n\n/** Collects the app info Guidely reports: app name and version, OS, architecture and Electron/Chrome versions. */\nexport function appInfo(app: ElectronAppLike, proc: ProcessLike = process as unknown as ProcessLike, osRelease?: () => string): ElectronAppInfo {\n const safe = <T>(fn: () => T): T | undefined => {\n try {\n return fn();\n } catch {\n return undefined;\n }\n };\n const osVersion = safe(() => proc.getSystemVersion?.()) || safe(() => osRelease?.());\n return {\n appName: safe(() => app.getName()) ?? \"app\",\n appVersion: safe(() => app.getVersion()) ?? \"0.0.0\",\n platform: proc.platform,\n arch: proc.arch,\n ...(osVersion ? { osVersion } : {}),\n ...(proc.versions.electron ? { electronVersion: proc.versions.electron } : {}),\n ...(proc.versions.chrome ? { chromeVersion: proc.versions.chrome } : {}),\n ...(app.getLocale ? { locale: safe(() => app.getLocale!()) } : {}),\n ...(typeof app.isPackaged === \"boolean\" ? { packaged: app.isPackaged } : {}),\n };\n}\n\n/**\n * Main process: answers the preload bridge's app-info request. Call once after `app` is ready:\n *\n * ```ts\n * import { app, ipcMain } from \"electron\";\n * import { setupGuidelyMain } from \"@guidelytech/electron/main\";\n * app.whenReady().then(() => { setupGuidelyMain({ app, ipcMain }); createWindow(); });\n * ```\n *\n * Returns a function that removes the handler.\n */\nexport function setupGuidelyMain(opts: { app: ElectronAppLike; ipcMain: IpcMainLike; process?: ProcessLike; osRelease?: () => string }): () => void {\n const info = appInfo(opts.app, opts.process, opts.osRelease);\n opts.ipcMain.removeHandler?.(APP_INFO_CHANNEL);\n opts.ipcMain.handle(APP_INFO_CHANNEL, () => info);\n return () => opts.ipcMain.removeHandler?.(APP_INFO_CHANNEL);\n}\n"],"mappings":";;;;;AA4BO,SAAS,QAAQ,KAAsB,OAAoB,SAAmC,WAA2C;AAC9I,QAAM,OAAO,CAAI,OAA+B;AAC9C,QAAI;AACF,aAAO,GAAG;AAAA,IACZ,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,YAAY,KAAK,MAAM,KAAK,mBAAmB,CAAC,KAAK,KAAK,MAAM,YAAY,CAAC;AACnF,SAAO;AAAA,IACL,SAAS,KAAK,MAAM,IAAI,QAAQ,CAAC,KAAK;AAAA,IACtC,YAAY,KAAK,MAAM,IAAI,WAAW,CAAC,KAAK;AAAA,IAC5C,UAAU,KAAK;AAAA,IACf,MAAM,KAAK;AAAA,IACX,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACjC,GAAI,KAAK,SAAS,WAAW,EAAE,iBAAiB,KAAK,SAAS,SAAS,IAAI,CAAC;AAAA,IAC5E,GAAI,KAAK,SAAS,SAAS,EAAE,eAAe,KAAK,SAAS,OAAO,IAAI,CAAC;AAAA,IACtE,GAAI,IAAI,YAAY,EAAE,QAAQ,KAAK,MAAM,IAAI,UAAW,CAAC,EAAE,IAAI,CAAC;AAAA,IAChE,GAAI,OAAO,IAAI,eAAe,YAAY,EAAE,UAAU,IAAI,WAAW,IAAI,CAAC;AAAA,EAC5E;AACF;AAaO,SAAS,iBAAiB,MAAmH;AAClJ,QAAM,OAAO,QAAQ,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS;AAC3D,OAAK,QAAQ,gBAAgB,gBAAgB;AAC7C,OAAK,QAAQ,OAAO,kBAAkB,MAAM,IAAI;AAChD,SAAO,MAAM,KAAK,QAAQ,gBAAgB,gBAAgB;AAC5D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/main-crashes.ts","../src/main.ts"],"sourcesContent":["import * as nodeFs from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { MAIN_ERRORS_CHANNEL, MAIN_ERRORS_READY_CHANNEL, type MainErrorEvent } from \"./types\";\n\n/**\n * Main-process error and crash capture (Sentry Electron's main-process integrations): uncaught\n * exceptions, unhandled rejections, renderer and child processes that die, and, opt-in, Crashpad\n * minidumps. The main process has no Guidely client, so events wait in a small file under\n * `userData` (they survive the crash or restart that usually follows) until a window's client takes\n * them over IPC and sends them; each event goes to exactly one window.\n */\n\n/** The parts of Electron's `app` crash capture uses. */\nexport interface CrashAppLike {\n getName?(): string;\n getVersion?(): string;\n getPath?(name: \"userData\" | \"crashDumps\"): string;\n on?(event: string, listener: (...args: any[]) => void): unknown;\n removeListener?(event: string, listener: (...args: any[]) => void): unknown;\n exit?(code?: number): void;\n}\n\n/** The parts of Node's `process` crash capture uses. */\nexport interface CrashProcessLike {\n on(event: string, listener: (...args: any[]) => void): unknown;\n removeListener(event: string, listener: (...args: any[]) => void): unknown;\n listenerCount(event: string): number;\n exit(code?: number): void;\n platform: string;\n versions: Record<string, string | undefined>;\n getSystemVersion?: () => string;\n}\n\n/** The parts of `node:fs` crash capture uses (tests pass a fake). */\nexport interface CrashFsLike {\n readFileSync(path: string, encoding: \"utf8\"): string;\n writeFileSync(path: string, data: string): void;\n readdirSync(path: string, options: { withFileTypes: true }): Array<{ name: string; isDirectory(): boolean; isFile(): boolean }>;\n statSync(path: string): { mtimeMs: number; size: number };\n}\n\n/** The parts of Electron's `crashReporter` used for native crashes. */\nexport interface CrashReporterLike {\n start(options: { uploadToServer: boolean; submitURL?: string; compress?: boolean }): void;\n}\n\n/** Electron's `webContents` module: windows are told when there are errors to take. */\nexport interface WebContentsModuleLike {\n getAllWebContents(): Array<{ send(channel: string, ...args: unknown[]): void; isDestroyed?(): boolean }>;\n}\n\nexport interface MainCrashOptions {\n /** Report uncaught exceptions and unhandled rejections (default true). */\n processHandlers?: boolean;\n /**\n * After an uncaught exception: log it, then `app.exit(1)`, as a Node process would (default\n * false: Electron shows its \"A JavaScript error occurred in the main process\" dialog and keeps\n * running, and so does it with Guidely). Unhandled rejections are reported and logged, never fatal.\n */\n exitOnUncaught?: boolean;\n /** Report renderers, GPU and utility processes that crash, are killed or run out of memory (default true). */\n processGone?: boolean;\n /**\n * Native crashes: Guidely starts Electron's `crashReporter` (`uploadToServer: false`) and, at the\n * next start, reports each new minidump by file name (the dump itself is not uploaded yet). Pass\n * `true` when your app starts the crash reporter itself. Default off.\n */\n nativeCrashes?: boolean | { crashReporter: CrashReporterLike };\n /** Where waiting events are kept (default `<userData>/guidely-main-errors.json`; `null`: in memory only). */\n storePath?: string | null;\n /** Electron's `webContents` module, to tell open windows to take new events right away (else the next window load takes them). */\n webContents?: WebContentsModuleLike;\n}\n\nexport interface MainCrashHandle {\n /** Reports an error from main-process code (`level` default `error`). */\n captureException(error: unknown, context?: { level?: \"fatal\" | \"error\"; tags?: Record<string, string>; contexts?: Record<string, Record<string, unknown>>; fingerprint?: string[] }): string;\n /** The waiting events, removed from the store (what the IPC handler answers). */\n take(): MainErrorEvent[];\n /** Removes the listeners and the IPC handler. */\n dispose(): void;\n}\n\nconst MAX_EVENTS = 30;\nconst OS_NAMES: Record<string, string> = { darwin: \"macOS\", win32: \"Windows\", linux: \"Linux\" };\n/** Reasons a process ends that are crashes; the rest (`abnormal-exit`, `launch-failed`, …) are errors. */\nconst FATAL_REASONS = new Set([\"crashed\", \"oom\", \"killed\"]);\n\nconst safe = <T>(fn: () => T): T | undefined => {\n try {\n return fn();\n } catch {\n return undefined;\n }\n};\n\nconst newId = () => Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join(\"\");\n\n/** An error (or any thrown value) as plain data for IPC, with up to 5 causes. */\nfunction serialize(error: unknown, depth = 0): Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\"> {\n const e = error as { name?: unknown; message?: unknown; stack?: unknown; cause?: unknown } | null;\n const isErr = !!e && typeof e === \"object\" && typeof e.message === \"string\";\n const out: Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\"> = {\n type: (isErr && typeof e!.name === \"string\" && e!.name) || (isErr ? \"Error\" : \"UnhandledRejection\"),\n message: (isErr ? String(e!.message) : safe(() => (typeof error === \"string\" ? error : JSON.stringify(error))) ?? String(error)).slice(0, 1000),\n ...(isErr && typeof e!.stack === \"string\" ? { stack: e!.stack.slice(0, 16000) } : {}),\n };\n if (isErr && e!.cause !== undefined && e!.cause !== null && depth < 5) out.cause = serialize(e!.cause, depth + 1);\n return out;\n}\n\n/**\n * Installs main-process crash capture and the IPC handler windows take events from. Call it as\n * early as you can in the main process (before `app.whenReady()` works too), or pass\n * `errors: true | { … }` to `setupGuidelyMain`. Never throws.\n */\nexport function installGuidelyCrashHandlers(opts: { app: CrashAppLike; ipcMain: { handle(channel: string, listener: (event: unknown, ...args: unknown[]) => unknown): void; removeHandler?(channel: string): void }; process?: CrashProcessLike; fs?: CrashFsLike } & MainCrashOptions): MainCrashHandle {\n const proc = opts.process ?? (process as unknown as CrashProcessLike);\n const fs = opts.fs ?? (nodeFs as unknown as CrashFsLike);\n const { app } = opts;\n const userData = safe(() => app.getPath?.(\"userData\"));\n const storePath = opts.storePath === undefined ? (userData ? join(userData, \"guidely-main-errors.json\") : null) : opts.storePath;\n const cleanups: Array<() => void> = [];\n\n let events: MainErrorEvent[] = [];\n if (storePath) {\n const saved = safe(() => JSON.parse(fs.readFileSync(storePath, \"utf8\")) as unknown);\n if (Array.isArray(saved)) events = saved.filter((e): e is MainErrorEvent => !!e && typeof e === \"object\" && typeof (e as MainErrorEvent).id === \"string\").slice(-MAX_EVENTS);\n }\n // Synchronous: the process may be about to exit.\n const persist = () => {\n if (storePath) safe(() => fs.writeFileSync(storePath, JSON.stringify(events)));\n };\n const notify = () => {\n safe(() => {\n for (const wc of opts.webContents?.getAllWebContents() ?? []) if (!safe(() => wc.isDestroyed?.())) safe(() => wc.send(MAIN_ERRORS_READY_CHANNEL));\n });\n };\n\n const baseContexts = (): Record<string, Record<string, unknown>> => {\n const osVersion = safe(() => proc.getSystemVersion?.());\n return {\n electron: { version: proc.versions.electron, chrome: proc.versions.chrome, node: proc.versions.node, processType: \"main\" },\n os: { name: OS_NAMES[proc.platform] ?? proc.platform, ...(osVersion ? { version: osVersion } : {}) },\n app: { app_name: safe(() => app.getName?.()), app_version: safe(() => app.getVersion?.()) },\n };\n };\n\n const record = (data: Pick<MainErrorEvent, \"type\" | \"message\" | \"stack\" | \"cause\">, extra: { level: \"fatal\" | \"error\"; mechanism: string; handled?: boolean; contexts?: Record<string, Record<string, unknown>>; tags?: Record<string, string>; fingerprint?: string[] }): string => {\n try {\n const contexts = baseContexts();\n for (const [k, v] of Object.entries(extra.contexts ?? {})) contexts[k] = { ...(contexts[k] ?? {}), ...v };\n const event: MainErrorEvent = { id: newId(), timestamp: Date.now(), ...data, level: extra.level, mechanism: extra.mechanism, handled: extra.handled ?? false, contexts, ...(extra.tags ? { tags: extra.tags } : {}), ...(extra.fingerprint ? { fingerprint: extra.fingerprint } : {}) };\n events.push(event);\n if (events.length > MAX_EVENTS) events.splice(0, events.length - MAX_EVENTS);\n persist();\n notify();\n return event.id;\n } catch {\n return \"\";\n }\n };\n\n const on = (target: { on?(e: string, l: (...a: any[]) => void): unknown; removeListener?(e: string, l: (...a: any[]) => void): unknown }, event: string, listener: (...args: any[]) => void) => {\n if (typeof target.on !== \"function\") return;\n safe(() => target.on!(event, listener));\n cleanups.push(() => target.removeListener?.(event, listener));\n };\n\n if (opts.processHandlers !== false) {\n // The monitor sees uncaught exceptions without changing what happens next (Electron's dialog, or Node's exit).\n on(proc, \"uncaughtExceptionMonitor\", (error: unknown, origin?: string) => {\n record(serialize(error), { level: \"fatal\", mechanism: origin === \"unhandledRejection\" ? \"unhandledRejection\" : \"uncaughtException\" });\n if (opts.exitOnUncaught) {\n safe(() => console.error(error));\n if (typeof app.exit === \"function\") app.exit(1);\n else proc.exit(1);\n }\n });\n const onRejection = (reason: unknown) => {\n record(serialize(reason), { level: \"error\", mechanism: \"unhandledRejection\" });\n // Listening stops Node from raising it: keep it visible, as Node's `warn` mode does.\n if (proc.listenerCount(\"unhandledRejection\") <= 1) safe(() => console.error(\"Unhandled promise rejection in the main process:\", reason));\n };\n on(proc, \"unhandledRejection\", onRejection);\n }\n\n if (opts.processGone !== false) {\n on(app, \"render-process-gone\", (_event: unknown, _webContents: unknown, details: { reason?: string; exitCode?: number } = {}) => {\n if (!details.reason || details.reason === \"clean-exit\") return;\n record({ type: \"RenderProcessGone\", message: `Renderer process gone: ${details.reason}` }, { level: FATAL_REASONS.has(details.reason) ? \"fatal\" : \"error\", mechanism: \"render-process-gone\", contexts: { electron: { reason: details.reason, exitCode: details.exitCode, processType: \"renderer\" } } });\n });\n on(app, \"child-process-gone\", (_event: unknown, details: { type?: string; reason?: string; exitCode?: number; serviceName?: string; name?: string } = {}) => {\n if (!details.reason || details.reason === \"clean-exit\") return;\n const kind = details.type ?? \"Child\";\n record({ type: \"ChildProcessGone\", message: `${kind} process gone: ${details.reason}` }, { level: FATAL_REASONS.has(details.reason) ? \"fatal\" : \"error\", mechanism: \"child-process-gone\", contexts: { electron: { reason: details.reason, exitCode: details.exitCode, processType: kind, ...(details.serviceName ? { serviceName: details.serviceName } : {}), ...(details.name ? { name: details.name } : {}) } } });\n });\n }\n\n if (opts.nativeCrashes) {\n const reporter = typeof opts.nativeCrashes === \"object\" ? opts.nativeCrashes.crashReporter : undefined;\n safe(() => reporter?.start({ uploadToServer: false }));\n reportNewMinidumps(app, fs, userData, (file, stat) =>\n record({ type: \"NativeCrash\", message: `Native crash (minidump ${basename(file)})` }, { level: \"fatal\", mechanism: \"minidump\", contexts: { electron: { minidump: basename(file), minidumpSize: stat.size, crashedAt: new Date(stat.mtimeMs).toISOString() } } }),\n );\n }\n\n safe(() => {\n opts.ipcMain.removeHandler?.(MAIN_ERRORS_CHANNEL);\n opts.ipcMain.handle(MAIN_ERRORS_CHANNEL, () => handle.take());\n cleanups.push(() => opts.ipcMain.removeHandler?.(MAIN_ERRORS_CHANNEL));\n });\n\n const handle: MainCrashHandle = {\n captureException: (error, context = {}) => record(serialize(error), { level: context.level ?? \"error\", mechanism: \"generic\", handled: true, contexts: context.contexts, tags: context.tags, fingerprint: context.fingerprint }),\n take: () => {\n const out = events;\n events = [];\n if (out.length) persist();\n return out;\n },\n dispose: () => {\n for (const off of cleanups.splice(0)) safe(off);\n },\n };\n return handle;\n}\n\n/**\n * Reports the minidumps Crashpad wrote since the last start (`<crashDumps>/**.dmp`, two levels\n * deep). The first run only records the dumps already there.\n */\nfunction reportNewMinidumps(app: CrashAppLike, fs: CrashFsLike, userData: string | undefined, report: (file: string, stat: { mtimeMs: number; size: number }) => void): void {\n const dir = safe(() => app.getPath?.(\"crashDumps\"));\n if (!dir || !userData) return;\n const seenPath = join(userData, \"guidely-minidumps.json\");\n const saved = safe(() => JSON.parse(fs.readFileSync(seenPath, \"utf8\")) as unknown);\n const firstRun = !Array.isArray(saved);\n const seen = new Set(Array.isArray(saved) ? saved.filter((s): s is string => typeof s === \"string\") : []);\n const found: string[] = [];\n const walk = (d: string, depth: number) => {\n for (const entry of safe(() => fs.readdirSync(d, { withFileTypes: true })) ?? []) {\n const path = join(d, entry.name);\n if (entry.isDirectory() && depth < 2) walk(path, depth + 1);\n else if (entry.isFile() && entry.name.endsWith(\".dmp\")) found.push(path);\n }\n };\n walk(dir, 0);\n for (const file of found) {\n if (seen.has(file) || firstRun) continue;\n const stat = safe(() => fs.statSync(file));\n if (stat) report(file, stat);\n }\n // Only the dumps still on disk: Crashpad prunes old ones.\n safe(() => fs.writeFileSync(seenPath, JSON.stringify(found.slice(-500))));\n}\n","import { installGuidelyCrashHandlers, type CrashAppLike, type MainCrashOptions } from \"./main-crashes\";\nimport { APP_INFO_CHANNEL, type ElectronAppInfo } from \"./types\";\n\nexport { APP_INFO_CHANNEL, MAIN_ERRORS_CHANNEL, MAIN_ERRORS_READY_CHANNEL } from \"./types\";\nexport type { ElectronAppInfo, MainErrorEvent } from \"./types\";\nexport { installGuidelyCrashHandlers } from \"./main-crashes\";\nexport type { CrashAppLike, CrashFsLike, CrashProcessLike, CrashReporterLike, MainCrashHandle, MainCrashOptions, WebContentsModuleLike } from \"./main-crashes\";\n\n/** The parts of Electron's `app` the helper reads (pass `import { app } from \"electron\"`). */\nexport interface ElectronAppLike {\n getName(): string;\n getVersion(): string;\n getLocale?(): string;\n isPackaged?: boolean;\n}\n\n/** The parts of Electron's `ipcMain` the helper uses. */\nexport interface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: unknown[]) => unknown): void;\n removeHandler?(channel: string): void;\n}\n\n/** Node's `process` fields read for the app info (overridable for tests). */\nexport interface ProcessLike {\n platform: string;\n arch: string;\n versions: Record<string, string | undefined>;\n getSystemVersion?: () => string;\n}\n\n/** Collects the app info Guidely reports: app name and version, OS, architecture and Electron/Chrome versions. */\nexport function appInfo(app: ElectronAppLike, proc: ProcessLike = process as unknown as ProcessLike, osRelease?: () => string): ElectronAppInfo {\n const safe = <T>(fn: () => T): T | undefined => {\n try {\n return fn();\n } catch {\n return undefined;\n }\n };\n const osVersion = safe(() => proc.getSystemVersion?.()) || safe(() => osRelease?.());\n return {\n appName: safe(() => app.getName()) ?? \"app\",\n appVersion: safe(() => app.getVersion()) ?? \"0.0.0\",\n platform: proc.platform,\n arch: proc.arch,\n ...(osVersion ? { osVersion } : {}),\n ...(proc.versions.electron ? { electronVersion: proc.versions.electron } : {}),\n ...(proc.versions.chrome ? { chromeVersion: proc.versions.chrome } : {}),\n ...(proc.versions.node ? { nodeVersion: proc.versions.node } : {}),\n ...(app.getLocale ? { locale: safe(() => app.getLocale!()) } : {}),\n ...(typeof app.isPackaged === \"boolean\" ? { packaged: app.isPackaged } : {}),\n };\n}\n\n/**\n * Main process: answers the preload bridge's app-info request. Call once after `app` is ready:\n *\n * ```ts\n * import { app, ipcMain } from \"electron\";\n * import { setupGuidelyMain } from \"@guidelytech/electron/main\";\n * app.whenReady().then(() => { setupGuidelyMain({ app, ipcMain }); createWindow(); });\n * ```\n *\n * Returns a function that removes the handler.\n */\nexport function setupGuidelyMain(opts: { app: ElectronAppLike & CrashAppLike; ipcMain: IpcMainLike; process?: ProcessLike; osRelease?: () => string; errors?: boolean | MainCrashOptions }): () => void {\n const info = appInfo(opts.app, opts.process, opts.osRelease);\n opts.ipcMain.removeHandler?.(APP_INFO_CHANNEL);\n opts.ipcMain.handle(APP_INFO_CHANNEL, () => info);\n // `errors`: main-process crash capture (see installGuidelyCrashHandlers), sent through the windows' clients.\n const crashes = opts.errors ? installGuidelyCrashHandlers({ app: opts.app, ipcMain: opts.ipcMain, ...(typeof opts.errors === \"object\" ? opts.errors : {}) }) : null;\n return () => {\n opts.ipcMain.removeHandler?.(APP_INFO_CHANNEL);\n crashes?.dispose();\n };\n}\n"],"mappings":"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","names":["safe"]}
|
package/dist/preload.cjs
CHANGED
|
@@ -25,6 +25,8 @@ __export(preload_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(preload_exports);
|
|
26
26
|
|
|
27
27
|
// src/types.ts
|
|
28
|
+
var MAIN_ERRORS_CHANNEL = "guidely:main-errors";
|
|
29
|
+
var MAIN_ERRORS_READY_CHANNEL = "guidely:main-errors-ready";
|
|
28
30
|
var APP_INFO_CHANNEL = "guidely:app-info";
|
|
29
31
|
var BRIDGE_KEY = "guidelyElectron";
|
|
30
32
|
|
|
@@ -32,7 +34,14 @@ var BRIDGE_KEY = "guidelyElectron";
|
|
|
32
34
|
function exposeGuidely(opts) {
|
|
33
35
|
let cached = null;
|
|
34
36
|
const bridge = {
|
|
35
|
-
getAppInfo: () => cached ?? (cached = opts.ipcRenderer.invoke(APP_INFO_CHANNEL))
|
|
37
|
+
getAppInfo: () => cached ?? (cached = opts.ipcRenderer.invoke(APP_INFO_CHANNEL)),
|
|
38
|
+
// Main-process errors (`setupGuidelyMain({ errors })`); an empty list when the main process does not collect them.
|
|
39
|
+
takeMainErrors: () => opts.ipcRenderer.invoke(MAIN_ERRORS_CHANNEL).then((v) => Array.isArray(v) ? v : [], () => []),
|
|
40
|
+
onMainErrors: (listener) => {
|
|
41
|
+
const l = () => listener();
|
|
42
|
+
opts.ipcRenderer.on?.(MAIN_ERRORS_READY_CHANNEL, l);
|
|
43
|
+
return () => void opts.ipcRenderer.removeListener?.(MAIN_ERRORS_READY_CHANNEL, l);
|
|
44
|
+
}
|
|
36
45
|
};
|
|
37
46
|
opts.contextBridge.exposeInMainWorld(BRIDGE_KEY, bridge);
|
|
38
47
|
}
|
package/dist/preload.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/preload.ts","../src/types.ts"],"sourcesContent":["import { APP_INFO_CHANNEL, BRIDGE_KEY, type ElectronAppInfo, type GuidelyElectronBridge } from \"./types\";\n\nexport type { ElectronAppInfo, GuidelyElectronBridge } from \"./types\";\n\n/** The parts of Electron's `contextBridge` the helper uses. */\nexport interface ContextBridgeLike {\n exposeInMainWorld(key: string, api: unknown): void;\n}\n\n/** The parts of Electron's `ipcRenderer` the helper uses. */\nexport interface IpcRendererLike {\n invoke(channel: string, ...args: unknown[]): Promise<unknown>;\n}\n\n/**\n * Preload script: exposes `window.guidelyElectron.getAppInfo()` to the page (works with\n * `contextIsolation: true`; the page gets a function, never `ipcRenderer` itself).\n *\n * ```ts\n * // preload.ts\n * import { contextBridge, ipcRenderer } from \"electron\";\n * import { exposeGuidely } from \"@guidelytech/electron/preload\";\n * exposeGuidely({ contextBridge, ipcRenderer });\n * ```\n *\n * Sandboxed preloads (Electron's default since 20) can't `require` npm packages: bundle the preload\n * (esbuild, Vite, webpack), or copy these few lines into it.\n */\nexport function exposeGuidely(opts: { contextBridge: ContextBridgeLike; ipcRenderer: IpcRendererLike }): void {\n let cached: Promise<ElectronAppInfo> | null = null;\n const bridge: GuidelyElectronBridge = {\n getAppInfo: () => (cached ??= opts.ipcRenderer.invoke(APP_INFO_CHANNEL) as Promise<ElectronAppInfo>),\n };\n opts.contextBridge.exposeInMainWorld(BRIDGE_KEY, bridge);\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;;;
|
|
1
|
+
{"version":3,"sources":["../src/preload.ts","../src/types.ts"],"sourcesContent":["import { APP_INFO_CHANNEL, BRIDGE_KEY, MAIN_ERRORS_CHANNEL, MAIN_ERRORS_READY_CHANNEL, type ElectronAppInfo, type GuidelyElectronBridge, type MainErrorEvent } from \"./types\";\n\nexport type { ElectronAppInfo, GuidelyElectronBridge, MainErrorEvent } from \"./types\";\n\n/** The parts of Electron's `contextBridge` the helper uses. */\nexport interface ContextBridgeLike {\n exposeInMainWorld(key: string, api: unknown): void;\n}\n\n/** The parts of Electron's `ipcRenderer` the helper uses. */\nexport interface IpcRendererLike {\n invoke(channel: string, ...args: unknown[]): Promise<unknown>;\n /** Used to hear about new main-process errors (optional: without it windows take them when they load). */\n on?(channel: string, listener: (...args: any[]) => void): unknown;\n removeListener?(channel: string, listener: (...args: any[]) => void): unknown;\n}\n\n/**\n * Preload script: exposes `window.guidelyElectron.getAppInfo()` to the page (works with\n * `contextIsolation: true`; the page gets a function, never `ipcRenderer` itself).\n *\n * ```ts\n * // preload.ts\n * import { contextBridge, ipcRenderer } from \"electron\";\n * import { exposeGuidely } from \"@guidelytech/electron/preload\";\n * exposeGuidely({ contextBridge, ipcRenderer });\n * ```\n *\n * Sandboxed preloads (Electron's default since 20) can't `require` npm packages: bundle the preload\n * (esbuild, Vite, webpack), or copy these few lines into it.\n */\nexport function exposeGuidely(opts: { contextBridge: ContextBridgeLike; ipcRenderer: IpcRendererLike }): void {\n let cached: Promise<ElectronAppInfo> | null = null;\n const bridge: GuidelyElectronBridge = {\n getAppInfo: () => (cached ??= opts.ipcRenderer.invoke(APP_INFO_CHANNEL) as Promise<ElectronAppInfo>),\n // Main-process errors (`setupGuidelyMain({ errors })`); an empty list when the main process does not collect them.\n takeMainErrors: () => opts.ipcRenderer.invoke(MAIN_ERRORS_CHANNEL).then((v) => (Array.isArray(v) ? (v as MainErrorEvent[]) : []), () => []),\n onMainErrors: (listener) => {\n const l = () => listener();\n opts.ipcRenderer.on?.(MAIN_ERRORS_READY_CHANNEL, l);\n return () => void opts.ipcRenderer.removeListener?.(MAIN_ERRORS_READY_CHANNEL, l);\n },\n };\n opts.contextBridge.exposeInMainWorld(BRIDGE_KEY, bridge);\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmDO,IAAM,sBAAsB;AAE5B,IAAM,4BAA4B;AAGlC,IAAM,mBAAmB;AAEzB,IAAM,aAAa;;;AD3BnB,SAAS,cAAc,MAAgF;AAC5G,MAAI,SAA0C;AAC9C,QAAM,SAAgC;AAAA,IACpC,YAAY,MAAO,oBAAW,KAAK,YAAY,OAAO,gBAAgB;AAAA;AAAA,IAEtE,gBAAgB,MAAM,KAAK,YAAY,OAAO,mBAAmB,EAAE,KAAK,CAAC,MAAO,MAAM,QAAQ,CAAC,IAAK,IAAyB,CAAC,GAAI,MAAM,CAAC,CAAC;AAAA,IAC1I,cAAc,CAAC,aAAa;AAC1B,YAAM,IAAI,MAAM,SAAS;AACzB,WAAK,YAAY,KAAK,2BAA2B,CAAC;AAClD,aAAO,MAAM,KAAK,KAAK,YAAY,iBAAiB,2BAA2B,CAAC;AAAA,IAClF;AAAA,EACF;AACA,OAAK,cAAc,kBAAkB,YAAY,MAAM;AACzD;","names":[]}
|
package/dist/preload.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { E as ElectronAppInfo, G as GuidelyElectronBridge } from './types-
|
|
1
|
+
export { E as ElectronAppInfo, G as GuidelyElectronBridge, b as MainErrorEvent } from './types-jhX9pTdK.cjs';
|
|
2
2
|
|
|
3
3
|
/** The parts of Electron's `contextBridge` the helper uses. */
|
|
4
4
|
interface ContextBridgeLike {
|
|
@@ -7,6 +7,9 @@ interface ContextBridgeLike {
|
|
|
7
7
|
/** The parts of Electron's `ipcRenderer` the helper uses. */
|
|
8
8
|
interface IpcRendererLike {
|
|
9
9
|
invoke(channel: string, ...args: unknown[]): Promise<unknown>;
|
|
10
|
+
/** Used to hear about new main-process errors (optional: without it windows take them when they load). */
|
|
11
|
+
on?(channel: string, listener: (...args: any[]) => void): unknown;
|
|
12
|
+
removeListener?(channel: string, listener: (...args: any[]) => void): unknown;
|
|
10
13
|
}
|
|
11
14
|
/**
|
|
12
15
|
* Preload script: exposes `window.guidelyElectron.getAppInfo()` to the page (works with
|
package/dist/preload.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { E as ElectronAppInfo, G as GuidelyElectronBridge } from './types-
|
|
1
|
+
export { E as ElectronAppInfo, G as GuidelyElectronBridge, b as MainErrorEvent } from './types-jhX9pTdK.js';
|
|
2
2
|
|
|
3
3
|
/** The parts of Electron's `contextBridge` the helper uses. */
|
|
4
4
|
interface ContextBridgeLike {
|
|
@@ -7,6 +7,9 @@ interface ContextBridgeLike {
|
|
|
7
7
|
/** The parts of Electron's `ipcRenderer` the helper uses. */
|
|
8
8
|
interface IpcRendererLike {
|
|
9
9
|
invoke(channel: string, ...args: unknown[]): Promise<unknown>;
|
|
10
|
+
/** Used to hear about new main-process errors (optional: without it windows take them when they load). */
|
|
11
|
+
on?(channel: string, listener: (...args: any[]) => void): unknown;
|
|
12
|
+
removeListener?(channel: string, listener: (...args: any[]) => void): unknown;
|
|
10
13
|
}
|
|
11
14
|
/**
|
|
12
15
|
* Preload script: exposes `window.guidelyElectron.getAppInfo()` to the page (works with
|
package/dist/preload.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
APP_INFO_CHANNEL,
|
|
3
|
-
BRIDGE_KEY
|
|
4
|
-
|
|
3
|
+
BRIDGE_KEY,
|
|
4
|
+
MAIN_ERRORS_CHANNEL,
|
|
5
|
+
MAIN_ERRORS_READY_CHANNEL
|
|
6
|
+
} from "./chunk-JP3V2XK5.js";
|
|
5
7
|
|
|
6
8
|
// src/preload.ts
|
|
7
9
|
function exposeGuidely(opts) {
|
|
8
10
|
let cached = null;
|
|
9
11
|
const bridge = {
|
|
10
|
-
getAppInfo: () => cached ?? (cached = opts.ipcRenderer.invoke(APP_INFO_CHANNEL))
|
|
12
|
+
getAppInfo: () => cached ?? (cached = opts.ipcRenderer.invoke(APP_INFO_CHANNEL)),
|
|
13
|
+
// Main-process errors (`setupGuidelyMain({ errors })`); an empty list when the main process does not collect them.
|
|
14
|
+
takeMainErrors: () => opts.ipcRenderer.invoke(MAIN_ERRORS_CHANNEL).then((v) => Array.isArray(v) ? v : [], () => []),
|
|
15
|
+
onMainErrors: (listener) => {
|
|
16
|
+
const l = () => listener();
|
|
17
|
+
opts.ipcRenderer.on?.(MAIN_ERRORS_READY_CHANNEL, l);
|
|
18
|
+
return () => void opts.ipcRenderer.removeListener?.(MAIN_ERRORS_READY_CHANNEL, l);
|
|
19
|
+
}
|
|
11
20
|
};
|
|
12
21
|
opts.contextBridge.exposeInMainWorld(BRIDGE_KEY, bridge);
|
|
13
22
|
}
|
package/dist/preload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/preload.ts"],"sourcesContent":["import { APP_INFO_CHANNEL, BRIDGE_KEY, type ElectronAppInfo, type GuidelyElectronBridge } from \"./types\";\n\nexport type { ElectronAppInfo, GuidelyElectronBridge } from \"./types\";\n\n/** The parts of Electron's `contextBridge` the helper uses. */\nexport interface ContextBridgeLike {\n exposeInMainWorld(key: string, api: unknown): void;\n}\n\n/** The parts of Electron's `ipcRenderer` the helper uses. */\nexport interface IpcRendererLike {\n invoke(channel: string, ...args: unknown[]): Promise<unknown>;\n}\n\n/**\n * Preload script: exposes `window.guidelyElectron.getAppInfo()` to the page (works with\n * `contextIsolation: true`; the page gets a function, never `ipcRenderer` itself).\n *\n * ```ts\n * // preload.ts\n * import { contextBridge, ipcRenderer } from \"electron\";\n * import { exposeGuidely } from \"@guidelytech/electron/preload\";\n * exposeGuidely({ contextBridge, ipcRenderer });\n * ```\n *\n * Sandboxed preloads (Electron's default since 20) can't `require` npm packages: bundle the preload\n * (esbuild, Vite, webpack), or copy these few lines into it.\n */\nexport function exposeGuidely(opts: { contextBridge: ContextBridgeLike; ipcRenderer: IpcRendererLike }): void {\n let cached: Promise<ElectronAppInfo> | null = null;\n const bridge: GuidelyElectronBridge = {\n getAppInfo: () => (cached ??= opts.ipcRenderer.invoke(APP_INFO_CHANNEL) as Promise<ElectronAppInfo>),\n };\n opts.contextBridge.exposeInMainWorld(BRIDGE_KEY, bridge);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/preload.ts"],"sourcesContent":["import { APP_INFO_CHANNEL, BRIDGE_KEY, MAIN_ERRORS_CHANNEL, MAIN_ERRORS_READY_CHANNEL, type ElectronAppInfo, type GuidelyElectronBridge, type MainErrorEvent } from \"./types\";\n\nexport type { ElectronAppInfo, GuidelyElectronBridge, MainErrorEvent } from \"./types\";\n\n/** The parts of Electron's `contextBridge` the helper uses. */\nexport interface ContextBridgeLike {\n exposeInMainWorld(key: string, api: unknown): void;\n}\n\n/** The parts of Electron's `ipcRenderer` the helper uses. */\nexport interface IpcRendererLike {\n invoke(channel: string, ...args: unknown[]): Promise<unknown>;\n /** Used to hear about new main-process errors (optional: without it windows take them when they load). */\n on?(channel: string, listener: (...args: any[]) => void): unknown;\n removeListener?(channel: string, listener: (...args: any[]) => void): unknown;\n}\n\n/**\n * Preload script: exposes `window.guidelyElectron.getAppInfo()` to the page (works with\n * `contextIsolation: true`; the page gets a function, never `ipcRenderer` itself).\n *\n * ```ts\n * // preload.ts\n * import { contextBridge, ipcRenderer } from \"electron\";\n * import { exposeGuidely } from \"@guidelytech/electron/preload\";\n * exposeGuidely({ contextBridge, ipcRenderer });\n * ```\n *\n * Sandboxed preloads (Electron's default since 20) can't `require` npm packages: bundle the preload\n * (esbuild, Vite, webpack), or copy these few lines into it.\n */\nexport function exposeGuidely(opts: { contextBridge: ContextBridgeLike; ipcRenderer: IpcRendererLike }): void {\n let cached: Promise<ElectronAppInfo> | null = null;\n const bridge: GuidelyElectronBridge = {\n getAppInfo: () => (cached ??= opts.ipcRenderer.invoke(APP_INFO_CHANNEL) as Promise<ElectronAppInfo>),\n // Main-process errors (`setupGuidelyMain({ errors })`); an empty list when the main process does not collect them.\n takeMainErrors: () => opts.ipcRenderer.invoke(MAIN_ERRORS_CHANNEL).then((v) => (Array.isArray(v) ? (v as MainErrorEvent[]) : []), () => []),\n onMainErrors: (listener) => {\n const l = () => listener();\n opts.ipcRenderer.on?.(MAIN_ERRORS_READY_CHANNEL, l);\n return () => void opts.ipcRenderer.removeListener?.(MAIN_ERRORS_READY_CHANNEL, l);\n },\n };\n opts.contextBridge.exposeInMainWorld(BRIDGE_KEY, bridge);\n}\n"],"mappings":";;;;;;;;AA+BO,SAAS,cAAc,MAAgF;AAC5G,MAAI,SAA0C;AAC9C,QAAM,SAAgC;AAAA,IACpC,YAAY,MAAO,oBAAW,KAAK,YAAY,OAAO,gBAAgB;AAAA;AAAA,IAEtE,gBAAgB,MAAM,KAAK,YAAY,OAAO,mBAAmB,EAAE,KAAK,CAAC,MAAO,MAAM,QAAQ,CAAC,IAAK,IAAyB,CAAC,GAAI,MAAM,CAAC,CAAC;AAAA,IAC1I,cAAc,CAAC,aAAa;AAC1B,YAAM,IAAI,MAAM,SAAS;AACzB,WAAK,YAAY,KAAK,2BAA2B,CAAC;AAClD,aAAO,MAAM,KAAK,KAAK,YAAY,iBAAiB,2BAA2B,CAAC;AAAA,IAClF;AAAA,EACF;AACA,OAAK,cAAc,kBAAkB,YAAY,MAAM;AACzD;","names":[]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** What the main process knows about the desktop app, sent to the renderer over IPC. */
|
|
2
|
+
interface ElectronAppInfo {
|
|
3
|
+
/** `app.getName()`. */
|
|
4
|
+
appName: string;
|
|
5
|
+
/** `app.getVersion()` (from package.json). */
|
|
6
|
+
appVersion: string;
|
|
7
|
+
/** `process.platform`: `darwin`, `win32`, `linux`. */
|
|
8
|
+
platform: string;
|
|
9
|
+
/** `process.arch`: `x64`, `arm64`, … */
|
|
10
|
+
arch: string;
|
|
11
|
+
/** `os.release()` / `process.getSystemVersion()`, when available. */
|
|
12
|
+
osVersion?: string;
|
|
13
|
+
electronVersion?: string;
|
|
14
|
+
chromeVersion?: string;
|
|
15
|
+
/** The main process's Node.js version. */
|
|
16
|
+
nodeVersion?: string;
|
|
17
|
+
/** `app.getLocale()`. */
|
|
18
|
+
locale?: string;
|
|
19
|
+
/** `app.isPackaged`: false while running from source (`electron .`). */
|
|
20
|
+
packaged?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** The API the preload script exposes on `window.guidelyElectron`. */
|
|
23
|
+
interface GuidelyElectronBridge {
|
|
24
|
+
getAppInfo(): Promise<ElectronAppInfo>;
|
|
25
|
+
/** Main-process errors and crashes waiting to be sent (each is handed to one window only). */
|
|
26
|
+
takeMainErrors?(): Promise<MainErrorEvent[]>;
|
|
27
|
+
/** Called when the main process has new errors to take; returns an unsubscribe function. */
|
|
28
|
+
onMainErrors?(listener: () => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
/** An error the main process caught (thrown, rejected, a process gone, a minidump), sent by a renderer's client. */
|
|
31
|
+
interface MainErrorEvent {
|
|
32
|
+
id: string;
|
|
33
|
+
/** When it happened (ms since epoch); it may be sent much later, after a restart. */
|
|
34
|
+
timestamp: number;
|
|
35
|
+
type: string;
|
|
36
|
+
message: string;
|
|
37
|
+
stack?: string;
|
|
38
|
+
/** The error's `cause`, serialized the same way. */
|
|
39
|
+
cause?: Pick<MainErrorEvent, "type" | "message" | "stack" | "cause">;
|
|
40
|
+
level: "fatal" | "error";
|
|
41
|
+
/** `uncaughtException`, `unhandledRejection`, `render-process-gone`, `child-process-gone`, `minidump`, `generic`. */
|
|
42
|
+
mechanism: string;
|
|
43
|
+
handled: boolean;
|
|
44
|
+
contexts: Record<string, Record<string, unknown>>;
|
|
45
|
+
tags?: Record<string, string>;
|
|
46
|
+
fingerprint?: string[];
|
|
47
|
+
}
|
|
48
|
+
/** IPC channel the preload bridge takes main-process errors from. */
|
|
49
|
+
declare const MAIN_ERRORS_CHANNEL = "guidely:main-errors";
|
|
50
|
+
/** IPC message the main process sends windows when it has errors to take. */
|
|
51
|
+
declare const MAIN_ERRORS_READY_CHANNEL = "guidely:main-errors-ready";
|
|
52
|
+
/** IPC channel used between the main-process handler and the preload bridge. */
|
|
53
|
+
declare const APP_INFO_CHANNEL = "guidely:app-info";
|
|
54
|
+
/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */
|
|
55
|
+
declare const BRIDGE_KEY = "guidelyElectron";
|
|
56
|
+
/** Desktop operating system names used in Guidely device properties. */
|
|
57
|
+
declare function osName(platform: string): string;
|
|
58
|
+
|
|
59
|
+
export { APP_INFO_CHANNEL as A, BRIDGE_KEY as B, type ElectronAppInfo as E, type GuidelyElectronBridge as G, MAIN_ERRORS_CHANNEL as M, MAIN_ERRORS_READY_CHANNEL as a, type MainErrorEvent as b, osName as o };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** What the main process knows about the desktop app, sent to the renderer over IPC. */
|
|
2
|
+
interface ElectronAppInfo {
|
|
3
|
+
/** `app.getName()`. */
|
|
4
|
+
appName: string;
|
|
5
|
+
/** `app.getVersion()` (from package.json). */
|
|
6
|
+
appVersion: string;
|
|
7
|
+
/** `process.platform`: `darwin`, `win32`, `linux`. */
|
|
8
|
+
platform: string;
|
|
9
|
+
/** `process.arch`: `x64`, `arm64`, … */
|
|
10
|
+
arch: string;
|
|
11
|
+
/** `os.release()` / `process.getSystemVersion()`, when available. */
|
|
12
|
+
osVersion?: string;
|
|
13
|
+
electronVersion?: string;
|
|
14
|
+
chromeVersion?: string;
|
|
15
|
+
/** The main process's Node.js version. */
|
|
16
|
+
nodeVersion?: string;
|
|
17
|
+
/** `app.getLocale()`. */
|
|
18
|
+
locale?: string;
|
|
19
|
+
/** `app.isPackaged`: false while running from source (`electron .`). */
|
|
20
|
+
packaged?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** The API the preload script exposes on `window.guidelyElectron`. */
|
|
23
|
+
interface GuidelyElectronBridge {
|
|
24
|
+
getAppInfo(): Promise<ElectronAppInfo>;
|
|
25
|
+
/** Main-process errors and crashes waiting to be sent (each is handed to one window only). */
|
|
26
|
+
takeMainErrors?(): Promise<MainErrorEvent[]>;
|
|
27
|
+
/** Called when the main process has new errors to take; returns an unsubscribe function. */
|
|
28
|
+
onMainErrors?(listener: () => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
/** An error the main process caught (thrown, rejected, a process gone, a minidump), sent by a renderer's client. */
|
|
31
|
+
interface MainErrorEvent {
|
|
32
|
+
id: string;
|
|
33
|
+
/** When it happened (ms since epoch); it may be sent much later, after a restart. */
|
|
34
|
+
timestamp: number;
|
|
35
|
+
type: string;
|
|
36
|
+
message: string;
|
|
37
|
+
stack?: string;
|
|
38
|
+
/** The error's `cause`, serialized the same way. */
|
|
39
|
+
cause?: Pick<MainErrorEvent, "type" | "message" | "stack" | "cause">;
|
|
40
|
+
level: "fatal" | "error";
|
|
41
|
+
/** `uncaughtException`, `unhandledRejection`, `render-process-gone`, `child-process-gone`, `minidump`, `generic`. */
|
|
42
|
+
mechanism: string;
|
|
43
|
+
handled: boolean;
|
|
44
|
+
contexts: Record<string, Record<string, unknown>>;
|
|
45
|
+
tags?: Record<string, string>;
|
|
46
|
+
fingerprint?: string[];
|
|
47
|
+
}
|
|
48
|
+
/** IPC channel the preload bridge takes main-process errors from. */
|
|
49
|
+
declare const MAIN_ERRORS_CHANNEL = "guidely:main-errors";
|
|
50
|
+
/** IPC message the main process sends windows when it has errors to take. */
|
|
51
|
+
declare const MAIN_ERRORS_READY_CHANNEL = "guidely:main-errors-ready";
|
|
52
|
+
/** IPC channel used between the main-process handler and the preload bridge. */
|
|
53
|
+
declare const APP_INFO_CHANNEL = "guidely:app-info";
|
|
54
|
+
/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */
|
|
55
|
+
declare const BRIDGE_KEY = "guidelyElectron";
|
|
56
|
+
/** Desktop operating system names used in Guidely device properties. */
|
|
57
|
+
declare function osName(platform: string): string;
|
|
58
|
+
|
|
59
|
+
export { APP_INFO_CHANNEL as A, BRIDGE_KEY as B, type ElectronAppInfo as E, type GuidelyElectronBridge as G, MAIN_ERRORS_CHANNEL as M, MAIN_ERRORS_READY_CHANNEL as a, type MainErrorEvent as b, osName as o };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guidelytech/electron",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Guidely for Electron apps: page paths for file:// and custom protocols, app version and OS metadata, preload bridge",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"README.md"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@guidelytech/sdk-core": "0.
|
|
33
|
+
"@guidelytech/sdk-core": "0.17.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@eslint/js": "^9.39.5",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"tsup": "^8.5.1",
|
|
40
40
|
"typescript": "^5.9.3",
|
|
41
41
|
"typescript-eslint": "^8.46.0",
|
|
42
|
-
"vitest": "^
|
|
43
|
-
"happy-dom": "^
|
|
42
|
+
"vitest": "^4.1.12",
|
|
43
|
+
"happy-dom": "^21.0.4"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
@@ -1 +0,0 @@
|
|
|
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 /** `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":";AA0BO,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":[]}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/** What the main process knows about the desktop app, sent to the renderer over IPC. */
|
|
2
|
-
interface ElectronAppInfo {
|
|
3
|
-
/** `app.getName()`. */
|
|
4
|
-
appName: string;
|
|
5
|
-
/** `app.getVersion()` (from package.json). */
|
|
6
|
-
appVersion: string;
|
|
7
|
-
/** `process.platform`: `darwin`, `win32`, `linux`. */
|
|
8
|
-
platform: string;
|
|
9
|
-
/** `process.arch`: `x64`, `arm64`, … */
|
|
10
|
-
arch: string;
|
|
11
|
-
/** `os.release()` / `process.getSystemVersion()`, when available. */
|
|
12
|
-
osVersion?: string;
|
|
13
|
-
electronVersion?: string;
|
|
14
|
-
chromeVersion?: string;
|
|
15
|
-
/** `app.getLocale()`. */
|
|
16
|
-
locale?: string;
|
|
17
|
-
/** `app.isPackaged`: false while running from source (`electron .`). */
|
|
18
|
-
packaged?: boolean;
|
|
19
|
-
}
|
|
20
|
-
/** The API the preload script exposes on `window.guidelyElectron`. */
|
|
21
|
-
interface GuidelyElectronBridge {
|
|
22
|
-
getAppInfo(): Promise<ElectronAppInfo>;
|
|
23
|
-
}
|
|
24
|
-
/** IPC channel used between the main-process handler and the preload bridge. */
|
|
25
|
-
declare const APP_INFO_CHANNEL = "guidely:app-info";
|
|
26
|
-
/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */
|
|
27
|
-
declare const BRIDGE_KEY = "guidelyElectron";
|
|
28
|
-
/** Desktop operating system names used in Guidely device properties. */
|
|
29
|
-
declare function osName(platform: string): string;
|
|
30
|
-
|
|
31
|
-
export { APP_INFO_CHANNEL as A, BRIDGE_KEY as B, type ElectronAppInfo as E, type GuidelyElectronBridge as G, osName as o };
|
package/dist/types-BEnWCecg.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/** What the main process knows about the desktop app, sent to the renderer over IPC. */
|
|
2
|
-
interface ElectronAppInfo {
|
|
3
|
-
/** `app.getName()`. */
|
|
4
|
-
appName: string;
|
|
5
|
-
/** `app.getVersion()` (from package.json). */
|
|
6
|
-
appVersion: string;
|
|
7
|
-
/** `process.platform`: `darwin`, `win32`, `linux`. */
|
|
8
|
-
platform: string;
|
|
9
|
-
/** `process.arch`: `x64`, `arm64`, … */
|
|
10
|
-
arch: string;
|
|
11
|
-
/** `os.release()` / `process.getSystemVersion()`, when available. */
|
|
12
|
-
osVersion?: string;
|
|
13
|
-
electronVersion?: string;
|
|
14
|
-
chromeVersion?: string;
|
|
15
|
-
/** `app.getLocale()`. */
|
|
16
|
-
locale?: string;
|
|
17
|
-
/** `app.isPackaged`: false while running from source (`electron .`). */
|
|
18
|
-
packaged?: boolean;
|
|
19
|
-
}
|
|
20
|
-
/** The API the preload script exposes on `window.guidelyElectron`. */
|
|
21
|
-
interface GuidelyElectronBridge {
|
|
22
|
-
getAppInfo(): Promise<ElectronAppInfo>;
|
|
23
|
-
}
|
|
24
|
-
/** IPC channel used between the main-process handler and the preload bridge. */
|
|
25
|
-
declare const APP_INFO_CHANNEL = "guidely:app-info";
|
|
26
|
-
/** Global the preload bridge is exposed as (`contextBridge.exposeInMainWorld`). */
|
|
27
|
-
declare const BRIDGE_KEY = "guidelyElectron";
|
|
28
|
-
/** Desktop operating system names used in Guidely device properties. */
|
|
29
|
-
declare function osName(platform: string): string;
|
|
30
|
-
|
|
31
|
-
export { APP_INFO_CHANNEL as A, BRIDGE_KEY as B, type ElectronAppInfo as E, type GuidelyElectronBridge as G, osName as o };
|