├── .eslintrc ├── tsconfig.json ├── .gitignore ├── .github └── workflows │ ├── release.yml │ └── nodejs.yml ├── test ├── child.js └── sendmessage.test.js ├── LICENSE ├── package.json ├── README.md ├── CHANGELOG.md └── src └── index.ts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint-config-egg/typescript", 4 | "eslint-config-egg/lib/rules/enforce-node-prefix" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@eggjs/tsconfig", 3 | "compilerOptions": { 4 | "strict": true, 5 | "noImplicitAny": true, 6 | "target": "ES2022", 7 | "module": "NodeNext", 8 | "moduleResolution": "NodeNext" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.html 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | coverage/ 17 | .tshy* 18 | .eslintcache 19 | dist 20 | package-lock.json 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [ master ] 5 | 6 | permissions: 7 | contents: write 8 | deployments: write 9 | issues: write 10 | pull-requests: write 11 | id-token: write 12 | 13 | jobs: 14 | release: 15 | name: NPM Release 16 | uses: node-modules/github-actions/.github/workflows/npm-release.yml@master 17 | secrets: 18 | GIT_TOKEN: ${{ secrets.GIT_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | Job: 11 | name: Node.js 12 | uses: node-modules/github-actions/.github/workflows/node-test.yml@master 13 | with: 14 | os: 'ubuntu-latest, macos-latest, windows-latest' 15 | version: '16, 18.19.0, 18, 20, 22, 24' 16 | secrets: 17 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 18 | -------------------------------------------------------------------------------- /test/child.js: -------------------------------------------------------------------------------- 1 | import { isMainThread, parentPort } from 'node:worker_threads'; 2 | import sendmessage from '../dist/esm/index.js'; 3 | 4 | const listener = function(message) { 5 | if (message.disconnect) { 6 | process.disconnect(); 7 | } 8 | 9 | sendmessage(process, { 10 | from: 'child', 11 | got: message, 12 | }); 13 | }; 14 | 15 | process.on('message', listener); 16 | 17 | if (!isMainThread) { 18 | // worker thread 19 | parentPort.on('message', listener); 20 | } 21 | 22 | sendmessage(process, { 23 | from: 'child', 24 | hi: 'this is a message send to master', 25 | }); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-present node-modules and other contributors. 4 | Copyright (c) 2014 - 2015 fengmk2 and other contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendmessage", 3 | "version": "3.0.2", 4 | "engines": { 5 | "node": ">= 18.19.0" 6 | }, 7 | "description": "Send a cross process message if message channel is connected.", 8 | "scripts": { 9 | "lint": "eslint --cache src --ext .ts", 10 | "pretest": "npm run prepublishOnly && attw --pack", 11 | "test": "npm run lint && mocha --exit -t 5000 test/*.test.js", 12 | "ci": "c8 -r lcov -r text -r text-summary npm test", 13 | "prepublishOnly": "tshy && tshy-after" 14 | }, 15 | "devDependencies": { 16 | "@arethetypeswrong/cli": "^0.17.1", 17 | "@eggjs/tsconfig": "1", 18 | "@types/mocha": "10", 19 | "@types/node": "22", 20 | "c8": "^10.1.2", 21 | "eslint": "8", 22 | "eslint-config-egg": "14", 23 | "mm": "3", 24 | "mocha": "^11.0.1", 25 | "tshy": "^3.0.2", 26 | "tshy-after": "1", 27 | "typescript": "5" 28 | }, 29 | "homepage": "https://github.com/node-modules/sendmessage", 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/node-modules/sendmessage.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/node-modules/sendmessage/issues" 36 | }, 37 | "keywords": [ 38 | "sendmessage", 39 | "send", 40 | "cluster", 41 | "message", 42 | "channel closed" 43 | ], 44 | "author": "fengmk2 (https://github.com/fengmk2)", 45 | "license": "MIT", 46 | "type": "module", 47 | "tshy": { 48 | "exports": { 49 | ".": "./src/index.ts", 50 | "./package.json": "./package.json" 51 | } 52 | }, 53 | "exports": { 54 | ".": { 55 | "import": { 56 | "types": "./dist/esm/index.d.ts", 57 | "default": "./dist/esm/index.js" 58 | }, 59 | "require": { 60 | "types": "./dist/commonjs/index.d.ts", 61 | "default": "./dist/commonjs/index.js" 62 | } 63 | }, 64 | "./package.json": "./package.json" 65 | }, 66 | "files": [ 67 | "dist", 68 | "src" 69 | ], 70 | "types": "./dist/commonjs/index.d.ts", 71 | "main": "./dist/commonjs/index.js", 72 | "module": "./dist/esm/index.js" 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sendmessage 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![CI](https://github.com/node-modules/sendmessage/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/sendmessage/actions/workflows/nodejs.yml) 5 | [![Test coverage][codecov-image]][codecov-url] 6 | [![npm download][download-image]][download-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/sendmessage.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/sendmessage 10 | [download-image]: https://img.shields.io/npm/dm/sendmessage.svg?style=flat-square 11 | [download-url]: https://npmjs.org/package/sendmessage 12 | [codecov-image]: https://codecov.io/gh/node-modules/sendmessage/branch/master/graph/badge.svg 13 | [codecov-url]: https://codecov.io/gh/node-modules/sendmessage 14 | 15 | Send a cross process message if message channel is connected. 16 | Avoid [channel closed](https://github.com/joyent/node/blob/cfcb1de130867197cbc9c6012b7e84e08e53d032/lib/child_process.js#L411) error throw out. 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install sendmessage --save 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### master.js 27 | 28 | ```ts 29 | import { fork } from 'node:child_process'; 30 | import { sendmessage } from 'sendmessage'; 31 | 32 | const worker = fork('./worker.js'); 33 | 34 | sendmessage(worker, { hi: 'this is a message to worker' }); 35 | ``` 36 | 37 | ### worker.js 38 | 39 | ```ts 40 | import { sendmessage } from 'sendmessage'; 41 | 42 | sendmessage(process, { hello: 'this is a message to master' }); 43 | ``` 44 | 45 | ## API 46 | 47 | ### #sendmessage(childProcess, message) 48 | 49 | Send a cross process message. 50 | If a process is not child process, this will just call `process.emit('message', message)` instead. 51 | 52 | - childProcess: child process instance 53 | - message: the message need to send 54 | 55 | ```js 56 | sendmessage(process, { hello: 'this is a message to master' }); 57 | ``` 58 | 59 | You can switch to `process.emit('message', message)` using `process.env.SENDMESSAGE_ONE_PROCESS` 60 | 61 | ## License 62 | 63 | [MIT](LICENSE) 64 | 65 | ## Contributors 66 | 67 | [![Contributors](https://contrib.rocks/image?repo=node-modules/sendmessage)](https://github.com/node-modules/sendmessage/graphs/contributors) 68 | 69 | Made with [contributors-img](https://contrib.rocks). 70 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0.2 (2025-09-27) 4 | 5 | * fix: use npm trust publish (#9) ([abc5a8e](https://github.com/node-modules/sendmessage/commit/abc5a8e)), closes [#9](https://github.com/node-modules/sendmessage/issues/9) 6 | 7 | ## [3.0.1](https://github.com/node-modules/sendmessage/compare/v3.0.0...v3.0.1) (2024-12-11) 8 | 9 | 10 | ### Bug Fixes 11 | 12 | * export sendmessage function ([#7](https://github.com/node-modules/sendmessage/issues/7)) ([34ad0d0](https://github.com/node-modules/sendmessage/commit/34ad0d011679c716e11a786ef50f98cfd7f9b526)) 13 | 14 | ## [3.0.0](https://github.com/node-modules/sendmessage/compare/v2.0.0...v3.0.0) (2024-06-23) 15 | 16 | 17 | ### ⚠ BREAKING CHANGES 18 | 19 | * drop Node.js < 18.19.0 support 20 | 21 | part of https://github.com/eggjs/egg/issues/3644 22 | 23 | https://github.com/eggjs/egg/issues/5257 24 | 25 | ### Features 26 | 27 | * support cjs and esm both by tshy ([#6](https://github.com/node-modules/sendmessage/issues/6)) ([30acc65](https://github.com/node-modules/sendmessage/commit/30acc65d9359531ad817ccb8195af791805a63c5)) 28 | 29 | ## [2.0.0](https://github.com/node-modules/sendmessage/compare/v1.1.0...v2.0.0) (2023-06-13) 30 | 31 | 32 | ### ⚠ BREAKING CHANGES 33 | 34 | * Drop Node.js < 14 support 35 | 36 | ### Features 37 | 38 | * support worker_threads ipc ([#3](https://github.com/node-modules/sendmessage/issues/3)) ([3a27475](https://github.com/node-modules/sendmessage/commit/3a274755a43d8c7f4af6116717409ae2d9d66cea)) 39 | * use github action and drop non-lts Node.js support ([#4](https://github.com/node-modules/sendmessage/issues/4)) ([9592a9b](https://github.com/node-modules/sendmessage/commit/9592a9bd9fe880b565475d7583448af46da077f4)) 40 | 41 | --- 42 | 43 | 44 | 1.1.0 / 2016-11-02 45 | ================== 46 | 47 | * feat: add more env SENDMESSAGE_ONE_PROCESS (#2) 48 | 49 | 1.0.5 / 2015-04-14 50 | ================== 51 | 52 | * support IS_NODE_DEV_RUNNER env 53 | 54 | 1.0.4 / 2014-12-04 55 | ================== 56 | 57 | * fix: try to detect node-dev env, dont use NODE_ENV 58 | 59 | 1.0.3 / 2014-11-21 60 | ================== 61 | 62 | * use NODE_ENV === 'development' 63 | 64 | 1.0.2 / 2014-11-21 65 | ================== 66 | 67 | * fix missing message on node-dev 68 | 69 | 1.0.1 / 2014-11-14 70 | ================== 71 | 72 | * fix: support cluster.fork() child process 73 | 74 | 1.0.0 / 2014-11-14 75 | ================== 76 | 77 | * first commit 78 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { debuglog } from 'node:util'; 2 | import { isMainThread, parentPort } from 'node:worker_threads'; 3 | import { EventEmitter } from 'node:events'; 4 | 5 | const debug = debuglog('sendmessage'); 6 | 7 | let IS_NODE_DEV_RUNNER = /node\-dev$/.test(process.env._ || ''); 8 | if (!IS_NODE_DEV_RUNNER && process.env.IS_NODE_DEV_RUNNER) { 9 | IS_NODE_DEV_RUNNER = true; 10 | } 11 | debug('IS_NODE_DEV_RUNNER: %s', IS_NODE_DEV_RUNNER); 12 | 13 | export interface ChildProcessOrWorker extends EventEmitter { 14 | // Worker 15 | postMessage?(message: unknown): void; 16 | // ChildProcess 17 | send?(message: unknown): boolean; 18 | connected?: boolean; 19 | pid?: number; 20 | process?: { 21 | connected?: boolean; 22 | pid?: number; 23 | }; 24 | } 25 | 26 | export default function sendmessage(child: ChildProcessOrWorker, message: unknown) { 27 | if ( 28 | isMainThread // not in worker thread 29 | && typeof child.postMessage !== 'function' // child is not worker 30 | && typeof child.send !== 'function' 31 | ) { 32 | debug('child is master process, emit message: %j', message); 33 | // not a child process 34 | return setImmediate(child.emit.bind(child, 'message', message)); 35 | } 36 | 37 | if (IS_NODE_DEV_RUNNER || process.env.SENDMESSAGE_ONE_PROCESS) { 38 | // run with node-dev, only one process 39 | // https://github.com/node-modules/sendmessage/issues/1 40 | debug('node-dev: %s or SENDMESSAGE_ONE_PROCESS: %s, emit message: %j', 41 | IS_NODE_DEV_RUNNER, process.env.SENDMESSAGE_ONE_PROCESS, message); 42 | return setImmediate(child.emit.bind(child, 'message', message)); 43 | } 44 | 45 | // child is worker 46 | if (typeof child.postMessage === 'function') { 47 | debug('child is worker, postMessage: %j', message); 48 | return child.postMessage(message); 49 | } 50 | // in worker thread 51 | if (!isMainThread) { 52 | debug('in worker thread, parentPort.postMessage: %j', message); 53 | return parentPort!.postMessage(message); 54 | } 55 | 56 | // cluster.fork(): child.process is process 57 | // childprocess.fork(): child is process 58 | const connected = child.process ? child.process.connected : child.connected; 59 | 60 | if (connected) { 61 | debug('child is process, send: %j', message); 62 | return child.send!(message); 63 | } 64 | 65 | // just log warning message 66 | const pid = child.process ? child.process.pid : child.pid; 67 | const err = new Error('channel closed'); 68 | console.warn('[%s][sendmessage] WARN pid#%s channel closed, nothing send\nstack: %s', 69 | Date(), pid, err.stack); 70 | } 71 | 72 | export { sendmessage }; 73 | -------------------------------------------------------------------------------- /test/sendmessage.test.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'node:assert'; 2 | import path from 'node:path'; 3 | import { fileURLToPath } from 'node:url'; 4 | import childprocess from 'node:child_process'; 5 | import cluster from 'node:cluster'; 6 | import workerThreads from 'node:worker_threads'; 7 | import mm from 'mm'; 8 | import sendmessage, { sendmessage as sendmessage2 } from '../dist/esm/index.js'; 9 | 10 | const __filename = fileURLToPath(import.meta.url); 11 | const __dirname = path.dirname(__filename); 12 | const childFile = path.join(__dirname, 'child.js'); 13 | 14 | describe('sendmessage.test.js', () => { 15 | afterEach(mm.restore); 16 | 17 | describe('single process', () => { 18 | it('should emit message when process is not child process', done => { 19 | process.once('message', message => { 20 | assert.deepEqual(message, { 21 | foo: 'bar', 22 | }); 23 | done(); 24 | }); 25 | sendmessage(process, { foo: 'bar' }); 26 | }); 27 | }); 28 | 29 | describe('child_process.fork()', () => { 30 | it('should send cross process message', done => { 31 | const child = childprocess.fork(childFile); 32 | child.once('message', function(message) { 33 | assert.deepEqual(message, { 34 | from: 'child', 35 | hi: 'this is a message send to master', 36 | }); 37 | 38 | sendmessage2(child, { 39 | from: 'master', 40 | reply: 'this is a reply message send to child', 41 | }); 42 | 43 | child.once('message', function(message) { 44 | assert.deepEqual(message, { 45 | from: 'child', 46 | got: { 47 | from: 'master', 48 | reply: 'this is a reply message send to child', 49 | }, 50 | }); 51 | done(); 52 | }); 53 | }); 54 | }); 55 | 56 | it('should show warning message when channel closed', done => { 57 | const child = childprocess.fork(childFile); 58 | child.once('message', function(message) { 59 | assert.deepEqual(message, { 60 | from: 'child', 61 | hi: 'this is a message send to master', 62 | }); 63 | 64 | sendmessage(child, { 65 | from: 'master', 66 | disconnect: true, 67 | }); 68 | 69 | child.once('disconnect', () => { 70 | console.log('child#%d disconnected', child.pid); 71 | sendmessage(child, { 72 | from: 'master', 73 | hi: 'here?', 74 | }); 75 | done(); 76 | }); 77 | }); 78 | }); 79 | }); 80 | 81 | describe('cluster.fork()', () => { 82 | it('should send cross process message', done => { 83 | cluster.setupPrimary({ 84 | exec: childFile, 85 | }); 86 | const child = cluster.fork(); 87 | child.once('message', function(message) { 88 | assert.deepEqual(message, { 89 | from: 'child', 90 | hi: 'this is a message send to master', 91 | }); 92 | 93 | sendmessage(child, { 94 | from: 'master', 95 | reply: 'this is a reply message send to child', 96 | }); 97 | 98 | child.once('message', function(message) { 99 | assert.deepEqual(message, { 100 | from: 'child', 101 | got: { 102 | from: 'master', 103 | reply: 'this is a reply message send to child', 104 | }, 105 | }); 106 | done(); 107 | }); 108 | }); 109 | }); 110 | 111 | it('should show warning message when channel closed', done => { 112 | cluster.setupPrimary({ 113 | exec: childFile, 114 | }); 115 | const child = cluster.fork(); 116 | child.once('message', function(message) { 117 | assert.deepEqual(message, { 118 | from: 'child', 119 | hi: 'this is a message send to master', 120 | }); 121 | 122 | sendmessage(child, { 123 | from: 'master', 124 | disconnect: true, 125 | }); 126 | 127 | child.once('disconnect', () => { 128 | console.log('child#%d disconnected', child.process.pid); 129 | sendmessage(child, { 130 | from: 'master', 131 | hi: 'here?', 132 | }); 133 | done(); 134 | }); 135 | }); 136 | }); 137 | }); 138 | 139 | describe('worker_threads', () => { 140 | it('should send cross process message', done => { 141 | const worker = new workerThreads.Worker(childFile); 142 | worker.once('message', function(message) { 143 | assert.deepEqual(message, { 144 | from: 'child', 145 | hi: 'this is a message send to master', 146 | }); 147 | 148 | sendmessage(worker, { 149 | from: 'master', 150 | reply: 'this is a reply message send to child', 151 | }); 152 | 153 | worker.once('message', function(message) { 154 | assert.deepEqual(message, { 155 | from: 'child', 156 | got: { 157 | from: 'master', 158 | reply: 'this is a reply message send to child', 159 | }, 160 | }); 161 | done(); 162 | }); 163 | }); 164 | }); 165 | }); 166 | 167 | describe('SENDMESSAGE_ONE_PROCESS', () => { 168 | it('should emit when SENDMESSAGE_ONE_PROCESS = true', done => { 169 | const child = childprocess.fork(childFile); 170 | mm(process.env, 'SENDMESSAGE_ONE_PROCESS', 'true'); 171 | child.once('message', function(message) { 172 | assert.deepEqual(message, { 173 | from: 'child', 174 | hi: 'this is a message send to master', 175 | }); 176 | 177 | sendmessage(child, { 178 | from: 'master', 179 | reply: 'this is a reply message send to child', 180 | }); 181 | 182 | child.once('message', function(msg) { 183 | assert.deepEqual(msg, { 184 | from: 'master', 185 | reply: 'this is a reply message send to child', 186 | }); 187 | done(); 188 | }); 189 | }); 190 | }); 191 | }); 192 | }); 193 | --------------------------------------------------------------------------------