@lvce-editor/ipc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/index.js +4 -0
  4. package/dist/parts/Assert/Assert.js +1 -0
  5. package/dist/parts/CamelCase/CamelCase.js +11 -0
  6. package/dist/parts/Character/Character.js +12 -0
  7. package/dist/parts/ErrorCodes/ErrorCodes.js +22 -0
  8. package/dist/parts/FirstNodeWorkerEventType/FirstNodeWorkerEventType.js +3 -0
  9. package/dist/parts/FirstWebSocketEventType/FirstWebSocketEventType.js +2 -0
  10. package/dist/parts/FormatUtilityProcessName/FormatUtilityProcessName.js +5 -0
  11. package/dist/parts/GetFirstEvent/GetFirstEvent.js +23 -0
  12. package/dist/parts/GetFirstNodeChildProcessEvent/GetFirstNodeChildProcessEvent.js +41 -0
  13. package/dist/parts/GetFirstNodeWorkerEvent/GetFirstNodeWorkerEvent.js +9 -0
  14. package/dist/parts/GetFirstUtilityProcessEvent/GetFirstUtilityProcessEvent.js +42 -0
  15. package/dist/parts/GetFirstWebSocketEvent/GetFirstWebSocketEvent.js +25 -0
  16. package/dist/parts/GetHelpfulChildProcessError/GetHelpfulChildProcessError.js +119 -0
  17. package/dist/parts/IpcChild/IpcChild.js +9 -0
  18. package/dist/parts/IpcChildModule/IpcChildModule.js +10 -0
  19. package/dist/parts/IpcChildType/IpcChildType.js +5 -0
  20. package/dist/parts/IpcChildWithElectronMessagePort/IpcChildWithElectronMessagePort.js +51 -0
  21. package/dist/parts/IpcChildWithElectronUtilityProcess/IpcChildWithElectronUtilityProcess.js +45 -0
  22. package/dist/parts/IpcChildWithNodeForkedProcess/IpcChildWithNodeForkedProcess.js +46 -0
  23. package/dist/parts/IpcChildWithWebSocket/IpcChildWithWebSocket.js +59 -0
  24. package/dist/parts/IpcError/IpcError.js +16 -0
  25. package/dist/parts/IpcParent/IpcParent.js +25 -0
  26. package/dist/parts/IpcParentModule/IpcParentModule.js +14 -0
  27. package/dist/parts/IpcParentType/IpcParentType.js +3 -0
  28. package/dist/parts/IpcParentWithElectronUtilityProcess/IpcParentWithElectronUtilityProcess.js +43 -0
  29. package/dist/parts/IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js +37 -0
  30. package/dist/parts/IpcParentWithNodeWorker/IpcParentWithNodeWorker.js +49 -0
  31. package/dist/parts/IsMessagePortMain/IsMessagePortMain.js +3 -0
  32. package/dist/parts/IsWebSocketOpen/IsWebSocketOpen.js +5 -0
  33. package/dist/parts/JoinLines/JoinLines.js +5 -0
  34. package/dist/parts/Promises/Promises.js +19 -0
  35. package/dist/parts/SplitLines/SplitLines.js +5 -0
  36. package/dist/parts/VError/VError.js +1 -0
  37. package/dist/parts/WebSocketReadyState/WebSocketReadyState.js +5 -0
  38. package/dist/parts/WebSocketSerialization/WebSocketSerialization.js +7 -0
  39. package/dist/parts/WebSocketServer/WebSocketServer.js +1 -0
  40. package/package.json +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lvce Editor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Lvce Editor Ipc
