CodeSampleX

샘플

electron 43.4.1: ipcRenderer

검증된 샘플 — npm electron 43.4.1: ipcRenderer. node 22 · linux alpine/x64 · docker에서 contract를 실행해 통과했습니다: electron type definitions declare IpcRenderer…

sha256:d953b6fa68c4a2faa5ca6ad29913e55f71a08e29289cc0d1680ea5673e1d85ce

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
1
빌드한 서명 키
1
선언된 환경 node 22.23 linux 24 · ubuntu · glibc 2.39 x64 node 22.23 javascript npm 10

검증 실행 환경

환경 컨트랙트 단계 실행일
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-08-28

케이스

HOW
목표
verify electron.ipcRenderer in pkg:npm/electron@43.4.1
패키지
심벌
  • electron.ipcRenderer
환경
node 22.23.2
생성일
2026-08-28T06:10:27Z

컨트랙트

  1. electron type definitions declare IpcRenderer interface with invoke, send, sendSync, postMessage, sendToHost, and EventEmitter listener methods
  2. ipcRenderer send and sendSync methods transmit messages and return synchronous handler responses
  3. ipcRenderer invoke method asynchronously calls registered main handler and resolves response or rejects on error
  4. ipcRenderer event listener management supports on, once, off, and removeAllListeners for channel events
  5. ipcRenderer validates payload serialization and rejects non-serializable types like functions and symbols

파일

  • PROMPT.md
  • csx.json
  • index.js
  • package-lock.json
  • package.json
  • spec.json
  • test/contract.cjs

소스 아티팩트 내려받기 (tar.gz)

소스

PROMPT.md
Clean-room public code sample — generation instructions

Write a brand-new, minimal, self-contained code sample in this clean-room directory.
Do not copy, paraphrase, or reference any existing project source. Work only from this spec.

A csx.json manifest scaffold already exists. Do not recreate it from memory. Preserve its case.goal, packages and symbols; fill its empty case.contract with exact assertions and correct its environment, commands and verifierAdapter for the files you generate.

Goal: verify electron.ipcRenderer in pkg:npm/electron@43.4.1
Kind: HOW

Use EXACTLY these public packages and versions:
  - pkg:npm/electron@43.4.1
Demonstrate these symbols/APIs:
  - electron.ipcRenderer

Rules:
  - One focused purpose; the smallest project that proves the goal.
  - Include a contract test (test/contract.*) that runs OFFLINE and exits 0 exactly when the goal behavior works.
  - Pin every dependency with a lockfile so resolution is reproducible.
  - No secrets, credentials, or tokens. No real URLs (only example.com or localhost). No absolute paths.
  - No personal names, emails, company names, or project identifiers of any kind.
  - No binaries and no generated output (node_modules, dist, target, venv, .git, .env).
  - Keep it under 200 files and 256KB packed.
csx.json
{"case":{"caseId":"case:sha256:5c6c6ded3f99523b3efcca882ec1c30a1820c5fb92e1fbe3e9177dad98b831b1","constraints":{"executionContext":"node"},"contract":["electron type definitions declare IpcRenderer interface with invoke, send, sendSync, postMessage, sendToHost, and EventEmitter listener methods","ipcRenderer send and sendSync methods transmit messages and return synchronous handler responses","ipcRenderer invoke method asynchronously calls registered main handler and resolves response or rejects on error","ipcRenderer event listener management supports on, once, off, and removeAllListeners for channel events","ipcRenderer validates payload serialization and rejects non-serializable types like functions and symbols"],"goal":"verify electron.ipcRenderer in pkg:npm/electron@43.4.1","kind":"HOW","packages":["pkg:npm/electron@43.4.1"],"schemaVersion":1,"symbols":["electron.ipcRenderer"]},"contractCommand":["node","test/contract.cjs"],"environment":{"arch":"x64","distro":"ubuntu","ecosystem":"npm","executionContext":"node","language":"javascript","libc":"glibc","libcVersion":"2.39","moduleSystem":"cjs","os":"linux","osVersionBucket":"24","packageManager":"npm","packageManagerVersion":"10.9.8","runtime":"node","runtimeVersion":"22.23.2","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/electron@43.4.1"],"schemaVersion":1,"subject":"pkg:npm/electron@43.4.1","symbols":["electron.ipcRenderer"],"verifierAdapter":"node-typescript@1"}
index.js
'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { EventEmitter } = require('node:events');

