@wildwinter/app-shell 0.57.0 → 0.58.1
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 +195 -0
- package/dist/chunk-4JQWNC6Z.js +319 -0
- package/dist/chunk-4JQWNC6Z.js.map +1 -0
- package/dist/chunk-ICYPXUNA.js +8 -0
- package/dist/chunk-ICYPXUNA.js.map +1 -0
- package/dist/{chunk-XJM733E6.js → chunk-OO6O4LEL.js} +6 -2
- package/dist/chunk-OO6O4LEL.js.map +1 -0
- package/dist/{chunk-CPNTROWD.js → chunk-OZQ2BYXH.js} +13 -4
- package/dist/chunk-OZQ2BYXH.js.map +1 -0
- package/dist/{chunk-5BKGDTYD.js → chunk-RMQNBWLZ.js} +3 -3
- package/dist/{chunk-5BKGDTYD.js.map → chunk-RMQNBWLZ.js.map} +1 -1
- package/dist/index.cjs +334 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/property-list.cjs.map +1 -1
- package/dist/property-list.js +2 -2
- package/dist/tool-window.cjs +11 -2
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.d.cts +10 -4
- package/dist/tool-window.d.ts +10 -4
- package/dist/tool-window.js +1 -1
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.js +1 -1
- package/dist/value-control.cjs +4 -0
- package/dist/value-control.cjs.map +1 -1
- package/dist/value-control.d.cts +4 -4
- package/dist/value-control.d.ts +4 -4
- package/dist/value-control.js +1 -1
- package/dist/value-rows.cjs +8 -1
- package/dist/value-rows.cjs.map +1 -1
- package/dist/value-rows.js +5 -2
- package/dist/value-rows.js.map +1 -1
- package/dist/view-client.cjs +172 -0
- package/dist/view-client.cjs.map +1 -0
- package/dist/view-client.d.cts +46 -0
- package/dist/view-client.d.ts +46 -0
- package/dist/view-client.js +165 -0
- package/dist/view-client.js.map +1 -0
- package/dist/view-host-web.cjs +327 -0
- package/dist/view-host-web.cjs.map +1 -0
- package/dist/view-host-web.d.cts +61 -0
- package/dist/view-host-web.d.ts +61 -0
- package/dist/view-host-web.js +4 -0
- package/dist/view-host-web.js.map +1 -0
- package/dist/view-host.cjs +532 -0
- package/dist/view-host.cjs.map +1 -0
- package/dist/view-host.d.cts +178 -0
- package/dist/view-host.d.ts +178 -0
- package/dist/view-host.js +426 -0
- package/dist/view-host.js.map +1 -0
- package/dist/view-preload.cjs +16 -0
- package/dist/view-preload.cjs.map +1 -0
- package/dist/view-preload.js +8 -0
- package/dist/view-preload.js.map +1 -0
- package/dist/view-protocol.cjs +12 -0
- package/dist/view-protocol.cjs.map +1 -0
- package/dist/view-protocol.d.cts +257 -0
- package/dist/view-protocol.d.ts +257 -0
- package/dist/view-protocol.js +3 -0
- package/dist/view-protocol.js.map +1 -0
- package/package.json +25 -4
- package/dist/chunk-CPNTROWD.js.map +0 -1
- package/dist/chunk-XJM733E6.js.map +0 -1
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var electron = require('electron');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var url = require('url');
|
|
6
|
+
var extensions = require('@wildwinter/toolkit/extensions');
|
|
7
|
+
var promises = require('fs/promises');
|
|
8
|
+
var zlib = require('zlib');
|
|
9
|
+
|
|
10
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
|
+
// src/view-host.ts
|
|
12
|
+
function rememberBounds(w, write) {
|
|
13
|
+
const saveBounds = () => {
|
|
14
|
+
if (!w.isDestroyed()) write(w.getBounds());
|
|
15
|
+
};
|
|
16
|
+
let boundsTimer;
|
|
17
|
+
const queueSave = () => {
|
|
18
|
+
clearTimeout(boundsTimer);
|
|
19
|
+
boundsTimer = setTimeout(saveBounds, 400);
|
|
20
|
+
};
|
|
21
|
+
w.on("resize", queueSave);
|
|
22
|
+
w.on("move", queueSave);
|
|
23
|
+
w.on("close", () => {
|
|
24
|
+
clearTimeout(boundsTimer);
|
|
25
|
+
saveBounds();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
var macPinned = /* @__PURE__ */ new Set();
|
|
29
|
+
var macActivationWired = false;
|
|
30
|
+
function wireMacActivation() {
|
|
31
|
+
if (macActivationWired) return;
|
|
32
|
+
macActivationWired = true;
|
|
33
|
+
electron.app.on("did-become-active", () => {
|
|
34
|
+
for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(true, "floating");
|
|
35
|
+
});
|
|
36
|
+
electron.app.on("did-resign-active", () => {
|
|
37
|
+
for (const w of macPinned) if (!w.isDestroyed()) w.setAlwaysOnTop(false);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function pinToolWindow(w, parent, on, platform = process.platform) {
|
|
41
|
+
if (!w || w.isDestroyed()) return;
|
|
42
|
+
if (platform === "darwin") {
|
|
43
|
+
w.setParentWindow(null);
|
|
44
|
+
if (on) {
|
|
45
|
+
if (!macPinned.has(w)) {
|
|
46
|
+
macPinned.add(w);
|
|
47
|
+
w.once("closed", () => macPinned.delete(w));
|
|
48
|
+
}
|
|
49
|
+
wireMacActivation();
|
|
50
|
+
w.setAlwaysOnTop(electron.BrowserWindow.getFocusedWindow() !== null, "floating");
|
|
51
|
+
} else {
|
|
52
|
+
macPinned.delete(w);
|
|
53
|
+
w.setAlwaysOnTop(false);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
w.setAlwaysOnTop(false);
|
|
58
|
+
const owner = on && parent && !parent.isDestroyed() ? parent : null;
|
|
59
|
+
w.setParentWindow(owner);
|
|
60
|
+
}
|
|
61
|
+
function openToolWindow(existing, opts) {
|
|
62
|
+
if (opts.url === void 0 && (opts.page === void 0 || opts.rendererDir === void 0)) {
|
|
63
|
+
throw new Error("openToolWindow needs a url, or a page and its rendererDir");
|
|
64
|
+
}
|
|
65
|
+
const w = new electron.BrowserWindow({
|
|
66
|
+
...opts.rect,
|
|
67
|
+
...opts.min !== void 0 ? { minWidth: opts.min.width, minHeight: opts.min.height } : {},
|
|
68
|
+
show: false,
|
|
69
|
+
title: opts.title,
|
|
70
|
+
...opts.frame !== void 0 ? { frame: opts.frame } : {},
|
|
71
|
+
webPreferences: {
|
|
72
|
+
contextIsolation: true,
|
|
73
|
+
nodeIntegration: false,
|
|
74
|
+
sandbox: true,
|
|
75
|
+
// Electron's defaults, said out loud: no webview tag, and a dropped file never navigates the window.
|
|
76
|
+
webviewTag: false,
|
|
77
|
+
navigateOnDragDrop: false,
|
|
78
|
+
preload: opts.preload,
|
|
79
|
+
...opts.partition !== void 0 ? { partition: opts.partition } : {},
|
|
80
|
+
...opts.devTools !== void 0 ? { devTools: opts.devTools } : {}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
if (opts.pinTo !== void 0) pinToolWindow(w, opts.pinTo(), opts.pinned ?? false);
|
|
84
|
+
if (opts.remember) rememberBounds(w, opts.remember);
|
|
85
|
+
w.once("ready-to-show", () => w.show());
|
|
86
|
+
if (opts.onClosed) w.on("closed", opts.onClosed);
|
|
87
|
+
if (opts.url !== void 0) void w.loadURL(opts.url);
|
|
88
|
+
else if (process.env["ELECTRON_RENDERER_URL"]) void w.loadURL(`${process.env["ELECTRON_RENDERER_URL"]}/${opts.page}`);
|
|
89
|
+
else void w.loadFile(path.join(opts.rendererDir, opts.page));
|
|
90
|
+
return w;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/view-protocol.ts
|
|
94
|
+
var VIEW_PORT_CHANNEL = "wildwinter-view:port";
|
|
95
|
+
var VIEW_CSP = [
|
|
96
|
+
"default-src 'self'",
|
|
97
|
+
"script-src 'self'",
|
|
98
|
+
"style-src 'self' 'unsafe-inline'",
|
|
99
|
+
"img-src 'self' data: blob:",
|
|
100
|
+
"font-src 'self' data:",
|
|
101
|
+
"connect-src 'none'",
|
|
102
|
+
"object-src 'none'",
|
|
103
|
+
"base-uri 'none'",
|
|
104
|
+
"form-action 'none'",
|
|
105
|
+
"frame-src 'none'"
|
|
106
|
+
].join("; ");
|
|
107
|
+
function viewSchemePrivileges(scheme) {
|
|
108
|
+
return { scheme, privileges: { standard: true, secure: true, supportFetchAPI: true } };
|
|
109
|
+
}
|
|
110
|
+
function viewPageUrl(scheme, extension, view) {
|
|
111
|
+
return `${scheme}://${extension}/?view=${encodeURIComponent(view)}`;
|
|
112
|
+
}
|
|
113
|
+
var TYPES = {
|
|
114
|
+
".js": "text/javascript; charset=utf-8",
|
|
115
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
116
|
+
".css": "text/css; charset=utf-8",
|
|
117
|
+
".html": "text/html; charset=utf-8",
|
|
118
|
+
".json": "application/json; charset=utf-8",
|
|
119
|
+
".map": "application/json; charset=utf-8",
|
|
120
|
+
".txt": "text/plain; charset=utf-8",
|
|
121
|
+
".svg": "image/svg+xml",
|
|
122
|
+
".png": "image/png",
|
|
123
|
+
".jpg": "image/jpeg",
|
|
124
|
+
".jpeg": "image/jpeg",
|
|
125
|
+
".gif": "image/gif",
|
|
126
|
+
".webp": "image/webp",
|
|
127
|
+
".avif": "image/avif",
|
|
128
|
+
".ico": "image/x-icon",
|
|
129
|
+
".woff": "font/woff",
|
|
130
|
+
".woff2": "font/woff2",
|
|
131
|
+
".ttf": "font/ttf",
|
|
132
|
+
".otf": "font/otf",
|
|
133
|
+
".wasm": "application/wasm"
|
|
134
|
+
};
|
|
135
|
+
function viewContentType(path$1) {
|
|
136
|
+
return TYPES[path.extname(path$1).toLowerCase()] ?? "application/octet-stream";
|
|
137
|
+
}
|
|
138
|
+
var headers = (type) => ({
|
|
139
|
+
"Content-Type": type,
|
|
140
|
+
"Content-Security-Policy": VIEW_CSP,
|
|
141
|
+
"X-Content-Type-Options": "nosniff",
|
|
142
|
+
"Cache-Control": "no-store"
|
|
143
|
+
});
|
|
144
|
+
var notFound = () => new Response("Not found", { status: 404, headers: headers("text/plain; charset=utf-8") });
|
|
145
|
+
var escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
146
|
+
function viewPage(view) {
|
|
147
|
+
const el = view.element;
|
|
148
|
+
return `<!doctype html>
|
|
149
|
+
<html><head><meta charset="utf-8"><title>${escapeHtml(view.name)}</title>
|
|
150
|
+
<style>
|
|
151
|
+
html, body { margin: 0; height: 100%; }
|
|
152
|
+
body { background: var(--bg, Canvas); color: var(--ink, CanvasText); font-family: var(--font-ui, system-ui, sans-serif); color-scheme: var(--color-scheme, light dark); }
|
|
153
|
+
body > ${el} { display: block; height: 100%; overflow: auto; }
|
|
154
|
+
</style>
|
|
155
|
+
<script type="module" src="/${view.entry.split("/").map(encodeURIComponent).join("/")}"></script>
|
|
156
|
+
</head><body><${el}></${el}></body></html>
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
async function viewResponse(url, opts) {
|
|
160
|
+
let u;
|
|
161
|
+
try {
|
|
162
|
+
u = new URL(url);
|
|
163
|
+
} catch {
|
|
164
|
+
return notFound();
|
|
165
|
+
}
|
|
166
|
+
if (u.protocol !== `${opts.scheme}:`) return notFound();
|
|
167
|
+
const source = await opts.resolve(u.hostname);
|
|
168
|
+
if (!source) return notFound();
|
|
169
|
+
if (u.pathname === "/" || u.pathname === "") {
|
|
170
|
+
const id = u.searchParams.get("view");
|
|
171
|
+
const view = id === null ? void 0 : extensions.viewsFor(source.manifest, opts.host).find((v) => v.id === id);
|
|
172
|
+
if (!view) return notFound();
|
|
173
|
+
return new Response(viewPage(view), { status: 200, headers: headers("text/html; charset=utf-8") });
|
|
174
|
+
}
|
|
175
|
+
const file = await confined(source.root, u.pathname);
|
|
176
|
+
if (!file) return notFound();
|
|
177
|
+
try {
|
|
178
|
+
return new Response(new Uint8Array(await promises.readFile(file)), { status: 200, headers: headers(viewContentType(file)) });
|
|
179
|
+
} catch {
|
|
180
|
+
return notFound();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function confined(root, pathname) {
|
|
184
|
+
let path$1;
|
|
185
|
+
try {
|
|
186
|
+
path$1 = decodeURIComponent(pathname);
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (path$1.includes("\0") || path$1.includes("\\")) return null;
|
|
191
|
+
const parts = path$1.replace(/^\/+/, "").split("/");
|
|
192
|
+
if (parts.some((p) => p === "" || p === "." || p === "..")) return null;
|
|
193
|
+
const full = path.join(root, ...parts);
|
|
194
|
+
const inside = (base, p) => {
|
|
195
|
+
const rel = path.relative(base, p);
|
|
196
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
197
|
+
};
|
|
198
|
+
if (!inside(root, full)) return null;
|
|
199
|
+
try {
|
|
200
|
+
const [realRoot, realFull] = await Promise.all([promises.realpath(root), promises.realpath(full)]);
|
|
201
|
+
return inside(realRoot, realFull) ? realFull : null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function serveViews(protocol, opts) {
|
|
207
|
+
protocol.handle(opts.scheme, (request) => viewResponse(request.url, opts));
|
|
208
|
+
}
|
|
209
|
+
function viewRequestAllowed(url, scheme) {
|
|
210
|
+
const colon = url.indexOf(":");
|
|
211
|
+
const protocol = colon < 0 ? "" : url.slice(0, colon).toLowerCase();
|
|
212
|
+
return protocol === scheme || protocol === "devtools" || protocol === "data" || protocol === "blob";
|
|
213
|
+
}
|
|
214
|
+
function guardViewSession(session2, scheme) {
|
|
215
|
+
session2.webRequest.onBeforeRequest((details, callback) => callback({ cancel: !viewRequestAllowed(details.url, scheme) }));
|
|
216
|
+
session2.setPermissionRequestHandler((_wc, _permission, callback) => callback(false));
|
|
217
|
+
session2.setPermissionCheckHandler?.(() => false);
|
|
218
|
+
}
|
|
219
|
+
var ID = /^[a-z][a-z0-9-]*$/;
|
|
220
|
+
var VERSION = /^\d+\.\d+\.\d+$/;
|
|
221
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
222
|
+
var kept = (path) => path.split("/").every((s) => s !== "node_modules" && !s.startsWith("."));
|
|
223
|
+
var safePath = (path) => path !== "" && !path.includes("\\") && !path.includes("\0") && !path.startsWith("/") && path.split("/").every((s) => s !== "" && s !== "." && s !== "..");
|
|
224
|
+
async function readExtensionPackage(path$1) {
|
|
225
|
+
let stat;
|
|
226
|
+
try {
|
|
227
|
+
stat = await promises.lstat(path$1);
|
|
228
|
+
} catch {
|
|
229
|
+
return { ok: false, why: `There's nothing at ${path$1}.` };
|
|
230
|
+
}
|
|
231
|
+
let files;
|
|
232
|
+
if (stat.isDirectory()) files = await readFolder(path$1);
|
|
233
|
+
else if (stat.isFile()) files = readTarball(await promises.readFile(path$1));
|
|
234
|
+
else return { ok: false, why: `${path.basename(path$1)} isn't a folder or an npm package tarball (.tgz).` };
|
|
235
|
+
if (typeof files === "string") return { ok: false, why: files };
|
|
236
|
+
return packageFromFiles(files);
|
|
237
|
+
}
|
|
238
|
+
function packageFromFiles(files) {
|
|
239
|
+
const manifests = [...files.keys()].filter((p) => !p.includes("/") && p.endsWith(extensions.MANIFEST_FILE_SUFFIX));
|
|
240
|
+
if (manifests.length === 0) {
|
|
241
|
+
return { ok: false, why: `This package has no extension manifest (a file such as pattershot${extensions.MANIFEST_FILE_SUFFIX}) at its top level.` };
|
|
242
|
+
}
|
|
243
|
+
if (manifests.length > 1) {
|
|
244
|
+
return { ok: false, why: `This package has more than one extension manifest at its top level: ${manifests.sort().join(", ")}.` };
|
|
245
|
+
}
|
|
246
|
+
const file = manifests[0];
|
|
247
|
+
let manifest;
|
|
248
|
+
try {
|
|
249
|
+
manifest = extensions.parseManifest(new TextDecoder().decode(files.get(file)));
|
|
250
|
+
} catch (e) {
|
|
251
|
+
return { ok: false, why: `The manifest ${file} won't parse: ${e.message}` };
|
|
252
|
+
}
|
|
253
|
+
const problems = extensions.manifestProblems(manifest);
|
|
254
|
+
const stem = file.slice(0, -extensions.MANIFEST_FILE_SUFFIX.length);
|
|
255
|
+
if (typeof manifest === "object" && manifest !== null && typeof manifest.id === "string" && manifest.id !== stem) {
|
|
256
|
+
return { ok: false, why: `The manifest ${file} names the extension "${manifest.id}"; its file name should match.` };
|
|
257
|
+
}
|
|
258
|
+
return { ok: true, manifest, problems, files };
|
|
259
|
+
}
|
|
260
|
+
async function readFolder(root) {
|
|
261
|
+
const files = /* @__PURE__ */ new Map();
|
|
262
|
+
let total = 0;
|
|
263
|
+
const walk = async (dir, rel) => {
|
|
264
|
+
for (const entry of await promises.readdir(dir, { withFileTypes: true })) {
|
|
265
|
+
const path$1 = rel === "" ? entry.name : `${rel}/${entry.name}`;
|
|
266
|
+
if (!kept(path$1)) continue;
|
|
267
|
+
if (entry.isDirectory()) {
|
|
268
|
+
const why2 = await walk(path.join(dir, entry.name), path$1);
|
|
269
|
+
if (why2) return why2;
|
|
270
|
+
} else if (entry.isFile()) {
|
|
271
|
+
const bytes = await promises.readFile(path.join(dir, entry.name));
|
|
272
|
+
total += bytes.length;
|
|
273
|
+
if (total > MAX_BYTES) return tooBig();
|
|
274
|
+
files.set(path$1, bytes);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
};
|
|
279
|
+
const why = await walk(root, "");
|
|
280
|
+
return why ?? files;
|
|
281
|
+
}
|
|
282
|
+
var tooBig = () => `This package is bigger than ${MAX_BYTES / 1024 / 1024} MB, which is more than a view should need.`;
|
|
283
|
+
var notATarball = "This file isn't a folder or an npm package tarball (.tgz).";
|
|
284
|
+
function readTarball(gz) {
|
|
285
|
+
let tar;
|
|
286
|
+
try {
|
|
287
|
+
tar = zlib.gunzipSync(gz, { maxOutputLength: MAX_BYTES * 2 });
|
|
288
|
+
} catch {
|
|
289
|
+
return notATarball;
|
|
290
|
+
}
|
|
291
|
+
const raw = [];
|
|
292
|
+
let at = 0;
|
|
293
|
+
let longName = null;
|
|
294
|
+
let total = 0;
|
|
295
|
+
const text = (from, len) => {
|
|
296
|
+
const slice = tar.subarray(from, from + len);
|
|
297
|
+
const end = slice.indexOf(0);
|
|
298
|
+
return slice.subarray(0, end < 0 ? slice.length : end).toString("utf8");
|
|
299
|
+
};
|
|
300
|
+
while (at + 512 <= tar.length) {
|
|
301
|
+
const header = tar.subarray(at, at + 512);
|
|
302
|
+
if (header.every((b) => b === 0)) break;
|
|
303
|
+
if (text(at + 257, 5) !== "ustar") return notATarball;
|
|
304
|
+
const size = parseInt(text(at + 124, 12).trim() || "0", 8);
|
|
305
|
+
if (!Number.isFinite(size) || size < 0) return notATarball;
|
|
306
|
+
const type = String.fromCharCode(header[156]);
|
|
307
|
+
const prefix = text(at + 345, 155);
|
|
308
|
+
const name = prefix ? `${prefix}/${text(at, 100)}` : text(at, 100);
|
|
309
|
+
const body = tar.subarray(at + 512, at + 512 + size);
|
|
310
|
+
if (body.length < size) return notATarball;
|
|
311
|
+
at += 512 + Math.ceil(size / 512) * 512;
|
|
312
|
+
if (type === "x") {
|
|
313
|
+
longName = paxPath(body.toString("utf8")) ?? longName;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (type === "L") {
|
|
317
|
+
longName = body.toString("utf8").replace(/\0+$/, "");
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (type === "g") continue;
|
|
321
|
+
const path = (longName ?? name).replace(/^\.\//, "");
|
|
322
|
+
longName = null;
|
|
323
|
+
if (type !== "0" && type !== "\0") continue;
|
|
324
|
+
if (!safePath(path)) return "This package holds a file that would land outside it, so it won't be read.";
|
|
325
|
+
total += size;
|
|
326
|
+
if (total > MAX_BYTES) return tooBig();
|
|
327
|
+
raw.push([path, new Uint8Array(body)]);
|
|
328
|
+
}
|
|
329
|
+
const tops = new Set(raw.map(([p]) => p.split("/")[0]));
|
|
330
|
+
const strip = tops.size === 1 && raw.every(([p]) => p.includes("/"));
|
|
331
|
+
const files = /* @__PURE__ */ new Map();
|
|
332
|
+
for (const [p, bytes] of raw) {
|
|
333
|
+
const path = strip ? p.slice(p.indexOf("/") + 1) : p;
|
|
334
|
+
if (kept(path)) files.set(path, bytes);
|
|
335
|
+
}
|
|
336
|
+
return files;
|
|
337
|
+
}
|
|
338
|
+
function paxPath(records) {
|
|
339
|
+
for (const line of records.split("\n")) {
|
|
340
|
+
const m = /^\d+ path=(.*)$/.exec(line);
|
|
341
|
+
if (m) return m[1];
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
async function installExtensionPackage(pkg, dir) {
|
|
346
|
+
const { manifest } = pkg;
|
|
347
|
+
const name = typeof manifest.name === "string" ? manifest.name : "This extension";
|
|
348
|
+
const errors = pkg.problems.filter((p) => p.severity === "error");
|
|
349
|
+
if (errors.length > 0 || !ID.test(manifest.id) || !VERSION.test(manifest.version)) {
|
|
350
|
+
const first = errors[0];
|
|
351
|
+
return { ok: false, why: `${name}'s manifest has a fault, so it won't install${first ? `: ${first.message}` : ""}.` };
|
|
352
|
+
}
|
|
353
|
+
const home = path.join(dir, manifest.id);
|
|
354
|
+
const target = path.join(home, manifest.version);
|
|
355
|
+
const stamp = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
356
|
+
const temp = path.join(home, `.installing-${manifest.version}-${stamp}`);
|
|
357
|
+
const old = path.join(home, `.replaced-${manifest.version}-${stamp}`);
|
|
358
|
+
try {
|
|
359
|
+
await promises.mkdir(temp, { recursive: true });
|
|
360
|
+
for (const [path$1, bytes] of pkg.files) {
|
|
361
|
+
if (!safePath(path$1) || !kept(path$1)) continue;
|
|
362
|
+
const to = path.join(temp, ...path$1.split("/"));
|
|
363
|
+
await promises.mkdir(path.dirname(to), { recursive: true });
|
|
364
|
+
await promises.writeFile(to, bytes);
|
|
365
|
+
}
|
|
366
|
+
let replaced = false;
|
|
367
|
+
try {
|
|
368
|
+
await promises.rename(target, old);
|
|
369
|
+
replaced = true;
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
await promises.rename(temp, target);
|
|
374
|
+
} catch (e) {
|
|
375
|
+
if (replaced) await promises.rename(old, target).catch(() => {
|
|
376
|
+
});
|
|
377
|
+
throw e;
|
|
378
|
+
}
|
|
379
|
+
if (replaced) await promises.rm(old, { recursive: true, force: true });
|
|
380
|
+
} catch (e) {
|
|
381
|
+
await promises.rm(temp, { recursive: true, force: true }).catch(() => {
|
|
382
|
+
});
|
|
383
|
+
return { ok: false, why: `${name} couldn't be installed: ${e.message}` };
|
|
384
|
+
}
|
|
385
|
+
return { ok: true, installed: { id: manifest.id, version: manifest.version, manifest, root: target } };
|
|
386
|
+
}
|
|
387
|
+
async function removeInstalledExtension(dir, id, version) {
|
|
388
|
+
if (!ID.test(id) || version !== void 0 && !VERSION.test(version)) return;
|
|
389
|
+
await promises.rm(version === void 0 ? path.join(dir, id) : path.join(dir, id, version), { recursive: true, force: true });
|
|
390
|
+
}
|
|
391
|
+
async function installedExtensions(dir) {
|
|
392
|
+
const out = [];
|
|
393
|
+
const names = async (path) => {
|
|
394
|
+
try {
|
|
395
|
+
return (await promises.readdir(path, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
396
|
+
} catch {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
for (const id of (await names(dir)).filter((n) => ID.test(n)).sort()) {
|
|
401
|
+
for (const version of (await names(path.join(dir, id))).filter((n) => VERSION.test(n))) {
|
|
402
|
+
const root = path.join(dir, id, version);
|
|
403
|
+
try {
|
|
404
|
+
const manifest = extensions.parseManifest(await promises.readFile(path.join(root, `${id}${extensions.MANIFEST_FILE_SUFFIX}`), "utf8"));
|
|
405
|
+
if (manifest.id !== id || manifest.version !== version) continue;
|
|
406
|
+
out.push({ id, version, manifest, root });
|
|
407
|
+
} catch {
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return out.sort((a, b) => a.id === b.id ? compareVersions(a.version, b.version) : a.id < b.id ? -1 : 1);
|
|
412
|
+
}
|
|
413
|
+
function compareVersions(a, b) {
|
|
414
|
+
const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
|
|
415
|
+
for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
418
|
+
function chooseInstalled(installed, id, pinned, name) {
|
|
419
|
+
const mine = installed.filter((x) => x.id === id).sort((a, b) => compareVersions(b.version, a.version));
|
|
420
|
+
const called = name ?? mine[0]?.manifest.name ?? id;
|
|
421
|
+
if (mine.length === 0) return { installed: null, why: `${called} isn't installed on this machine.` };
|
|
422
|
+
if (pinned === void 0) return { installed: mine[0] };
|
|
423
|
+
const exact = mine.find((x) => x.version === pinned);
|
|
424
|
+
if (exact) return { installed: exact };
|
|
425
|
+
const major = pinned.split(".")[0];
|
|
426
|
+
const same = mine.find((x) => x.version.split(".")[0] === major);
|
|
427
|
+
if (same) return { installed: same, warning: `This project expects ${called} ${pinned}; ${same.version} is installed.` };
|
|
428
|
+
return {
|
|
429
|
+
installed: null,
|
|
430
|
+
why: `This project expects ${called} ${pinned}; ${mine[0].version} is installed, and a different major version won't open.`
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async function resolveInstalled(dir, id, pinned, name) {
|
|
434
|
+
return chooseInstalled(await installedExtensions(dir), id, pinned, name);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/view-host.ts
|
|
438
|
+
function viewPreloadPath() {
|
|
439
|
+
const here = typeof __dirname === "string" ? __dirname : path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('view-host.cjs', document.baseURI).href))));
|
|
440
|
+
return path.join(here, "view-preload.cjs");
|
|
441
|
+
}
|
|
442
|
+
var viewPartition = (extension) => `view-${extension}`;
|
|
443
|
+
var served = /* @__PURE__ */ new Map();
|
|
444
|
+
function prepareViewSession(extension, source, scheme, host) {
|
|
445
|
+
const partition = viewPartition(extension);
|
|
446
|
+
const known = served.has(partition);
|
|
447
|
+
served.set(partition, source);
|
|
448
|
+
if (known) return;
|
|
449
|
+
const ses = electron.session.fromPartition(partition);
|
|
450
|
+
serveViews(ses.protocol, { scheme, host, resolve: (id) => id === extension ? served.get(partition) ?? null : null });
|
|
451
|
+
guardViewSession(ses, scheme);
|
|
452
|
+
}
|
|
453
|
+
function openViewWindow(opts) {
|
|
454
|
+
const { installed, editor } = opts;
|
|
455
|
+
const decl = extensions.viewsFor(installed.manifest, opts.host).find((v) => v.id === opts.view);
|
|
456
|
+
if (!decl) throw new Error(`${installed.manifest.name} has no view called "${opts.view}" in this app.`);
|
|
457
|
+
prepareViewSession(installed.id, { root: installed.root, manifest: installed.manifest }, opts.scheme, opts.host);
|
|
458
|
+
const w = openToolWindow(null, {
|
|
459
|
+
title: opts.title ?? decl.name,
|
|
460
|
+
url: viewPageUrl(opts.scheme, installed.id, decl.id),
|
|
461
|
+
preload: opts.preload ?? viewPreloadPath(),
|
|
462
|
+
partition: viewPartition(installed.id),
|
|
463
|
+
devTools: opts.devTools ?? false,
|
|
464
|
+
rect: opts.rect,
|
|
465
|
+
frame: opts.frame ?? true,
|
|
466
|
+
...opts.min !== void 0 ? { min: opts.min } : {},
|
|
467
|
+
...opts.pinTo !== void 0 ? { pinTo: opts.pinTo } : {},
|
|
468
|
+
...opts.pinned !== void 0 ? { pinned: opts.pinned } : {},
|
|
469
|
+
...opts.remember !== void 0 ? { remember: opts.remember } : {},
|
|
470
|
+
...opts.onClosed !== void 0 ? { onClosed: opts.onClosed } : {}
|
|
471
|
+
});
|
|
472
|
+
const meta = {
|
|
473
|
+
window: w.id,
|
|
474
|
+
extension: installed.id,
|
|
475
|
+
view: decl.id,
|
|
476
|
+
version: installed.version,
|
|
477
|
+
...opts.pinnedVersion !== void 0 ? { pinned: opts.pinnedVersion } : {}
|
|
478
|
+
};
|
|
479
|
+
let viewLoaded = false;
|
|
480
|
+
const handOver = () => {
|
|
481
|
+
if (w.isDestroyed() || editor.isDestroyed()) return;
|
|
482
|
+
const { port1, port2 } = new electron.MessageChannelMain();
|
|
483
|
+
w.webContents.postMessage(VIEW_PORT_CHANNEL, null, [port1]);
|
|
484
|
+
editor.webContents.postMessage(VIEW_PORT_CHANNEL, meta, [port2]);
|
|
485
|
+
};
|
|
486
|
+
const onViewLoad = () => {
|
|
487
|
+
viewLoaded = true;
|
|
488
|
+
handOver();
|
|
489
|
+
};
|
|
490
|
+
const onEditorLoad = () => {
|
|
491
|
+
if (viewLoaded) handOver();
|
|
492
|
+
};
|
|
493
|
+
const close = () => {
|
|
494
|
+
if (!w.isDestroyed()) w.close();
|
|
495
|
+
};
|
|
496
|
+
w.webContents.on("did-finish-load", onViewLoad);
|
|
497
|
+
editor.webContents.on("did-finish-load", onEditorLoad);
|
|
498
|
+
editor.once("closed", close);
|
|
499
|
+
w.once("closed", () => {
|
|
500
|
+
if (!editor.isDestroyed()) {
|
|
501
|
+
editor.webContents.postMessage(VIEW_PORT_CHANNEL, { ...meta, closed: true }, []);
|
|
502
|
+
editor.webContents.removeListener("did-finish-load", onEditorLoad);
|
|
503
|
+
editor.removeListener("closed", close);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
return { window: w, meta, close };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
exports.VIEW_CSP = VIEW_CSP;
|
|
510
|
+
exports.chooseInstalled = chooseInstalled;
|
|
511
|
+
exports.compareVersions = compareVersions;
|
|
512
|
+
exports.confined = confined;
|
|
513
|
+
exports.guardViewSession = guardViewSession;
|
|
514
|
+
exports.installExtensionPackage = installExtensionPackage;
|
|
515
|
+
exports.installedExtensions = installedExtensions;
|
|
516
|
+
exports.openViewWindow = openViewWindow;
|
|
517
|
+
exports.packageFromFiles = packageFromFiles;
|
|
518
|
+
exports.readExtensionPackage = readExtensionPackage;
|
|
519
|
+
exports.readTarball = readTarball;
|
|
520
|
+
exports.removeInstalledExtension = removeInstalledExtension;
|
|
521
|
+
exports.resolveInstalled = resolveInstalled;
|
|
522
|
+
exports.serveViews = serveViews;
|
|
523
|
+
exports.viewContentType = viewContentType;
|
|
524
|
+
exports.viewPage = viewPage;
|
|
525
|
+
exports.viewPageUrl = viewPageUrl;
|
|
526
|
+
exports.viewPartition = viewPartition;
|
|
527
|
+
exports.viewPreloadPath = viewPreloadPath;
|
|
528
|
+
exports.viewRequestAllowed = viewRequestAllowed;
|
|
529
|
+
exports.viewResponse = viewResponse;
|
|
530
|
+
exports.viewSchemePrivileges = viewSchemePrivileges;
|
|
531
|
+
//# sourceMappingURL=view-host.cjs.map
|
|
532
|
+
//# sourceMappingURL=view-host.cjs.map
|