2
+
3
+ Inter Process Communiction for use in Lvce Editor.
4
+
5
+ ### Usage
6
+
7
+ ```js
8
+ import { IpcParent, IpcParentType } from '@lvce-editor/ipc'
9
+
10
+ const ipc = await IpcParent.create({
11
+ method: IpcParentType.NodeWorker,
12
+ path: '/test/worker.js',
13
+ })
14
+
15
+ ipc.send({
16
+ jsonrpc: '2.0',
17
+ method: 'readFile',
18
+ params: ['/test/file.txt'],
19
+ })
20
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * as IpcChild from './parts/IpcChild/IpcChild.js'
2
+ export * as IpcChildType from './parts/IpcChildType/IpcChildType.js'
3
+ export * as IpcParent from './parts/IpcParent/IpcParent.js'
4
+ export * as IpcParentType from './parts/IpcParentType/IpcParentType.js'
@@ -0,0 +1 @@
1
+ export * from '@lvce-editor/assert'
@@ -0,0 +1,11 @@
1
+ import * as Character from '../Character/Character.js'
2
+
3
+ const firstLetterLowerCase = (string) => {
4
+ return string[0].toLowerCase() + string.slice(1)
5
+ }
6
+
7
+ export const camelCase = (string) => {
8
+ const parts = string.split(Character.Space)
9
+ const lowerParts = parts.map(firstLetterLowerCase)
10
+ return lowerParts.join(Character.Dash)
11
+ }
@@ -0,0 +1,12 @@
1
+ export const Backslash = '\\'
2
+ export const Dash = '-'
3
+ export const Dot = '.'
4
+ export const EmptyString = ''
5
+ export const NewLine = '\n'
6
+ export const OpenAngleBracket = '<'
7
+ export const Slash = '/'
8
+ export const Space = ' '
9
+ export const Tab = '\t'
10
+ export const Underline = '_'
11
+ export const T = 't'
12
+ export const SemiColon = ';'
@@ -0,0 +1,22 @@
1
+ export const E_COLOR_THEME_NOT_FOUND = 'E_COLOR_THEME_NOT_FOUND'
2
+ export const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND'
3
+ export const E_ICON_THEME_NOT_FOUND = 'E_ICON_THEME_NOT_FOUND'
4
+ export const E_INCOMPATIBLE_NATIVE_MODULE = 'E_INCOMPATIBLE_NATIVE_MODULE'
5
+ export const E_MANIFEST_NOT_FOUND = 'E_MANIFEST_NOT_FOUND'
6
+ export const E_MODULES_NOT_SUPPORTED_IN_ELECTRON = 'E_MODULES_NOT_SUPPORTED_IN_ELECTRON'
7
+ export const E_JSON_PARSE = 'E_JSON_PARSE'
8
+ export const EACCES = 'EACCES'
9
+ export const ECONNRESET = 'ECONNRESET'
10
+ export const EEXIST = 'EEXIST'
11
+ export const EISDIR = 'EISDIR'
12
+ export const ELOOP = 'ELOOP'
13
+ export const ENOENT = 'ENOENT'
14
+ export const ENOTDIR = 'ENOTDIR'
15
+ export const EPERM = 'EPERM'
16
+ export const EPIPE = 'EPIPE'
17
+ export const ERR_IPC_CHANNEL_CLOSED = 'ERR_IPC_CHANNEL_CLOSED'
18
+ export const ERR_MODULE_NOT_FOUND = 'ERR_MODULE_NOT_FOUND'
19
+ export const EXDEV = 'EXDEV'
20
+ export const ERR_DLOPEN_FAILED = 'ERR_DLOPEN_FAILED'
21
+ export const ESRCH = 'ESRCH'
22
+ export const E_RIP_GREP_NOT_FOUND = 'E_RIP_GREP_NOT_FOUND'
@@ -0,0 +1,3 @@
1
+ export const Exit = 1
2
+ export const Error = 2
3
+ export const Message = 3
@@ -0,0 +1,2 @@
1
+ export const Open = 1
2
+ export const Close = 2
@@ -0,0 +1,5 @@
1
+ import * as CamelCase from '../CamelCase/CamelCase.js'
2
+
3
+ export const formatUtilityProcessName = (name) => {
4
+ return CamelCase.camelCase(name)
5
+ }
@@ -0,0 +1,23 @@
1
+ import * as Promises from '../Promises/Promises.js'
2
+
3
+ export const getFirstEvent = (eventEmitter, eventMap) => {
4
+ const { resolve, promise } = Promises.withResolvers()
5
+ const listenerMap = Object.create(null)
6
+ const cleanup = (value) => {
7
+ for (const event of Object.keys(eventMap)) {
8
+ eventEmitter.off(event, listenerMap[event])
9
+ }
10
+ resolve(value)
11
+ }
12
+ for (const [event, type] of Object.entries(eventMap)) {
13
+ const listener = (event) => {
14
+ cleanup({
15
+ type,
16
+ event,
17
+ })
18
+ }
19
+ eventEmitter.on(event, listener)
20
+ listenerMap[event] = listener
21
+ }
22
+ return promise
23
+ }
@@ -0,0 +1,41 @@
1
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
2
+
3
+ export const getFirstNodeChildProcessEvent = async (childProcess) => {
4
+ const { type, event, stdout, stderr } = await new Promise((resolve, reject) => {
5
+ let stderr = ''
6
+ let stdout = ''
7
+ const cleanup = (value) => {
8
+ if (childProcess.stdout && childProcess.stderr) {
9
+ childProcess.stderr.off('data', handleStdErrData)
10
+ childProcess.stdout.off('data', handleStdoutData)
11
+ }
12
+ childProcess.off('message', handleMessage)
13
+ childProcess.off('exit', handleExit)
14
+ childProcess.off('error', handleError)
15
+ resolve(value)
16
+ }
17
+ const handleStdErrData = (data) => {
18
+ stderr += data
19
+ }
20
+ const handleStdoutData = (data) => {
21
+ stdout += data
22
+ }
23
+ const handleMessage = (event) => {
24
+ cleanup({ type: FirstNodeWorkerEventType.Message, event, stdout, stderr })
25
+ }
26
+ const handleExit = (event) => {
27
+ cleanup({ type: FirstNodeWorkerEventType.Exit, event, stdout, stderr })
28
+ }
29
+ const handleError = (event) => {
30
+ cleanup({ type: FirstNodeWorkerEventType.Error, event, stdout, stderr })
31
+ }
32
+ if (childProcess.stdout && childProcess.stderr) {
33
+ childProcess.stderr.on('data', handleStdErrData)
34
+ childProcess.stdout.on('data', handleStdoutData)
35
+ }
36
+ childProcess.on('message', handleMessage)
37
+ childProcess.on('exit', handleExit)
38
+ childProcess.on('error', handleError)
39
+ })
40
+ return { type, event, stdout, stderr }
41
+ }
@@ -0,0 +1,9 @@
1
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
2
+ import * as GetFirstEvent from '../GetFirstEvent/GetFirstEvent.js'
3
+
4
+ export const getFirstNodeWorkerEvent = (worker) => {
5
+ return GetFirstEvent.getFirstEvent(worker, {
6
+ exit: FirstNodeWorkerEventType.Exit,
7
+ error: FirstNodeWorkerEventType.Error,
8
+ })
9
+ }
@@ -0,0 +1,42 @@
1
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
2
+ import * as Promises from '../Promises/Promises.js'
3
+
4
+ /**
5
+ *
6
+ * @param {import('electron').UtilityProcess} utilityProcess
7
+ * @returns
8
+ */
9
+ export const getFirstUtilityProcessEvent = async (utilityProcess) => {
10
+ const { resolve, promise } = Promises.withResolvers()
11
+ let stdout = ''
12
+ let stderr = ''
13
+ const cleanup = (value) => {
14
+ // @ts-ignore
15
+ utilityProcess.stderr.off('data', handleStdErrData)
16
+ // @ts-ignore
17
+ utilityProcess.stdout.off('data', handleStdoutData)
18
+ utilityProcess.off('message', handleMessage)
19
+ utilityProcess.off('exit', handleExit)
20
+ resolve(value)
21
+ }
22
+ const handleStdErrData = (data) => {
23
+ stderr += data
24
+ }
25
+ const handleStdoutData = (data) => {
26
+ stdout += data
27
+ }
28
+ const handleMessage = (event) => {
29
+ cleanup({ type: FirstNodeWorkerEventType.Message, event, stdout, stderr })
30
+ }
31
+ const handleExit = (event) => {
32
+ cleanup({ type: FirstNodeWorkerEventType.Exit, event, stdout, stderr })
33
+ }
34
+ // @ts-ignore
35
+ utilityProcess.stderr.on('data', handleStdErrData)
36
+ // @ts-ignore
37
+ utilityProcess.stdout.on('data', handleStdoutData)
38
+ utilityProcess.on('message', handleMessage)
39
+ utilityProcess.on('exit', handleExit)
40
+ const { type, event } = await promise
41
+ return { type, event, stdout, stderr }
42
+ }
@@ -0,0 +1,25 @@
1
+ import * as FirstWebSocketEventType from '../FirstWebSocketEventType/FirstWebSocketEventType.js'
2
+ import * as GetFirstEvent from '../GetFirstEvent/GetFirstEvent.js'
3
+ import * as WebSocketReadyState from '../WebSocketReadyState/WebSocketReadyState.js'
4
+
5
+ export const getFirstWebSocketEvent = async (webSocket) => {
6
+ switch (webSocket.readyState) {
7
+ case WebSocketReadyState.Open:
8
+ return {
9
+ type: FirstWebSocketEventType.Open,
10
+ event: undefined,
11
+ }
12
+ case WebSocketReadyState.Closed:
13
+ return {
14
+ type: FirstWebSocketEventType.Close,
15
+ event: undefined,
16
+ }
17
+ default:
18
+ break
19
+ }
20
+ const { type, event } = await GetFirstEvent.getFirstEvent(webSocket, {
21
+ open: FirstWebSocketEventType.Open,
22
+ close: FirstWebSocketEventType.Close,
23
+ })
24
+ return { type, event }
25
+ }
@@ -0,0 +1,119 @@
1
+ import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
2
+ import * as SplitLines from '../SplitLines/SplitLines.js'
3
+ import * as JoinLines from '../JoinLines/JoinLines.js'
4
+
5
+ const RE_NATIVE_MODULE_ERROR = /^innerError Error: Cannot find module '.*.node'/
6
+ const RE_NATIVE_MODULE_ERROR_2 = /was compiled against a different Node.js version/
7
+
8
+ const RE_MESSAGE_CODE_BLOCK_START = /^Error: The module '.*'$/
9
+ const RE_MESSAGE_CODE_BLOCK_END = /^\s* at/
10
+
11
+ const RE_AT = /^\s+at/
12
+ const RE_AT_PROMISE_INDEX = /^\s*at async Promise.all \(index \d+\)$/
13
+
14
+ const isUnhelpfulNativeModuleError = (stderr) => {
15
+ return RE_NATIVE_MODULE_ERROR.test(stderr) && RE_NATIVE_MODULE_ERROR_2.test(stderr)
16
+ }
17
+
18
+ const isMessageCodeBlockStartIndex = (line) => {
19
+ return RE_MESSAGE_CODE_BLOCK_START.test(line)
20
+ }
21
+
22
+ const isMessageCodeBlockEndIndex = (line) => {
23
+ return RE_MESSAGE_CODE_BLOCK_END.test(line)
24
+ }
25
+
26
+ const getMessageCodeBlock = (stderr) => {
27
+ const lines = SplitLines.splitLines(stderr)
28
+ const startIndex = lines.findIndex(isMessageCodeBlockStartIndex)
29
+ const endIndex = startIndex + lines.slice(startIndex).findIndex(isMessageCodeBlockEndIndex, startIndex)
30
+ const relevantLines = lines.slice(startIndex, endIndex)
31
+ const relevantMessage = relevantLines.join(' ').slice('Error: '.length)
32
+ return relevantMessage
33
+ }
34
+
35
+ const getNativeModuleErrorMessage = (stderr) => {
36
+ const message = getMessageCodeBlock(stderr)
37
+ return {
38
+ message: `Incompatible native node module: ${message}`,
39
+ code: ErrorCodes.E_INCOMPATIBLE_NATIVE_MODULE,
40
+ }
41
+ }
42
+
43
+ const isModulesSyntaxError = (stderr) => {
44
+ if (!stderr) {
45
+ return false
46
+ }
47
+ return stderr.includes('SyntaxError: Cannot use import statement outside a module')
48
+ }
49
+
50
+ const getModuleSyntaxError = (stderr) => {
51
+ return {
52
+ message: `ES Modules are not supported in electron`,
53
+ code: ErrorCodes.E_MODULES_NOT_SUPPORTED_IN_ELECTRON,
54
+ }
55
+ }
56
+
57
+ const isModuleNotFoundError = (stderr) => {
58
+ if (!stderr) {
59
+ return false
60
+ }
61
+ return stderr.includes('ERR_MODULE_NOT_FOUND')
62
+ }
63
+
64
+ const isModuleNotFoundMessage = (line) => {
65
+ return line.includes('ERR_MODULE_NOT_FOUND')
66
+ }
67
+
68
+ const getModuleNotFoundError = (stderr) => {
69
+ const lines = SplitLines.splitLines(stderr)
70
+ const messageIndex = lines.findIndex(isModuleNotFoundMessage)
71
+ const message = lines[messageIndex]
72
+ return {
73
+ message,
74
+ code: ErrorCodes.ERR_MODULE_NOT_FOUND,
75
+ }
76
+ }
77
+
78
+ const isNormalStackLine = (line) => {
79
+ return RE_AT.test(line) && !RE_AT_PROMISE_INDEX.test(line)
80
+ }
81
+
82
+ const getDetails = (lines) => {
83
+ const index = lines.findIndex(isNormalStackLine)
84
+ if (index === -1) {
85
+ return {
86
+ actualMessage: JoinLines.joinLines(lines),
87
+ rest: [],
88
+ }
89
+ }
90
+ let lastIndex = index - 1
91
+ while (++lastIndex < lines.length) {
92
+ if (!isNormalStackLine(lines[lastIndex])) {
93
+ break
94
+ }
95
+ }
96
+ return {
97
+ actualMessage: lines[index - 1],
98
+ rest: lines.slice(index, lastIndex),
99
+ }
100
+ }
101
+
102
+ export const getHelpfulChildProcessError = (stdout, stderr) => {
103
+ if (isUnhelpfulNativeModuleError(stderr)) {
104
+ return getNativeModuleErrorMessage(stderr)
105
+ }
106
+ if (isModulesSyntaxError(stderr)) {
107
+ return getModuleSyntaxError(stderr)
108
+ }
109
+ if (isModuleNotFoundError(stderr)) {
110
+ return getModuleNotFoundError(stderr)
111
+ }
112
+ const lines = SplitLines.splitLines(stderr)
113
+ const { actualMessage, rest } = getDetails(lines)
114
+ return {
115
+ message: `${actualMessage}`,
116
+ code: '',
117
+ stack: rest,
118
+ }
119
+ }
@@ -0,0 +1,9 @@
1
+ import * as IpcChildModule from '../IpcChildModule/IpcChildModule.js'
2
+
3
+ export const listen = async ({ method, ...params }) => {
4
+ const module = await IpcChildModule.getModule(method)
5
+ // @ts-ignore
6
+ const rawIpc = await module.listen(params)
7
+ const ipc = module.wrap(rawIpc)
8
+ return ipc
9
+ }
@@ -0,0 +1,10 @@
1
+ import * as IpcChildType from '../IpcChildType/IpcChildType.js'
2
+
3
+ export const getModule = (method) => {
4
+ switch (method) {
5
+ case IpcChildType.ElectronMessagePort:
6
+ return import('../IpcChildWithElectronMessagePort/IpcChildWithElectronMessagePort.js')
7
+ default:
8
+ throw new Error('unexpected ipc type')
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ export const NodeWorker = 1
2
+ export const NodeForkedProcess = 2
3
+ export const WebSocket = 3
4
+ export const ElectronUtilityProcess = 4
5
+ export const ElectronMessagePort = 5
@@ -0,0 +1,51 @@
1
+ import { IpcError } from '../IpcError/IpcError.js'
2
+ import * as IsMessagePortMain from '../IsMessagePortMain/IsMessagePortMain.js'
3
+
4
+ export const listen = ({ messagePort }) => {
5
+ if (!IsMessagePortMain.isMessagePortMain(messagePort)) {
6
+ throw new IpcError('port must be of type MessagePortMain')
7
+ }
8
+ return messagePort
9
+ }
10
+
11
+ const getActualData = (event) => {
12
+ const { data, ports } = event
13
+ if (ports.length === 0) {
14
+ return data
15
+ }
16
+ return {
17
+ ...data,
18
+ params: [...ports, ...data.params],
19
+ }
20
+ }
21
+
22
+ export const wrap = (messagePort) => {
23
+ return {
24
+ messagePort,
25
+ on(event, listener) {
26
+ if (event === 'message') {
27
+ const wrappedListener = (event) => {
28
+ const actualData = getActualData(event)
29
+ listener(actualData)
30
+ }
31
+ this.messagePort.on(event, wrappedListener)
32
+ } else if (event === 'close') {
33
+ this.messagePort.on('close', listener)
34
+ } else {
35
+ throw new Error('unsupported event type')
36
+ }
37
+ },
38
+ off(event, listener) {
39
+ this.messagePort.off(event, listener)
40
+ },
41
+ send(message) {
42
+ this.messagePort.postMessage(message)
43
+ },
44
+ dispose() {
45
+ this.messagePort.close()
46
+ },
47
+ start() {
48
+ this.messagePort.start()
49
+ },
50
+ }
51
+ }
@@ -0,0 +1,45 @@
1
+ import * as GetUtilityProcessPortData from '../GetUtilityProcessPortData/GetUtilityProcessPortData.js'
2
+
3
+ export const listen = () => {
4
+ // @ts-ignore
5
+ const { parentPort } = process
6
+ if (!parentPort) {
7
+ throw new Error('parent port must be defined')
8
+ }
9
+ return parentPort
10
+ }
11
+
12
+ export const signal = (parentPort) => {
13
+ parentPort.postMessage('ready')
14
+ }
15
+
16
+ export const wrap = (parentPort) => {
17
+ return {
18
+ parentPort,
19
+ on(event, listener) {
20
+ if (event === 'message') {
21
+ const wrappedListener = (event) => {
22
+ const actualData = GetUtilityProcessPortData.getUtilityProcessPortData(event)
23
+ listener(actualData)
24
+ }
25
+ this.parentPort.on(event, wrappedListener)
26
+ } else if (event === 'close') {
27
+ this.parentPort.on('close', listener)
28
+ } else {
29
+ throw new Error('unsupported event type')
30
+ }
31
+ },
32
+ off(event, listener) {
33
+ this.parentPort.off(event, listener)
34
+ },
35
+ send(message) {
36
+ this.parentPort.postMessage(message)
37
+ },
38
+ sendAndTransfer(message, transfer) {
39
+ this.parentPort.postMessage(message, transfer)
40
+ },
41
+ dispose() {
42
+ this.parentPort.close()
43
+ },
44
+ }
45
+ }
@@ -0,0 +1,46 @@
1
+ export const listen = async () => {
2
+ if (!process.send) {
3
+ throw new Error('process.send must be defined')
4
+ }
5
+ return process
6
+ }
7
+
8
+ export const signal = (process) => {
9
+ process.send('ready')
10
+ }
11
+
12
+ const getActualData = (message, handle) => {
13
+ if (handle) {
14
+ return {
15
+ ...message,
16
+ params: [...message.params, handle],
17
+ }
18
+ }
19
+ return message
20
+ }
21
+
22
+ export const wrap = (process) => {
23
+ return {
24
+ process,
25
+ on(event, listener) {
26
+ if (event === 'message') {
27
+ const wrappedListener = (event, handle) => {
28
+ const actualData = getActualData(event, handle)
29
+ listener(actualData)
30
+ }
31
+ this.process.on(event, wrappedListener)
32
+ } else if (event === 'close') {
33
+ this.process.on('close', listener)
34
+ } else {
35
+ throw new Error('unsupported event type')
36
+ }
37
+ },
38
+ off(event, listener) {
39
+ this.process.off(event, listener)
40
+ },
41
+ send(message) {
42
+ this.process.send(message)
43
+ },
44
+ dispose() {},
45
+ }
46
+ }
@@ -0,0 +1,59 @@
1
+ import * as GetFirstWebSocketEvent from '../GetFirstWebSocketEvent/GetFirstWebSocketEvent.js'
2
+ import { IpcError } from '../IpcError/IpcError.js'
3
+ import * as IsWebSocketOpen from '../IsWebSocketOpen/IsWebSocketOpen.js'
4
+ import * as WebSocketSerialization from '../WebSocketSerialization/WebSocketSerialization.js'
5
+ import * as WebSocketServer from '../WebSocketServer/WebSocketServer.js'
6
+
7
+ export const listen = async ({ request, handle }) => {
8
+ if (!request) {
9
+ throw new IpcError('request must be defined')
10
+ }
11
+ if (!handle) {
12
+ throw new IpcError('handle must be defined')
13
+ }
14
+ const webSocket = await WebSocketServer.handleUpgrade(request, handle)
15
+ webSocket.pause()
16
+ if (!IsWebSocketOpen.isWebSocketOpen(webSocket)) {
17
+ const { type, event } = await GetFirstWebSocketEvent.getFirstWebSocketEvent(webSocket)
18
+ }
19
+ return webSocket
20
+ }
21
+
22
+ export const wrap = (webSocket) => {
23
+ return {
24
+ webSocket,
25
+ /**
26
+ * @type {any}
27
+ */
28
+ wrappedListener: undefined,
29
+ on(event, listener) {
30
+ switch (event) {
31
+ case 'message':
32
+ const wrappedListener = (message) => {
33
+ const data = WebSocketSerialization.deserialize(message)
34
+ listener(data)
35
+ }
36
+ webSocket.on('message', wrappedListener)
37
+ break
38
+ case 'close':
39
+ webSocket.on('close', listener)
40
+ break
41
+ default:
42
+ throw new Error('unknown event listener type')
43
+ }
44
+ },
45
+ off(event, listener) {
46
+ this.webSocket.off(event, listener)
47
+ },
48
+ send(message) {
49
+ const stringifiedMessage = WebSocketSerialization.serialize(message)
50
+ this.webSocket.send(stringifiedMessage)
51
+ },
52
+ dispose() {
53
+ this.webSocket.close()
54
+ },
55
+ start() {
56
+ this.webSocket.resume()
57
+ },
58
+ }
59
+ }
@@ -0,0 +1,16 @@
1
+ import * as GetHelpfulChildProcessError from '../GetHelpfulChildProcessError/GetHelpfulChildProcessError.js'
2
+ import { VError } from '../VError/VError.js'
3
+
4
+ export class IpcError extends VError {
5
+ constructor(message, stdout = '', stderr = '') {
6
+ if (stdout || stderr) {
7
+ const cause = GetHelpfulChildProcessError.getHelpfulChildProcessError(message, stdout, stderr)
8
+ super(cause, message)
9
+ } else {
10
+ super(message)
11
+ }
12
+ this.name = 'IpcError'
13
+ this.stdout = stdout
14
+ this.stderr = stderr
15
+ }
16
+ }
@@ -0,0 +1,25 @@
1
+ import * as IpcParentModule from '../IpcParentModule/IpcParentModule.js'
2
+
3
+ /**
4
+ *
5
+ * @param {*} param0
6
+ * @returns {Promise<any>}
7
+ */
8
+ export const create = async ({ method, ...options }) => {
9
+ const module = await IpcParentModule.getModule(method)
10
+ // @ts-ignore
11
+ const rawIpc = await module.create(options)
12
+ // @ts-ignore
13
+ if (module.effects) {
14
+ // @ts-ignore
15
+ module.effects({
16
+ rawIpc,
17
+ ...options,
18
+ })
19
+ }
20
+ if (options.noReturn) {
21
+ return undefined
22
+ }
23
+ const ipc = module.wrap(rawIpc)
24
+ return ipc
25
+ }
@@ -0,0 +1,14 @@
1
+ import * as IpcParentType from '../IpcParentType/IpcParentType.js'
2
+
3
+ export const getModule = (method) => {
4
+ switch (method) {
5
+ case IpcParentType.NodeWorker:
6
+ return import('../IpcParentWithNodeWorker/IpcParentWithNodeWorker.js')
7
+ case IpcParentType.NodeForkedProcess:
8
+ return import('../IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js')
9
+ case IpcParentType.ElectronUtilityProcess:
10
+ return import('../IpcParentWithElectronUtilityProcess/IpcParentWithElectronUtilityProcess.js')
11
+ default:
12
+ throw new Error(`unexpected ipc type ${method}`)
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ export const NodeWorker = 1
2
+ export const NodeForkedProcess = 2
3
+ export const ElectronUtilityProcess = 3
@@ -0,0 +1,43 @@
1
+ import { utilityProcess } from 'electron'
2
+ import * as Assert from '../Assert/Assert.js'
3
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
4
+ import * as GetFirstUtilityProcessEvent from '../GetFirstUtilityProcessEvent/GetFirstUtilityProcessEvent.js'
5
+ import { IpcError } from '../IpcError/IpcError.js'
6
+
7
+ export const create = async ({ path, argv = [], execArgv = [], name }) => {
8
+ Assert.string(path)
9
+ const actualArgv = ['--ipc-type=electron-utility-process', ...argv]
10
+ const childProcess = utilityProcess.fork(path, actualArgv, {
11
+ execArgv,
12
+ stdio: 'pipe',
13
+ serviceName: name,
14
+ })
15
+ // @ts-ignore
16
+ childProcess.stdout.pipe(process.stdout)
17
+ const { type, event, stdout, stderr } = await GetFirstUtilityProcessEvent.getFirstUtilityProcessEvent(childProcess)
18
+ if (type === FirstNodeWorkerEventType.Exit) {
19
+ throw new IpcError(`Utility process exited before ipc connection was established`, stdout, stderr)
20
+ }
21
+ // @ts-ignore
22
+ childProcess.stderr.pipe(process.stderr)
23
+ return childProcess
24
+ }
25
+
26
+ export const wrap = (process) => {
27
+ return {
28
+ process,
29
+ on(event, listener) {
30
+ this.process.on(event, listener)
31
+ },
32
+ send(message) {
33
+ this.process.postMessage(message)
34
+ },
35
+ sendAndTransfer(message, transfer) {
36
+ Assert.array(transfer)
37
+ this.process.postMessage(message, transfer)
38
+ },
39
+ dispose() {
40
+ this.process.kill()
41
+ },
42
+ }
43
+ }
@@ -0,0 +1,37 @@
1
+ import { fork } from 'node:child_process'
2
+ import * as Assert from '../Assert/Assert.js'
3
+
4
+ /**
5
+ *
6
+ * @param {{path:string, argv?:string[], env?:any, execArgv?:string[], stdio?:'inherit'}} param0
7
+ * @returns
8
+ */
9
+ export const create = async ({ path, argv = [], env = process.env, execArgv = [], stdio = 'inherit' }) => {
10
+ Assert.string(path)
11
+ const actualArgv = ['--ipc-type=node-forked-process', ...argv]
12
+ const childProcess = fork(path, actualArgv, {
13
+ env,
14
+ execArgv,
15
+ stdio,
16
+ })
17
+ return childProcess
18
+ }
19
+
20
+ export const wrap = (childProcess) => {
21
+ return {
22
+ childProcess,
23
+ on(event, listener) {
24
+ this.childProcess.on(event, listener)
25
+ },
26
+ send(message) {
27
+ this.childProcess.send(message)
28
+ },
29
+ sendAndTransfer(message, transfer) {
30
+ throw new Error('transfer is not supported')
31
+ },
32
+ dispose() {
33
+ this.childProcess.kill()
34
+ },
35
+ pid: childProcess.pid,
36
+ }
37
+ }
@@ -0,0 +1,49 @@
1
+ import { Worker } from 'node:worker_threads'
2
+ import * as Assert from '../Assert/Assert.js'
3
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
4
+ import * as GetFirstNodeWorkerEvent from '../GetFirstNodeWorkerEvent/GetFirstNodeWorkerEvent.js'
5
+ import { IpcError } from '../IpcError/IpcError.js'
6
+
7
+ export const create = async ({ path, argv = [], env = process.env, execArgv = [] }) => {
8
+ Assert.string(path)
9
+ const actualArgv = ['--ipc-type=node-worker', ...argv]
10
+ const actualEnv = {
11
+ ...env,
12
+ ELECTRON_RUN_AS_NODE: '1',
13
+ }
14
+ const worker = new Worker(path, {
15
+ argv: actualArgv,
16
+ env: actualEnv,
17
+ execArgv,
18
+ })
19
+ const { type, event } = await GetFirstNodeWorkerEvent.getFirstNodeWorkerEvent(worker)
20
+ if (type === FirstNodeWorkerEventType.Exit) {
21
+ throw new IpcError(`Worker exited before ipc connection was established`)
22
+ }
23
+ if (type === FirstNodeWorkerEventType.Error) {
24
+ throw new IpcError(`Worker threw an error before ipc connection was established: ${event}`)
25
+ }
26
+ if (event !== 'ready') {
27
+ throw new IpcError('unexpected first message from worker')
28
+ }
29
+ return worker
30
+ }
31
+
32
+ export const wrap = (worker) => {
33
+ return {
34
+ worker,
35
+ on(event, listener) {
36
+ this.worker.on(event, listener)
37
+ },
38
+ send(message) {
39
+ this.worker.postMessage(message)
40
+ },
41
+ sendAndTransfer(message, transfer) {
42
+ Assert.array(transfer)
43
+ this.worker.postMessage(message, transfer)
44
+ },
45
+ dispose() {
46
+ this.worker.terminate()
47
+ },
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ export const isMessagePortMain = (value) => {
2
+ return value && value.constructor && value.constructor.name === 'MessagePortMain'
3
+ }
@@ -0,0 +1,5 @@
1
+ import { WebSocket } from 'ws'
2
+
3
+ export const isWebSocketOpen = (webSocket) => {
4
+ return webSocket.readyState === WebSocket.OPEN
5
+ }
@@ -0,0 +1,5 @@
1
+ import * as Character from '../Character/Character.js'
2
+
3
+ export const joinLines = (lines) => {
4
+ return lines.join(Character.NewLine)
5
+ }
@@ -0,0 +1,19 @@
1
+ export const withResolvers = () => {
2
+ /**
3
+ * @type {any}
4
+ */
5
+ let _resolve
6
+ /**
7
+ * @type {any}
8
+ */
9
+ let _reject
10
+ const promise = new Promise((resolve, reject) => {
11
+ _resolve = resolve
12
+ _reject = reject
13
+ })
14
+ return {
15
+ resolve: _resolve,
16
+ reject: _reject,
17
+ promise,
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ import * as Character from '../Character/Character.js'
2
+
3
+ export const splitLines = (lines) => {
4
+ return lines.split(Character.NewLine)
5
+ }
@@ -0,0 +1 @@
1
+ export * from '@lvce-editor/verror'
@@ -0,0 +1,5 @@
1
+ import { WebSocket } from 'ws'
2
+
3
+ export const Open = WebSocket.OPEN
4
+
5
+ export const Closed = WebSocket.CLOSED
@@ -0,0 +1,7 @@
1
+ export const serialize = (message) => {
2
+ return JSON.stringify(message)
3
+ }
4
+
5
+ export const deserialize = (message) => {
6
+ return JSON.parse(message.toString())
7
+ }
@@ -0,0 +1 @@
1
+ export * from '@lvce-editor/web-socket-server'
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@lvce-editor/ipc",
3
+ "version": "1.0.0",
4
+ "description": "Inter Process Communication for Lvce Editor",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "keywords": [
8
+ "ipc"
9
+ ],
10
+ "author": "Lvce Editor",
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/lvce-editor/ipc.git"
15
+ },
16
+ "dependencies": {
17
+ "@lvce-editor/assert": "^1.2.0",
18
+ "@lvce-editor/verror": "^1.1.2",
19
+ "@lvce-editor/web-socket-server": "^1.1.0"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ }
24
+ }