Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
98 lines
3.9 KiB
JavaScript
98 lines
3.9 KiB
JavaScript
"use strict";
|
|
|
|
const MAX_VISIBLE_RESOURCES = 20;
|
|
const MAX_VISIBLE_FIELD_LENGTH = 1_024;
|
|
const UNSAFE_DISPLAY_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/gu;
|
|
|
|
function escapePermissionText(value, maxLength = MAX_VISIBLE_FIELD_LENGTH) {
|
|
const text = String(value).slice(0, maxLength);
|
|
return text.replace(UNSAFE_DISPLAY_CHARACTERS, (character) => {
|
|
if (character === "\n") return "\\n";
|
|
if (character === "\r") return "\\r";
|
|
if (character === "\t") return "\\t";
|
|
return `\\u${character.codePointAt(0).toString(16).padStart(4, "0")}`;
|
|
});
|
|
}
|
|
|
|
function describePermissionRequest(request) {
|
|
const identity = request.pluginName
|
|
? `${escapePermissionText(request.pluginName)} (${escapePermissionText(request.pluginId)})`
|
|
: escapePermissionText(request.pluginId);
|
|
const resources = request.resources ?? [];
|
|
const resourceKinds = request.resourceKinds ?? [];
|
|
const visibleResources = resources.slice(0, MAX_VISIBLE_RESOURCES).map((resource, index) => {
|
|
const suffix = resourceKinds[index] === "directory" ? " (directory and descendants)" : "";
|
|
return `- ${escapePermissionText(resource)}${suffix}`;
|
|
});
|
|
if (resources.length > visibleResources.length) {
|
|
visibleResources.push(`- …and ${resources.length - visibleResources.length} more`);
|
|
}
|
|
return [
|
|
`Plugin: ${identity}`,
|
|
...(request.publisher ? [`Publisher: ${escapePermissionText(request.publisher)}`] : []),
|
|
`Permission: ${escapePermissionText(request.permission)}`,
|
|
`Reason: ${escapePermissionText(request.reason)}`,
|
|
...(visibleResources.length ? ["Resources:", ...visibleResources] : []),
|
|
].join("\n");
|
|
}
|
|
|
|
function createNativePermissionDecisionProvider(options) {
|
|
if (!options?.dialog || typeof options.dialog.showMessageBox !== "function") {
|
|
throw new TypeError("Native plugin permission decisions require an Electron dialog");
|
|
}
|
|
const dialog = options.dialog;
|
|
const window = options.window;
|
|
return async (request, context = {}) => {
|
|
context.signal?.throwIfAborted();
|
|
const allowedScopes = Array.isArray(request.allowedScopes)
|
|
? new Set(request.allowedScopes)
|
|
: null;
|
|
const supportsScope = (scope) => allowedScopes == null || allowedScopes.has(scope);
|
|
const choices = [
|
|
{ label: "Deny", decision: "deny" },
|
|
...(supportsScope("once") ? [{ label: "Allow Once", decision: "allow", scope: "once" }] : []),
|
|
...(request.sessionId && supportsScope("session")
|
|
? [{ label: "Allow for Session", decision: "allow", scope: "session" }]
|
|
: []),
|
|
...(supportsScope("application")
|
|
? [{ label: "Allow for Application", decision: "allow", scope: "application" }]
|
|
: []),
|
|
...(supportsScope("always")
|
|
? [{ label: "Always Allow", decision: "allow", scope: "always" }]
|
|
: []),
|
|
];
|
|
const messageBoxOptions = {
|
|
type: "warning",
|
|
title: "Plugin permission request",
|
|
message: `${escapePermissionText(request.pluginName ?? request.pluginId)} requests ${escapePermissionText(request.permission)}`,
|
|
detail: describePermissionRequest(request),
|
|
buttons: choices.map(({ label }) => label),
|
|
defaultId: 0,
|
|
cancelId: 0,
|
|
noLink: true,
|
|
...(context.signal ? { signal: context.signal } : {}),
|
|
};
|
|
const result = window
|
|
? await dialog.showMessageBox(window, messageBoxOptions)
|
|
: await dialog.showMessageBox(messageBoxOptions);
|
|
context.signal?.throwIfAborted();
|
|
const choice = choices[result.response] ?? choices[0];
|
|
if (choice.decision !== "allow") {
|
|
return Object.freeze({ requestId: request.requestId, decision: "deny" });
|
|
}
|
|
return Object.freeze({
|
|
requestId: request.requestId,
|
|
decision: "allow",
|
|
scope: choice.scope,
|
|
});
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
MAX_VISIBLE_FIELD_LENGTH,
|
|
MAX_VISIBLE_RESOURCES,
|
|
createNativePermissionDecisionProvider,
|
|
describePermissionRequest,
|
|
escapePermissionText,
|
|
};
|