super-productivity/electron/preload-api-capability.test.cjs
Johannes Millan 95d3b212bc
fix(electron): gate Jira IPC behind a one-shot capability (#9008)
* docs(plugins): add microsoft 365 calendar provider plan

* fix(jira): gate Electron requests behind one-shot capability

Claim privileged Jira IPC before plugin startup and return responses through invoke instead of a broadcast event. Keep arbitrary HTTP(S) Jira hosts supported while rejecting redirects and bounding request resources.

* fix(jira): enforce Electron request capability

Bind privileged Jira IPC to a main-issued renderer-document token and strip raw Electron events from renderer callbacks. Scope image authentication by origin, base path, and resource type while preserving safe redirects and legacy configurations.

* fix(electron): handle payload-only IPC lifecycle

Clear Jira image authentication before replacement and when a new renderer document claims the capability. Parse before-close IDs from payload-only events so pending sync and finish-day hooks can complete.

* fix(electron): address Jira IPC capability review findings

- electron.effects: read ANY_FILE_DOWNLOADED payload at [0] after the
  payload-only IPC refactor (was [1], now undefined -> TypeError on every
  download); guard against a malformed payload
- jira-capability: rotate the token on re-register so a renderer reload
  that reuses the WebFrameMain object is not permanently locked out of
  Jira; invalidates any stale token
- document that the one-shot consumption order, not the bypassable
  main-frame check, is the real capability boundary
- jira-electron-bridge: skip the no-op clearImgHeaders IPC round-trip
  when image auth was never set up (non-Jira detail-panel open/close)
- jira-api: route a synchronous _toElectronRequestInit throw through
  _handleResponse instead of leaking a dangling request-log entry

* test(electron): cover ANY_FILE_DOWNLOADED payload parsing

Extract parseDownloadedFilePayload from ElectronEffects and add a
regression spec pinning the payload-only shape ([file], not [event,
file]) that caused a TypeError on every download. Hardened against
non-array input surfaced by the new test.

* fix(electron): restore Node ambient globals for frontend build
2026-07-14 21:02:27 +02:00

94 lines
2.6 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
require('ts-node/register/transpile-only');
const { IPC } = require(
path.resolve(__dirname, 'shared-with-frontend/ipc-events.const.ts'),
);
const {
createJiraPreloadApiConsumer,
toPayloadOnlyIpcListener,
} = require(path.resolve(__dirname, 'shared-with-frontend/preload-api.ts'));
test('renderer listeners receive payloads without the Electron event object', () => {
const received = [];
const listener = toPayloadOnlyIpcListener((...args) => received.push(args));
const rawEvent = {
sender: {
invoke: () => {
throw new Error('must not be exposed');
},
},
};
listener(rawEvent, { safe: true }, 'payload');
assert.deepEqual(received, [[{ safe: true }, 'payload']]);
assert.notEqual(received[0][0], rawEvent);
});
test('Jira preload capability registers eagerly and can only be consumed once', async () => {
const invocations = [];
const invoke = async (channel, ...args) => {
invocations.push([channel, ...args]);
if (channel === IPC.JIRA_REGISTER_CAPABILITY) {
return 'main-issued-token';
}
return { requestId: 'request-1', response: { ok: true } };
};
const consume = createJiraPreloadApiConsumer(invoke);
assert.deepEqual(invocations, [[IPC.JIRA_REGISTER_CAPABILITY]]);
const api = consume();
assert.ok(api);
assert.equal(consume(), null);
assert.equal(consume(), null);
const request = {
requestId: 'request-1',
url: 'https://jira.example.com/rest/api/latest/myself',
requestInit: { method: 'GET', headers: {} },
allowSelfSignedCertificate: false,
};
await api.makeRequest(request);
await api.clearImgHeaders();
assert.deepEqual(invocations[1], [
IPC.JIRA_MAKE_REQUEST_EVENT,
{
capabilityToken: 'main-issued-token',
payload: request,
},
]);
assert.deepEqual(invocations[2], [
IPC.JIRA_CLEAR_IMG_HEADERS,
{
capabilityToken: 'main-issued-token',
payload: null,
},
]);
});
test('Jira preload API refuses calls when main does not issue a capability', async () => {
const invocations = [];
const invoke = async (channel, ...args) => {
invocations.push([channel, ...args]);
return null;
};
const api = createJiraPreloadApiConsumer(invoke)();
await assert.rejects(
() =>
api.makeRequest({
requestId: 'request-1',
url: 'https://jira.example.com',
requestInit: { method: 'GET', headers: {} },
allowSelfSignedCertificate: false,
}),
/unavailable/i,
);
assert.deepEqual(invocations, [[IPC.JIRA_REGISTER_CAPABILITY]]);
});