-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.js
118 lines (94 loc) · 3.43 KB
/
client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { createClient } from 'rpc-reflector'
import pDefer from 'p-defer'
import {
MANAGER_CHANNEL_ID,
MAPEO_RPC_ID,
SubChannel,
} from './lib/sub-channel.js'
/**
* @typedef {import('rpc-reflector/client.js').ClientApi<import('@mapeo/core/dist/mapeo-project.js').MapeoProject>} MapeoProjectApi
*/
/**
* @typedef {import('rpc-reflector/client.js').ClientApi<
* Omit<
* import('@mapeo/core').MapeoManager,
* 'getProject'
* > & {
* getProject: (projectPublicId: string) => Promise<MapeoProjectApi>
* }
* >} MapeoClientApi */
const CLOSE = Symbol('close')
/**
* @param {import('./lib/sub-channel.js').MessagePortLike} messagePort
* @param {object} [opts]
* @param {number} [opts.timeout]
*
* @returns {MapeoClientApi}
*/
export function createMapeoClient(messagePort, opts = {}) {
/** @type {Map<string, Promise<import('rpc-reflector/client.js').ClientApi<import('@mapeo/core/dist/mapeo-project.js').MapeoProject>>>} */
const projectClientPromises = new Map()
const managerChannel = new SubChannel(messagePort, MANAGER_CHANNEL_ID)
const mapeoRpcChannel = new SubChannel(messagePort, MAPEO_RPC_ID)
/** @type {import('rpc-reflector').ClientApi<import('@mapeo/core').MapeoManager>} */
const managerClient = createClient(managerChannel, opts)
/** @type {import('rpc-reflector').ClientApi<import('./server.js').MapeoRpcApi>} */
const mapeoRpcClient = createClient(mapeoRpcChannel, opts)
mapeoRpcChannel.start()
managerChannel.start()
const client = new Proxy(managerClient, {
get(target, prop, receiver) {
if (prop === CLOSE) {
return async () => {
managerChannel.close()
createClient.close(managerClient)
const projectClientResults = await Promise.allSettled(
projectClientPromises.values(),
)
for (const result of projectClientResults) {
if (result.status === 'fulfilled') {
createClient.close(result.value)
}
}
}
}
if (prop === 'getProject') {
return createProjectClient
}
return Reflect.get(target, prop, receiver)
},
})
// TS can't know the type of the proxy, so we cast it in the function return
return /** @type {any} */ (client)
/**
* @param {string} projectPublicId
* @returns {Promise<MapeoProjectApi>}
*/
async function createProjectClient(projectPublicId) {
const existingClientPromise = projectClientPromises.get(projectPublicId)
if (existingClientPromise) return existingClientPromise
/** @type {import('p-defer').DeferredPromise<import('rpc-reflector/client.js').ClientApi<import('@mapeo/core/dist/mapeo-project.js').MapeoProject>>}*/
const deferred = pDefer()
projectClientPromises.set(projectPublicId, deferred.promise)
try {
await mapeoRpcClient.assertProjectExists(projectPublicId)
} catch (err) {
deferred.reject(err)
throw err
}
const projectChannel = new SubChannel(messagePort, projectPublicId)
/** @type {import('rpc-reflector').ClientApi<import('@mapeo/core/dist/mapeo-project.js').MapeoProject>} */
const projectClient = createClient(projectChannel, opts)
projectChannel.start()
deferred.resolve(projectClient)
return projectClient
}
}
/**
* @param {MapeoClientApi} client client created with `createMapeoClient`
* @returns {Promise<void>}
*/
export async function closeMapeoClient(client) {
// @ts-expect-error
return client[CLOSE]()
}