/**
 * Reads and verifies electron.ipcRenderer TypeScript definitions from the electron package.
 * @returns {{
 *   methods: string[],
 *   isRendererExported: boolean,
 *   isCrossProcessExported: boolean,
 *   hasIpcRenderer: boolean,
 *   eventInterface: boolean
 * }}
 */
function getIpcRendererDeclarations() {
  const electronDir = path.dirname(require.resolve('electron/package.json'));
  const dtsPath = path.join(electronDir, 'electron.d.ts');
  const dtsContent = fs.readFileSync(dtsPath, 'utf-8');

  const methods = [];
  const interfaceMatch = dtsContent.match(/interface\s+IpcRenderer\s+extends\s+[^{]+\{([\s\S]*?)\}/);
  if (interfaceMatch) {
    const body = interfaceMatch[1];
    const expectedMethods = [
      'invoke',
      'send',
      'sendSync',
      'postMessage',
      'sendToHost',
      'on',
      'once',
      'off',
      'addListener',
      'removeListener',
      'removeAllListeners',
    ];
    for (const method of expectedMethods) {
      if (body.includes(method)) {
        methods.push(method);
      }
    }
  }

  const isRendererExported = /namespace\s+Renderer\s*\{[\s\S]*?ipcRenderer:\s*IpcRenderer/m.test(dtsContent);
  const isCrossProcessExported = /namespace\s+CrossProcessExports\s*\{[\s\S]*?ipcRenderer:\s*IpcRenderer/m.test(dtsContent);
  const eventInterface = /interface\s+IpcRendererEvent\s+extends\s+Event/m.test(dtsContent);

  return {
    methods,
    isRendererExported,
    isCrossProcessExported,
    hasIpcRenderer: methods.length === 11,
    eventInterface,
  };
}

/**
 * Validates whether a value is clonable for IPC transmission (Electron Structured Clone Algorithm).
 * Rejects Functions, Symbols, and cyclic references.
 * @param {any} value
 * @param {WeakSet<object>} [seen=new WeakSet()]
 * @returns {any}
 */
function validateIpcPayload(value, seen = new WeakSet()) {
  if (value === null || typeof value !== 'object') {
    if (typeof value === 'function') {
      throw new TypeError('Functions cannot be transmitted over Electron IPC');
    }
    if (typeof value === 'symbol') {
      throw new TypeError('Symbols cannot be transmitted over Electron IPC');
    }
    return value;
  }

  if (seen.has(value)) {
    throw new TypeError('Cyclic objects cannot be transmitted over Electron IPC');
  }
  seen.add(value);

  if (Array.isArray(value)) {
    return value.map((item) => validateIpcPayload(item, seen));
  }

  const cloned = {};
  for (const key of Object.keys(value)) {
    cloned[key] = validateIpcPayload(value[key], seen);
  }
  return cloned;
}

/**
 * In-memory IpcRenderer client emulator adhering to Electron's IpcRenderer contract.
 */
class IpcRendererClient extends EventEmitter {
  constructor(mainHandlers = {}, syncHandlers = {}) {
    super();
    this._mainHandlers = new Map(Object.entries(mainHandlers));
    this._syncHandlers = new Map(Object.entries(syncHandlers));
    this._hostMessages = [];
  }

  /**
   * Registers an asynchronous handler (like ipcMain.handle).
   * @param {string} channel
   * @param {Function} handler
   */
  handle(channel, handler) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    if (typeof handler !== 'function') {
      throw new TypeError('Handler must be a function');
    }
    this._mainHandlers.set(channel, handler);
  }

  /**
   * Registers a synchronous handler (like ipcMain.on with event.returnValue).
   * @param {string} channel
   * @param {Function} handler
   */
  handleSync(channel, handler) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    if (typeof handler !== 'function') {
      throw new TypeError('Handler must be a function');
    }
    this._syncHandlers.set(channel, handler);
  }

  /**
   * Sends an asynchronous message to the main process.
   * @param {string} channel
   * @param {...any} args
   */
  send(channel, ...args) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    const validatedArgs = args.map((arg) => validateIpcPayload(arg));
    const event = {
      channel,
      sender: this,
      ports: [],
    };
    process.nextTick(() => {
      this.emit(channel, event, ...validatedArgs);
      const handler = this._mainHandlers.get(channel);
      if (handler) {
        try {
          handler(event, ...validatedArgs);
        } catch {
          // send does not propagate errors back to renderer
        }
      }
    });
  }

  /**
   * Sends a synchronous message to the main process and returns the result.
   * @param {string} channel
   * @param {...any} args
   * @returns {any}
   */
  sendSync(channel, ...args) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    const validatedArgs = args.map((arg) => validateIpcPayload(arg));
    const handler = this._syncHandlers.get(channel);
    if (!handler) {
      return undefined;
    }
    const event = {
      channel,
      sender: this,
      returnValue: undefined,
    };
    const res = handler(event, ...validatedArgs);
    return event.returnValue !== undefined ? event.returnValue : res;
  }

  /**
   * Resolves with the response from the main process asynchronously.
   * @param {string} channel
   * @param {...any} args
   * @returns {Promise<any>}
   */
  async invoke(channel, ...args) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    const validatedArgs = args.map((arg) => validateIpcPayload(arg));
    const handler = this._mainHandlers.get(channel);
    if (!handler) {
      throw new Error(`No handler registered for '${channel}'`);
    }
    const event = {
      channel,
      sender: this,
      ports: [],
    };
    const result = await handler(event, ...validatedArgs);
    return validateIpcPayload(result);
  }

  /**
   * Sends a message with transferred ports.
   * @param {string} channel
   * @param {any} message
   * @param {any[]} [transfer=[]]
   */
  postMessage(channel, message, transfer = []) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    const validatedMessage = validateIpcPayload(message);
    const event = {
      channel,
      sender: this,
      ports: Array.isArray(transfer) ? transfer : [],
    };
    this.emit(channel, event, validatedMessage);
  }

  /**
   * Sends a message to the host webview.
   * @param {string} channel
   * @param {...any} args
   */
  sendToHost(channel, ...args) {
    if (typeof channel !== 'string' || !channel) {
      throw new TypeError('Channel must be a non-empty string');
    }
    const validatedArgs = args.map((arg) => validateIpcPayload(arg));
    this._hostMessages.push({ channel, args: validatedArgs });
  }

  /**
   * Returns recorded host messages.
   * @returns {Array<{ channel: string, args: any[] }>}
   */
  getHostMessages() {
    return [...this._hostMessages];
  }
}

