Vydané zdroje
Stáhnout zdroje ZIP
CH-J Server Manager
Procházejte adresáře a soubory konkrétního vydání aplikace.
Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
1
"use strict";3
const test = require("node:test");4
const assert = require("node:assert/strict");5
const fs = require("node:fs");6
const os = require("node:os");7
const path = require("node:path");8
const { EventEmitter } = require("node:events");9
const { PluginRegistry, PLUGIN_API_VERSION, supportsPluginApi, validateManifest } = require("../src/main/plugins/pluginRegistry");10
const { PLUGIN_CSP, PluginRuntime } = require("../src/main/plugins/pluginRuntime");12
const pluginRoot = path.join(__dirname, "..", "src", "main", "firstPartyPlugins");13
const manifestPath = path.join(pluginRoot, "chj.hash-checksum", "0.0.1", "manifest.json");15
test("Hash & Checksum is a bundled first-party plugin on Plugin API 1.2", (t) => {16
const storage = fs.mkdtempSync(path.join(os.tmpdir(), "chj-hash-registry-")); t.after(() => fs.rmSync(storage, { recursive: true, force: true }));17
const manifest = validateManifest(JSON.parse(fs.readFileSync(manifestPath, "utf8")));18
assert.equal(PLUGIN_API_VERSION, "1.2.0"); assert.equal(manifest.pluginApi, "^1.2.0"); assert.deepEqual(manifest.permissions, ["local.hash"]);19
assert.equal(supportsPluginApi("^1.0.0"), true); assert.equal(supportsPluginApi("^1.1.0"), true); assert.equal(supportsPluginApi("^1.2.0"), true);20
const registry = new PluginRegistry(storage, null, { bundledRoot: pluginRoot }); const installed = registry.getInstalled("chj.hash-checksum");21
assert.equal(installed.bundled, true); assert.equal(registry.resolveEntry(installed.id).entryPath.endsWith(path.join("ui", "index.html")), true);22
});24
test("plugin UI is network-isolated and contains no Node, remote-session, or path-based access", () => {25
const preload = fs.readFileSync(path.join(__dirname, "..", "src", "preload", "pluginPreload.js"), "utf8");26
const ui = fs.readFileSync(path.join(pluginRoot, "chj.hash-checksum", "0.0.1", "ui", "index.js"), "utf8");27
assert.match(PLUGIN_CSP, /connect-src 'none'/); assert.match(preload, /plugin:hashing:selectFiles/); assert.doesNotMatch(preload, /require\(["']node:fs/);28
assert.doesNotMatch(ui, /\brequire\s*\(/); assert.doesNotMatch(ui, /\b(?:ssh|sftp|scp|remote\.exec)\b/i); assert.doesNotMatch(ui, /filePath|localPath|absolutePath/);29
});31
test("hash IPC enforces local.hash, scopes calls and progress to the owning plugin, and cleans up on close", async () => {32
const handlers = new Map(); const calls = []; const events = []; const hashing = new EventEmitter();33
hashing.getAlgorithms = () => [{ id: "sha256" }];34
hashing.start = (pluginId, payload) => { calls.push(["start", pluginId, payload.selectionId]); return { jobId: "job_owner" }; };35
hashing.status = (pluginId, jobId) => { calls.push(["status", pluginId, jobId]); if (pluginId !== "owner") throw Object.assign(new Error("Unknown job"), { code: "HASH_INVALID_JOB" }); return { jobId }; };36
hashing.cancel = async (pluginId, jobId) => { calls.push(["cancel", pluginId, jobId]); return { state: "cancelled" }; };37
hashing.selectFiles = async () => ({}); hashing.selectDirectory = async () => ({}); hashing.selectManifest = async () => ({}); hashing.selectManifestDestination = async () => ({});38
hashing.verify = () => ({}); hashing.compare = () => ({}); hashing.generateManifest = () => ({}); hashing.verifyManifest = () => ({}); hashing.exportResults = () => ({}); hashing.copyResult = () => ({});39
hashing.cleanupPlugin = (pluginId) => calls.push(["cleanup", pluginId]);40
const runtime = new PluginRuntime({ BrowserWindow: class {}, registry: {}, sessionManager: {}, localHashService: hashing, isVaultUnlocked: () => true });41
runtime.registerIpc({ handle: (channel, handler) => handlers.set(channel, handler) });42
const makeWindow = (id) => ({ isDestroyed: () => false, close: () => {}, webContents: { id, send: (channel, payload) => events.push([id, channel, payload.jobId]) } });43
const ownerWindow = makeWindow(10); const deniedWindow = makeWindow(11); const otherWindow = makeWindow(12);44
runtime.contexts.set(10, { manifest: { id: "owner", permissions: ["local.hash"] }, window: ownerWindow });45
runtime.contexts.set(11, { manifest: { id: "denied", permissions: [] }, window: deniedWindow });46
runtime.contexts.set(12, { manifest: { id: "other", permissions: ["local.hash"] }, window: otherWindow });47
runtime.windows.set("owner", ownerWindow); runtime.windows.set("other", otherWindow);48
assert.deepEqual(await handlers.get("plugin:hashing:start")({ sender: { id: 10 } }, { selectionId: "opaque" }), { jobId: "job_owner" });49
await assert.rejects(() => handlers.get("plugin:hashing:start")({ sender: { id: 11 } }, { selectionId: "opaque" }), { code: "PLUGIN_PERMISSION_DENIED" });50
await assert.rejects(() => handlers.get("plugin:hashing:status")({ sender: { id: 12 } }, { jobId: "job_owner" }), { code: "HASH_INVALID_JOB" });51
hashing.emit("progress", { pluginId: "owner", job: { jobId: "job_owner" } }); assert.deepEqual(events, [[10, "plugin:hashing:progress", "job_owner"]]);52
ownerWindow.webContents = { id: 10, send: () => {} }; runtime.contexts.set(10, { manifest: { id: "owner", permissions: ["local.hash"] }, window: ownerWindow });53
hashing.cleanupPlugin("owner"); assert.ok(calls.some((call) => call[0] === "cleanup" && call[1] === "owner"));54
});56
test("plugin language IPC uses the app language and broadcasts updates to open windows", async () => {57
const handlers = new Map();58
const events = [];59
const runtime = new PluginRuntime({ BrowserWindow: class {}, registry: {}, getLanguage: () => "cs", isVaultUnlocked: () => true });60
runtime.registerIpc({ handle: (channel, handler) => handlers.set(channel, handler) });61
const window = { isDestroyed: () => false, webContents: { send: (channel, payload) => events.push([channel, payload]) } };62
runtime.contexts.set(21, { manifest: { id: "chj.hash-checksum", permissions: ["local.hash"] }, window });63
runtime.windows.set("chj.hash-checksum", window);64
runtime.windows.set("closed", { isDestroyed: () => true });65
assert.equal(await handlers.get("plugin:ui:getLanguage")({ sender: { id: 21 } }), "cs");66
await assert.rejects(() => handlers.get("plugin:ui:getLanguage")({ sender: { id: 999 } }));67
runtime.notifyLanguageChanged("de");68
assert.deepEqual(events, [["plugin:ui:languageChanged", "de"]]);69
});71
test("hash errors retain their code in the IPC message for localized renderer errors", async () => {72
const handlers = new Map();73
const runtime = new PluginRuntime({ BrowserWindow: class {}, registry: {}, isVaultUnlocked: () => true, localHashService: {74
on: () => {},75
start: () => { throw Object.assign(new Error("Invalid key"), { code: "HASH_INVALID_KEY" }); }76
} });77
runtime.registerIpc({ handle: (channel, handler) => handlers.set(channel, handler) });78
runtime.contexts.set(22, { manifest: { id: "chj.hash-checksum", permissions: ["local.hash"] }, window: { isDestroyed: () => false } });79
await assert.rejects(() => handlers.get("plugin:hashing:start")({ sender: { id: 22 } }), { code: "HASH_INVALID_KEY", message: "HASH_INVALID_KEY: Invalid key" });80
});SHA-256: 9d363b9e71fe399a4d6e3030e39c559342ee6a90660e61665dc4f9a8ded5cee9
SHA-256 archivu: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0