-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add ipc wrappers #261
Changes from 4 commits
d8b0996
7b129c0
4be1303
9313f48
077edbf
79252cb
9f6a7a5
76e0d63
c3139c6
32c5a01
e9cdec4
c7fe50f
e067dc3
5f23d77
892b653
71a8398
fd58298
612f068
ada1aba
77cdf54
b7e98da
687c2bc
d8ebfc2
be7cb48
d976081
1fa204a
57456b7
fc86445
5119426
368d11c
08ae6a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// @ts-check | ||
import { createClient } from 'rpc-reflector' | ||
import { SubChannel } from './sub-channel.js' | ||
|
||
/** | ||
* @param {import('rpc-reflector/client.js').MessagePortLike} messagePort | ||
* @returns {import('rpc-reflector/client.js').ClientApi<import('../mapeo-manager.js').MapeoManager>} | ||
*/ | ||
export function createMapeoClient(messagePort) { | ||
// TODO: LRU cache? | ||
/** @type {Map<import('../types.js').ProjectPublicId, import('rpc-reflector/client.js').ClientApi<import('../mapeo-project.js').MapeoProject>>} */ | ||
const existingProjectClients = new Map() | ||
|
||
const managerChannel = new SubChannel(messagePort, '@@manager') | ||
|
||
/** @type {import('rpc-reflector').ClientApi<import('../mapeo-manager.js').MapeoManager>} */ | ||
const managerClient = createClient(managerChannel) | ||
|
||
const client = new Proxy(managerClient, { | ||
get(target, prop, receiver) { | ||
if (prop === 'getProject') { | ||
return createProjectClient | ||
} else { | ||
return Reflect.get(target, prop, receiver) | ||
} | ||
|
||
/** | ||
* @param {import('../types.js').ProjectPublicId} projectPublicId | ||
* @returns {Promise<import('rpc-reflector/client.js').ClientApi<import('../mapeo-project.js').MapeoProject>>} | ||
*/ | ||
function createProjectClient(projectPublicId) { | ||
const existingClient = existingProjectClients.get(projectPublicId) | ||
|
||
if (existingClient) return Promise.resolve(existingClient) | ||
|
||
achou11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const projectChannel = new SubChannel(messagePort, projectPublicId) | ||
|
||
/** @type {import('rpc-reflector').ClientApi<import('../mapeo-project.js').MapeoProject>} */ | ||
const projectClient = new Proxy(createClient(projectChannel), { | ||
get(target, prop, receiver) { | ||
if (prop === 'then') { | ||
return projectClient | ||
} | ||
return Reflect.get(target, prop, receiver) | ||
}, | ||
}) | ||
|
||
existingProjectClients.set(projectPublicId, projectClient) | ||
|
||
return Promise.resolve(projectClient) | ||
} | ||
}, | ||
}) | ||
|
||
return client | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// @ts-check | ||
import { createServer } from 'rpc-reflector' | ||
import { SubChannel } from './sub-channel.js' | ||
|
||
/** | ||
* @param {import('../mapeo-manager.js').MapeoManager} manager | ||
* @param {import('rpc-reflector/server.js').MessagePortLike} messagePort | ||
achou11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
export function createMapeoServer(manager, messagePort) { | ||
// TODO: LRU? project.close() after time without use? | ||
/** @type {Map<string, { close: () => void, project: import('../mapeo-project.js').MapeoProject }>}*/ | ||
const existing = new Map() | ||
|
||
const managerChannel = new SubChannel(messagePort, '@@manager') | ||
|
||
const managerServer = createServer(manager, managerChannel) | ||
|
||
messagePort.on('message', async (payload) => { | ||
// TODO: figure out better way to know that this is the project public id | ||
const id = payload?.id | ||
if (typeof id !== 'string' || id === '@@manager') return | ||
achou11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (existing.has(id)) return | ||
|
||
const projectChannel = new SubChannel(messagePort, id) | ||
|
||
const project = await manager.getProject( | ||
gmaclennan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** @type {import('../types.js').ProjectPublicId} */ (id) | ||
) | ||
achou11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const { close } = createServer(project, projectChannel) | ||
|
||
achou11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
projectChannel.emit('message', payload.message) | ||
|
||
existing.set(id, { close, project }) | ||
}) | ||
|
||
return managerServer | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// @ts-check | ||
import { TypedEmitter } from 'tiny-typed-emitter' | ||
|
||
/** | ||
* @typedef {Object} Events | ||
* @property {(message: any) => void} message | ||
*/ | ||
|
||
/** | ||
* @extends {TypedEmitter<Events>} | ||
*/ | ||
export class SubChannel extends TypedEmitter { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wondering if i should avoid using TypedEmitter for this so that this can run in a browser environment as well (it's used for both server and client). I havea local implementation that works using EventTarget based on this helpful post. one of the main differences is the spec for the
Most notably the part about synchronously invoking listeners. Not sure if the Node implementation adheres to this or if it still behaves similar to an event emitter, but wondering if this specific detail could cause problems in our case or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i guess the simpler option would be using https://github.com/primus/eventemitter3, which is what rpc-reflector uses. it's cool that the EventTarget approach works for our use case though! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed via e067dc3 (using eventemitter3) |
||
#id | ||
#messagePort | ||
|
||
/** | ||
* @param {import('rpc-reflector/server.js').MessagePortLike} messagePort Parent channel to add namespace to | ||
* @param {string} id ID for the subchannel | ||
*/ | ||
constructor(messagePort, id) { | ||
super() | ||
|
||
this.#id = id | ||
this.#messagePort = messagePort | ||
|
||
// Listen to all messages on the shared channel but only handle the relevant ones | ||
this.#messagePort.on('message', ({ id, message }) => { | ||
if (this.#id !== id) return | ||
this.emit('message', message) | ||
}) | ||
} | ||
|
||
/** | ||
* Send messages with the subchannel's ID | ||
* @param {any} message | ||
*/ | ||
postMessage(message) { | ||
this.#messagePort.postMessage({ id: this.#id, message }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized that this should probably also queue when idle (need to check regular MessagePort behaviour), but let's deal with that in a pos-MVP follow-up. It's not going to affect existing code There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created issue here: #277 |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this out of the scope of
get()
, into thecreateMapeoClient()
scope.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed via fd58298