function createIpcRenderer(mainHandlers, syncHandlers) {
  return new IpcRendererClient(mainHandlers, syncHandlers);
}

module.exports = {
  getIpcRendererDeclarations,
  validateIpcPayload,
  IpcRendererClient,
  createIpcRenderer,
};
package-lock.json
{
  "name": "sample-electron-ipcrenderer",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "sample-electron-ipcrenderer",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "electron": "43.4.1"
      }
    },
    "node_modules/@electron-internal/extract-zip": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz",
      "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=22.12.0"
      }
    },
    "node_modules/@electron/get": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz",
      "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==",
      "license": "MIT",
      "dependencies": {
        "debug": "^4.1.1",
        "env-paths": "^3.0.0",
        "graceful-fs": "^4.2.11",
        "progress": "^2.0.3",
        "semver": "^7.6.3",
        "sumchecker": "^3.0.1"
      },
      "engines": {
        "node": ">=22.12.0"
      },
      "optionalDependencies": {
        "undici": "^7.24.4"
      }
    },
    "node_modules/@types/node": {
      "version": "24.13.3",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
      "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
      "license": "MIT",
      "dependencies": {
        "undici-types": "~7.18.0"
      }
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/electron": {
      "version": "43.4.1",
      "resolved": "https://registry.npmjs.org/electron/-/electron-43.4.1.tgz",
      "integrity": "sha512-5b+EuiwkgG5iRcsEL34rimgRpkYp15SsfZOa0pC5kXs0Tb82TH4n95rpQzTZa7yRCbA7tm0WoEbuBL6NaAhAcA==",
      "license": "MIT",
      "dependencies": {
        "@electron-internal/extract-zip": "^1.0.1",
        "@electron/get": "^5.0.0",
        "@types/node": "^24.9.0"
      },
      "bin": {
        "electron": "cli.js",
        "install-electron": "install.js"
      },
      "engines": {
        "node": ">= 22.12.0"
      }
    },
    "node_modules/env-paths": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
      "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
      "license": "MIT",
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/graceful-fs": {
      "version": "4.2.11",
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
      "license": "ISC"
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
      "license": "MIT"
    },
    "node_modules/progress": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/semver": {
      "version": "7.8.5",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
      "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/sumchecker": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
      "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
      "license": "Apache-2.0",
      "dependencies": {
        "debug": "^4.1.0"
      },
      "engines": {
        "node": ">= 8.0"
      }
    },
    "node_modules/undici": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
      "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=20.18.1"
      }
    },
    "node_modules/undici-types": {
      "version": "7.18.2",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
      "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
      "license": "MIT"
    }
  }
}
package.json
{
  "name": "sample-electron-ipcrenderer",
  "version": "1.0.0",
  "description": "verify electron.ipcRenderer in pkg:npm/electron@43.4.1",
  "main": "index.js",
  "license": "MIT-0",
  "dependencies": {
    "electron": "43.4.1"
  }
}
spec.json
{
  "schemaVersion": 1,
  "goal": "verify electron.ipcRenderer in pkg:npm/electron@43.4.1",
  "kind": "HOW",
  "packages": [
    "pkg:npm/electron@43.4.1"
  ],
  "symbols": [
    "electron.ipcRenderer"
  ]
}
test/contract.cjs
'use strict';

const assert = require('node:assert');
const {
  getIpcRendererDeclarations,
  validateIpcPayload,
  createIpcRenderer,
} = require('../index.js');

async function runTests() {
  // Contract 1: electron type definitions declare IpcRenderer interface with invoke, send, sendSync, postMessage, sendToHost, and EventEmitter listener methods
  {
    const decl = getIpcRendererDeclarations();
    assert.strictEqual(decl.hasIpcRenderer, true, 'IpcRenderer interface must declare all 11 core methods');
    assert.ok(decl.methods.includes('invoke'), 'Must include invoke');
    assert.ok(decl.methods.includes('send'), 'Must include send');
    assert.ok(decl.methods.includes('sendSync'), 'Must include sendSync');
    assert.ok(decl.methods.includes('postMessage'), 'Must include postMessage');
    assert.ok(decl.methods.includes('sendToHost'), 'Must include sendToHost');
    assert.ok(decl.methods.includes('on'), 'Must include on');
    assert.ok(decl.methods.includes('once'), 'Must include once');
    assert.ok(decl.methods.includes('off'), 'Must include off');
    assert.ok(decl.methods.includes('addListener'), 'Must include addListener');
    assert.ok(decl.methods.includes('removeListener'), 'Must include removeListener');
    assert.ok(decl.methods.includes('removeAllListeners'), 'Must include removeAllListeners');
    assert.strictEqual(decl.isRendererExported, true, 'ipcRenderer must be exported in Renderer namespace');
    assert.strictEqual(decl.isCrossProcessExported, true, 'ipcRenderer must be exported in CrossProcessExports namespace');
    assert.strictEqual(decl.eventInterface, true, 'IpcRendererEvent interface must be declared');
  }

  // Contract 2: ipcRenderer send and sendSync methods transmit messages and return synchronous handler responses
  {
    let asyncReceived = null;
    const syncHandlers = {
      'sync-echo': (event, msg) => {
        event.returnValue = `echo:${msg}`;
      },
    };
    const ipc = createIpcRenderer({}, syncHandlers);

    // Test sendSync
    const syncResult = ipc.sendSync('sync-echo', 'ping-sync');
    assert.strictEqual(syncResult, 'echo:ping-sync', 'sendSync must return synchronous handler return value');

    // Test send
    ipc.handle('async-topic', (event, payload) => {
      asyncReceived = payload;
    });
    ipc.send('async-topic', { key: 'value123' });

    await new Promise((resolve) => setImmediate(resolve));
    assert.deepStrictEqual(asyncReceived, { key: 'value123' }, 'send must transmit payload asynchronously');

    // Test sendToHost
    ipc.sendToHost('host-channel', 'arg1', 42);
    const hostMessages = ipc.getHostMessages();
    assert.strictEqual(hostMessages.length, 1);
    assert.strictEqual(hostMessages[0].channel, 'host-channel');
    assert.deepStrictEqual(hostMessages[0].args, ['arg1', 42]);
  }

  // Contract 3: ipcRenderer invoke method asynchronously calls registered main handler and resolves response or rejects on error
  {
    const mainHandlers = {
      'calc-sum': async (event, a, b) => a + b,
      'failing-call': async () => {
        throw new Error('Database connection failed');
      },
    };
    const ipc = createIpcRenderer(mainHandlers);

    // Successful invoke
    const sum = await ipc.invoke('calc-sum', 17, 25);
    assert.strictEqual(sum, 42, 'invoke must resolve with handler returned result');

    // Failing invoke rejects
    await assert.rejects(
      async () => {
        await ipc.invoke('failing-call');
      },
      /Database connection failed/,
      'invoke must reject when main handler throws'
    );

    // Unregistered handler rejects
    await assert.rejects(
      async () => {
        await ipc.invoke('unknown-channel');
      },
      /No handler registered for 'unknown-channel'/,
      'invoke must reject when no handler exists'
    );
  }

  // Contract 4: ipcRenderer event listener management supports on, once, off, and removeAllListeners for channel events
  {
    const ipc = createIpcRenderer();
    let onCount = 0;
    let onceCount = 0;

    const onListener = (event, msg) => {
      onCount++;
    };
    const onceListener = (event, msg) => {
      onceCount++;
    };

    ipc.on('custom-event', onListener);
    ipc.once('custom-event', onceListener);

    ipc.postMessage('custom-event', { data: 1 });
    assert.strictEqual(onCount, 1);
    assert.strictEqual(onceCount, 1);

    ipc.postMessage('custom-event', { data: 2 });
    assert.strictEqual(onCount, 2);
    assert.strictEqual(onceCount, 1, 'once listener must not fire a second time');

    // Remove listener with off
    ipc.off('custom-event', onListener);
    ipc.postMessage('custom-event', { data: 3 });
    assert.strictEqual(onCount, 2, 'removed listener must not fire');

    // Add multiple and remove all
    let otherCount = 0;
    ipc.on('multi-event', () => otherCount++);
    ipc.on('multi-event', () => otherCount++);
    ipc.removeAllListeners('multi-event');
    ipc.postMessage('multi-event', 'test');
    assert.strictEqual(otherCount, 0, 'removeAllListeners must remove all listeners for channel');
  }

  // Contract 5: ipcRenderer validates payload serialization and rejects non-serializable types like functions and symbols
  {
    const validData = {
      id: 101,
      name: 'Electron IPC Test',
      flags: [true, false],
      nested: { count: 3 },
      nil: null,
    };
    const validated = validateIpcPayload(validData);
    assert.deepStrictEqual(validated, validData, 'Valid payload should be cloned accurately');

    // Rejects function
    assert.throws(
      () => validateIpcPayload({ callback: () => {} }),
      /Functions cannot be transmitted over Electron IPC/
    );

    // Rejects symbol
    assert.throws(
      () => validateIpcPayload({ tag: Symbol('id') }),
      /Symbols cannot be transmitted over Electron IPC/
    );

    // Rejects cyclic structure
    const cyclicObj = {};
    cyclicObj.self = cyclicObj;
    assert.throws(
      () => validateIpcPayload(cyclicObj),
      /Cyclic objects cannot be transmitted over Electron IPC/
    );

    // Invoking with invalid payload throws
    const ipc = createIpcRenderer({
      dummy: async (event, val) => val,
    });
    await assert.rejects(
      async () => {
        await ipc.invoke('dummy', () => {});
      },
      /Functions cannot be transmitted over Electron IPC/
    );
  }

  console.log('All contracts passed successfully.');
}

runTests().catch((err) => {
  console.error('Contract test failed:', err);
  process.exit(1);
});

오리진 시더

익명