@nonstrict/recordkit 0.2.1 → 0.3.0-alpha.1
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.
- package/bin/.gitkeep +0 -0
- package/bin/recordkit-rpc +0 -0
- package/build.sh +32 -0
- package/jest.config.js +16 -0
- package/{dist → out}/IpcRecordKit.js +1 -0
- package/out/IpcRecordKit.js.map +1 -0
- package/{dist → out}/NonstrictRPC.js +1 -0
- package/out/NonstrictRPC.js.map +1 -0
- package/out/RecordKit.d.ts +50 -0
- package/out/RecordKit.js +48 -0
- package/out/RecordKit.js.map +1 -0
- package/out/Recorder.d.ts +59 -0
- package/{dist/RecordingSession.js → out/Recorder.js} +21 -5
- package/out/Recorder.js.map +1 -0
- package/{dist → out}/finalizationRegistry.js +1 -0
- package/out/finalizationRegistry.js.map +1 -0
- package/{dist → out}/index.cjs +54 -13
- package/out/index.cjs.map +1 -0
- package/out/index.d.ts +3 -0
- package/{dist → out}/index.js +1 -0
- package/out/index.js.map +1 -0
- package/package.json +9 -11
- package/rollup.config.js +11 -0
- package/src/IpcRecordKit.ts +46 -0
- package/src/NonstrictRPC.test.ts +98 -0
- package/src/NonstrictRPC.ts +308 -0
- package/src/RecordKit.ts +99 -0
- package/src/Recorder.ts +113 -0
- package/src/__snapshots__/NonstrictRPC.test.ts.snap +24 -0
- package/src/finalizationRegistry.ts +2 -0
- package/src/index.ts +3 -0
- package/tsconfig.json +109 -0
- package/typedoc.json +6 -0
- package/dist/RecordKit.d.ts +0 -34
- package/dist/RecordKit.js +0 -22
- package/dist/RecordingSession.d.ts +0 -70
- package/dist/index.d.ts +0 -1
- /package/{dist → out}/IpcRecordKit.d.ts +0 -0
- /package/{dist → out}/NonstrictRPC.d.ts +0 -0
- /package/{dist → out}/finalizationRegistry.d.ts +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NSRPC, NSRPCPerformClosureRequest } from './NonstrictRPC.js';
|
|
2
|
+
import v8 from 'v8';
|
|
3
|
+
|
|
4
|
+
// const finalizationRegistry = new FinalizationRegistry(async (destructor) => { console.log('==> ', destructor) })
|
|
5
|
+
|
|
6
|
+
// finalizationRegistry.register({}, 'CHECK');
|
|
7
|
+
// eval('%CollectGarbage(true)');
|
|
8
|
+
|
|
9
|
+
describe('NonstrictRPC', () => {
|
|
10
|
+
it('needs tests', () => { });
|
|
11
|
+
|
|
12
|
+
// beforeAll(() => {
|
|
13
|
+
// v8.setFlagsFromString('--allow-natives-syntax');
|
|
14
|
+
// });
|
|
15
|
+
|
|
16
|
+
// describe('initialize', () => {
|
|
17
|
+
// it('sends init request w/parameters', (done) => {
|
|
18
|
+
// const rpc = new NSRPC((data) => {
|
|
19
|
+
// const message = JSON.parse(data)
|
|
20
|
+
// expect(message).toMatchSnapshot({ id: expect.any(String) });
|
|
21
|
+
// done()
|
|
22
|
+
// });
|
|
23
|
+
|
|
24
|
+
// rpc.initialize({
|
|
25
|
+
// target: 'TheTarget',
|
|
26
|
+
// type: 'TheType',
|
|
27
|
+
// params: { aParameter: 'TheValue' },
|
|
28
|
+
// lifecycle: {}
|
|
29
|
+
// });
|
|
30
|
+
// });
|
|
31
|
+
|
|
32
|
+
// it('sends init request w/o parameters', (done) => {
|
|
33
|
+
// const rpc = new NSRPC((data) => {
|
|
34
|
+
// const message = JSON.parse(data)
|
|
35
|
+
// expect(message).toMatchSnapshot({ id: expect.any(String) });
|
|
36
|
+
// done()
|
|
37
|
+
// });
|
|
38
|
+
|
|
39
|
+
// rpc.initialize({
|
|
40
|
+
// target: 'TheTarget',
|
|
41
|
+
// type: 'TheType',
|
|
42
|
+
// lifecycle: {}
|
|
43
|
+
// });
|
|
44
|
+
// });
|
|
45
|
+
|
|
46
|
+
// it('sends release request', async () => {
|
|
47
|
+
// const rpc = new NSRPC((data) => {
|
|
48
|
+
// const message = JSON.parse(data)
|
|
49
|
+
// console.log(message)
|
|
50
|
+
// // if (message.procedure === 'release') {
|
|
51
|
+
// // expect(message).toMatchSnapshot({ id: expect.any(String) });
|
|
52
|
+
// // done()
|
|
53
|
+
// // }
|
|
54
|
+
// });
|
|
55
|
+
|
|
56
|
+
// async function scope() {
|
|
57
|
+
// const foo = {}
|
|
58
|
+
// await rpc.initialize({
|
|
59
|
+
// target: 'TheTarget',
|
|
60
|
+
// type: 'TheType',
|
|
61
|
+
// lifecycle: foo
|
|
62
|
+
// });
|
|
63
|
+
// finalizationRegistry.register(foo, 'CHECK');
|
|
64
|
+
// }
|
|
65
|
+
|
|
66
|
+
// await scope();
|
|
67
|
+
// eval("%CollectGarbage(true)");
|
|
68
|
+
// });
|
|
69
|
+
// });
|
|
70
|
+
|
|
71
|
+
// describe('registerClosure', () => {
|
|
72
|
+
// it('should do something', (done) => {
|
|
73
|
+
// const rpc = new NSRPC((data) => {
|
|
74
|
+
// expect(JSON.parse(data)).toStrictEqual({status:200,result:{test:"test"},nsrpc:1,id:"req_id"})
|
|
75
|
+
// done()
|
|
76
|
+
// });
|
|
77
|
+
|
|
78
|
+
// const target = rpc.registerClosure({
|
|
79
|
+
// handler: (params) => { params },
|
|
80
|
+
// prefix: 'test',
|
|
81
|
+
// lifecycle: {}
|
|
82
|
+
// });
|
|
83
|
+
|
|
84
|
+
// const rpcMessage: NSRPCPerformClosureRequest = {
|
|
85
|
+
// nsrpc: 1,
|
|
86
|
+
// id: 'req_id',
|
|
87
|
+
// procedure: 'perform',
|
|
88
|
+
// target,
|
|
89
|
+
// params: { test: 'test' }
|
|
90
|
+
// }
|
|
91
|
+
// rpc.receive(JSON.stringify(rpcMessage));
|
|
92
|
+
// });x
|
|
93
|
+
// });
|
|
94
|
+
|
|
95
|
+
// function timeout(ms: number) {
|
|
96
|
+
// return new Promise<void>(resolve => setTimeout(resolve, ms));
|
|
97
|
+
// }
|
|
98
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { finalizationRegistry } from "./finalizationRegistry.js";
|
|
3
|
+
|
|
4
|
+
type NSRPCMessage = NSRPCRequest | NSRPCResponse;
|
|
5
|
+
|
|
6
|
+
/* Request types */
|
|
7
|
+
|
|
8
|
+
type NSRPCRequest =
|
|
9
|
+
| NSRPCInitializationRequest
|
|
10
|
+
| NSRPCPerformStaticMethodRequest
|
|
11
|
+
| NSRPCPerformMethodRequest
|
|
12
|
+
| NSRPCPerformClosureRequest
|
|
13
|
+
| NSRPCReleaseRequest;
|
|
14
|
+
|
|
15
|
+
interface NSRPCInitializationRequest {
|
|
16
|
+
nsrpc: number;
|
|
17
|
+
id: string;
|
|
18
|
+
procedure: "init";
|
|
19
|
+
target: string;
|
|
20
|
+
type: string;
|
|
21
|
+
params?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface NSRPCPerformStaticMethodRequest {
|
|
25
|
+
nsrpc: number;
|
|
26
|
+
id?: string;
|
|
27
|
+
procedure: "perform";
|
|
28
|
+
type: string;
|
|
29
|
+
action: string;
|
|
30
|
+
params?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface NSRPCPerformMethodRequest {
|
|
34
|
+
nsrpc: number;
|
|
35
|
+
id?: string;
|
|
36
|
+
procedure: "perform";
|
|
37
|
+
target: string;
|
|
38
|
+
action: string;
|
|
39
|
+
params?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NSRPCPerformClosureRequest {
|
|
43
|
+
nsrpc: number;
|
|
44
|
+
id?: string;
|
|
45
|
+
procedure: "perform";
|
|
46
|
+
target: string;
|
|
47
|
+
params?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface NSRPCReleaseRequest {
|
|
51
|
+
nsrpc: number;
|
|
52
|
+
id: string;
|
|
53
|
+
procedure: "release";
|
|
54
|
+
target: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type NSRPCRequestBody =
|
|
58
|
+
| NSRPCInitializationRequestBody
|
|
59
|
+
| NSRPCPerformStaticMethodRequestBody
|
|
60
|
+
| NSRPCPerformMethodRequestBody
|
|
61
|
+
| NSRPCPerformClosureRequestBody
|
|
62
|
+
| NSRPCReleaseRequestBody;
|
|
63
|
+
type NSRPCInitializationRequestBody = Omit<
|
|
64
|
+
NSRPCInitializationRequest,
|
|
65
|
+
"nsrpc" | "id"
|
|
66
|
+
>;
|
|
67
|
+
type NSRPCPerformMethodRequestBody = Omit<
|
|
68
|
+
NSRPCPerformMethodRequest,
|
|
69
|
+
"nsrpc" | "id"
|
|
70
|
+
>;
|
|
71
|
+
type NSRPCPerformStaticMethodRequestBody = Omit<
|
|
72
|
+
NSRPCPerformStaticMethodRequest,
|
|
73
|
+
"nsrpc" | "id"
|
|
74
|
+
>;
|
|
75
|
+
type NSRPCPerformClosureRequestBody = Omit<
|
|
76
|
+
NSRPCPerformClosureRequest,
|
|
77
|
+
"nsrpc" | "id"
|
|
78
|
+
>;
|
|
79
|
+
type NSRPCReleaseRequestBody = Omit<NSRPCReleaseRequest, "nsrpc" | "id">;
|
|
80
|
+
|
|
81
|
+
/* Response types */
|
|
82
|
+
|
|
83
|
+
type NSRPCResponse = NSRPCSuccesfulResponse | NSRPCErrorResponse;
|
|
84
|
+
|
|
85
|
+
interface NSRPCSuccesfulResponse {
|
|
86
|
+
nsrpc: number;
|
|
87
|
+
id: string;
|
|
88
|
+
status: 200;
|
|
89
|
+
result?: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface NSRPCErrorResponse {
|
|
93
|
+
nsrpc: number;
|
|
94
|
+
id: string;
|
|
95
|
+
status: Exclude<number, 200>;
|
|
96
|
+
error: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type NSRPCResponseBody = NSRPCSuccesfulResponseBody | NSRPCErrorResponseBody;
|
|
100
|
+
type NSRPCSuccesfulResponseBody = Omit<NSRPCSuccesfulResponse, "nsrpc" | "id">;
|
|
101
|
+
type NSRPCErrorResponseBody = Omit<NSRPCErrorResponse, "nsrpc" | "id">;
|
|
102
|
+
|
|
103
|
+
interface PromiseSource {
|
|
104
|
+
resolve: (
|
|
105
|
+
value: unknown
|
|
106
|
+
) => void;
|
|
107
|
+
reject: (reason?: any) => void;
|
|
108
|
+
}
|
|
109
|
+
type ClosureTarget = (
|
|
110
|
+
params: Record<string, unknown>
|
|
111
|
+
) => Record<string, unknown> | void;
|
|
112
|
+
|
|
113
|
+
export class NSRPC {
|
|
114
|
+
private readonly send: (data: string) => void;
|
|
115
|
+
|
|
116
|
+
private responseHandlers: Map<string, PromiseSource> = new Map();
|
|
117
|
+
private closureTargets: Map<string, ClosureTarget> = new Map();
|
|
118
|
+
|
|
119
|
+
constructor(send: (data: string) => void) {
|
|
120
|
+
this.send = send;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
receive(data: string) {
|
|
124
|
+
// TODO: For now we just assume the message is a valid NSRPC message, but we should:
|
|
125
|
+
// - Handle invalid JSON comming in
|
|
126
|
+
// - Check if the nsrpc property is set to a number in the range of 1..<2
|
|
127
|
+
// - Validate the message against the defined interfaces above
|
|
128
|
+
const message = JSON.parse(data) as NSRPCMessage;
|
|
129
|
+
if ("status" in message) {
|
|
130
|
+
// This is a response, dispatch it so it can be handled
|
|
131
|
+
const responseHandler = this.responseHandlers.get(message.id);
|
|
132
|
+
this.responseHandlers.delete(message.id);
|
|
133
|
+
if (responseHandler === undefined) {
|
|
134
|
+
// TODO: Got a response for a request we don't know about, log this
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if ("error" in message) {
|
|
139
|
+
responseHandler.reject(message.error);
|
|
140
|
+
} else {
|
|
141
|
+
responseHandler.resolve(message.result);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// This is a request
|
|
145
|
+
const responseBody = this.handleRequest(message);
|
|
146
|
+
if (responseBody !== undefined) {
|
|
147
|
+
this.sendResponse(message.id, responseBody);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Sending helpers */
|
|
153
|
+
|
|
154
|
+
private sendMessage(message: NSRPCMessage) {
|
|
155
|
+
this.send(JSON.stringify(message));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private sendResponse(id: string | undefined, response: NSRPCResponseBody) {
|
|
159
|
+
if (id === undefined) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.sendMessage({ ...response, nsrpc: 1, id });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async sendRequest(
|
|
166
|
+
request: NSRPCRequestBody
|
|
167
|
+
): Promise<unknown> {
|
|
168
|
+
const id = "req_" + randomUUID();
|
|
169
|
+
const response = new Promise((resolve, reject) => {
|
|
170
|
+
this.responseHandlers.set(id, { resolve, reject });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.sendMessage({ ...request, nsrpc: 1, id });
|
|
174
|
+
|
|
175
|
+
return response;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Request handling */
|
|
179
|
+
|
|
180
|
+
private handleRequest(request: NSRPCRequest): NSRPCResponseBody | undefined {
|
|
181
|
+
switch (request.procedure) {
|
|
182
|
+
case "init":
|
|
183
|
+
return {
|
|
184
|
+
status: 501,
|
|
185
|
+
error: {
|
|
186
|
+
debugDescription: "Init procedure not implemented.",
|
|
187
|
+
userMessage:
|
|
188
|
+
"Failed to communicate with external process. (Procedure not implemented)",
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
case "perform":
|
|
192
|
+
if ("action" in request) {
|
|
193
|
+
return {
|
|
194
|
+
status: 501,
|
|
195
|
+
error: {
|
|
196
|
+
debugDescription:
|
|
197
|
+
"Perform procedure for (static) methods not implemented.",
|
|
198
|
+
userMessage:
|
|
199
|
+
"Failed to communicate with external process. (Procedure not implemented)",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
} else {
|
|
203
|
+
return this.handleClosureRequest(request);
|
|
204
|
+
}
|
|
205
|
+
case "release":
|
|
206
|
+
return {
|
|
207
|
+
status: 501,
|
|
208
|
+
error: {
|
|
209
|
+
debugDescription: "Release procedure not implemented.",
|
|
210
|
+
userMessage:
|
|
211
|
+
"Failed to communicate with external process. (Procedure not implemented)",
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private handleClosureRequest(
|
|
218
|
+
request: NSRPCPerformClosureRequest
|
|
219
|
+
): NSRPCResponseBody {
|
|
220
|
+
const handler = this.closureTargets.get(request.target);
|
|
221
|
+
if (handler === undefined) {
|
|
222
|
+
return {
|
|
223
|
+
status: 404,
|
|
224
|
+
error: {
|
|
225
|
+
debugDescription: `Perform target '${request.target}' not found.`,
|
|
226
|
+
userMessage:
|
|
227
|
+
"Failed to communicate with external process. (Target not found)",
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const rawresult = handler(request.params ?? {});
|
|
234
|
+
const result = rawresult === undefined ? undefined : rawresult;
|
|
235
|
+
return {
|
|
236
|
+
status: 200,
|
|
237
|
+
result,
|
|
238
|
+
};
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return {
|
|
241
|
+
status: 202,
|
|
242
|
+
// TODO: Would be good to have an error type that we can throw that fills these fields more specifically. (But for now it doesn't matter since this is just communicated back the the CLI and not to the user.)
|
|
243
|
+
error: {
|
|
244
|
+
debugDescription: `${error}`,
|
|
245
|
+
userMessage: "Handler failed to perform request.",
|
|
246
|
+
underlyingError: error,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Perform remote procedures */
|
|
253
|
+
|
|
254
|
+
async initialize(args: {
|
|
255
|
+
target: string;
|
|
256
|
+
type: string;
|
|
257
|
+
params?: Record<string, unknown>;
|
|
258
|
+
lifecycle: Object;
|
|
259
|
+
}) {
|
|
260
|
+
const target = args.target
|
|
261
|
+
finalizationRegistry.register(args.lifecycle, async () => {
|
|
262
|
+
await this.release(target);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
await this.sendRequest({
|
|
266
|
+
target: args.target,
|
|
267
|
+
type: args.type,
|
|
268
|
+
params: args.params,
|
|
269
|
+
procedure: "init",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async perform(body: { // TODO: Add support for static method calls.
|
|
274
|
+
type?: string;
|
|
275
|
+
target?: string;
|
|
276
|
+
action?: string;
|
|
277
|
+
params?: Record<string, unknown>;
|
|
278
|
+
}): Promise<unknown> {
|
|
279
|
+
return await this.sendRequest({
|
|
280
|
+
...body,
|
|
281
|
+
procedure: "perform",
|
|
282
|
+
} as any);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private async release(target: string) {
|
|
286
|
+
await this.sendRequest({
|
|
287
|
+
procedure: "release",
|
|
288
|
+
target,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Register locally available targets/actions */
|
|
293
|
+
|
|
294
|
+
registerClosure(options: {
|
|
295
|
+
handler: ClosureTarget;
|
|
296
|
+
lifecycle: Object;
|
|
297
|
+
prefix: string;
|
|
298
|
+
}): string {
|
|
299
|
+
const target = `target_${options.prefix}_${randomUUID()}`;
|
|
300
|
+
this.closureTargets.set(target, options.handler);
|
|
301
|
+
|
|
302
|
+
finalizationRegistry.register(options.lifecycle, () => {
|
|
303
|
+
this.closureTargets.delete(target);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return target;
|
|
307
|
+
}
|
|
308
|
+
}
|
package/src/RecordKit.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { IpcRecordKit } from "./IpcRecordKit.js";
|
|
2
|
+
import { Recorder, RecorderSchema, AbortReason } from "./Recorder.js";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
type AuthorizationStatus =
|
|
6
|
+
| 'notDetermined' // The user has not yet made a choice.
|
|
7
|
+
| 'restricted' // The user cannot change the client's status, possibly due to active restrictions such as parental controls being in place.
|
|
8
|
+
| 'denied' // The user explicitly denied access to the hardware supporting a media type for the client.
|
|
9
|
+
| 'authorized' // Application is authorized to access the hardware.
|
|
10
|
+
|
|
11
|
+
export interface Window {
|
|
12
|
+
id: number; // UInt32
|
|
13
|
+
title?: string;
|
|
14
|
+
frame: Bounds
|
|
15
|
+
level: number // Int
|
|
16
|
+
application_process_id?: number // Int32
|
|
17
|
+
application_name?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Camera {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
model_id: string;
|
|
24
|
+
manufacturer: string;
|
|
25
|
+
availability: 'available' | 'lidClosed' | 'unknownSuspended'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Microphone {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
model_id: string;
|
|
32
|
+
manufacturer: string;
|
|
33
|
+
availability: 'available' | 'lidClosed' | 'unknownSuspended'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Bounds {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class RecordKit {
|
|
44
|
+
private ipcRecordKit = new IpcRecordKit()
|
|
45
|
+
|
|
46
|
+
async initialize(args: { rpcBinaryPath: string, fallbackToNodeModules: boolean, logRpcMessages?: boolean }): Promise<void> {
|
|
47
|
+
let rpcBinaryPath = args.rpcBinaryPath
|
|
48
|
+
if (!args.fallbackToNodeModules) {
|
|
49
|
+
if (!existsSync(rpcBinaryPath)) {
|
|
50
|
+
console.log('Falling back to RPC binary from node_modules, no file at given RPC binary path.')
|
|
51
|
+
rpcBinaryPath = rpcBinaryPath.replace('node_modules/electron/dist/Electron.app/Contents/Resources', 'node_modules/@nonstrict/recordkit/bin')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this.ipcRecordKit.initialize(rpcBinaryPath, args.logRpcMessages)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getWindows(): Promise<Window[]> {
|
|
59
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'Recorder', action: 'getWindows' }) as Window[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getCameras(): Promise<Camera[]> {
|
|
63
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'Recorder', action: 'getCameras' }) as Camera[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getMicrophones(): Promise<Microphone[]> {
|
|
67
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'Recorder', action: 'getMicrophones' }) as Microphone[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getCameraAuthorizationStatus(): Promise<AuthorizationStatus> {
|
|
71
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'AuthorizationStatus', action: 'getCameraAuthorizationStatus' }) as AuthorizationStatus
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getMicrophoneAuthorizationStatus(): Promise<AuthorizationStatus> {
|
|
75
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'AuthorizationStatus', action: 'getMicrophoneAuthorizationStatus' }) as AuthorizationStatus
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getScreenRecordingAccess(): Promise<boolean> {
|
|
79
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'AuthorizationStatus', action: 'getScreenRecordingAccess' }) as boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async requestCameraAccess(): Promise<boolean> {
|
|
83
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'AuthorizationStatus', action: 'requestCameraAccess' }) as boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async requestMicrophoneAccess(): Promise<boolean> {
|
|
87
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'AuthorizationStatus', action: 'requestMicrophoneAccess' }) as boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async requestScreenRecordingAccess(): Promise<void> {
|
|
91
|
+
return await this.ipcRecordKit.nsrpc.perform({ type: 'AuthorizationStatus', action: 'requestScreenRecordingAccess' }) as void
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async createRecorder(schema: RecorderSchema): Promise<Recorder> {
|
|
95
|
+
return Recorder.newInstance(this.ipcRecordKit.nsrpc, schema);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export let recordkit = new RecordKit();
|
package/src/Recorder.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { finalizationRegistry } from "./finalizationRegistry.js";
|
|
3
|
+
import { NSRPC } from "./NonstrictRPC.js";
|
|
4
|
+
import { EventEmitter } from "stream";
|
|
5
|
+
import { Camera, Microphone, Window } from "./RecordKit.js";
|
|
6
|
+
|
|
7
|
+
export interface RecorderSchema {
|
|
8
|
+
output_directory?: string
|
|
9
|
+
items: RecorderSchemaItem[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type RecorderSchemaItem =
|
|
13
|
+
| WebcamSchema
|
|
14
|
+
| WindowBasedCropSchema
|
|
15
|
+
|
|
16
|
+
export interface WebcamSchema {
|
|
17
|
+
type: 'webcam'
|
|
18
|
+
filename?: string
|
|
19
|
+
camera: Camera | string
|
|
20
|
+
microphone: Microphone | string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WindowBasedCropSchema {
|
|
24
|
+
type: 'windowBasedCrop'
|
|
25
|
+
filename?: string
|
|
26
|
+
window: Window | number // UInt32
|
|
27
|
+
shows_cursor?: boolean
|
|
28
|
+
mouse_events?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type AbortReason =
|
|
32
|
+
| { reason: 'userStopped'; result: RecordingResult; }
|
|
33
|
+
| { reason: 'interrupted'; result: RecordingResult; error: RecordKitError; }
|
|
34
|
+
| { reason: 'failed'; error: RecordKitError; }
|
|
35
|
+
|
|
36
|
+
export interface RecordingResult {
|
|
37
|
+
url: string
|
|
38
|
+
info: BundleInfo
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RecordKitError {
|
|
42
|
+
message?: string // Message describing the problem and possible recovery options, intended to be shown directly to the end-user.
|
|
43
|
+
error_group: string // Generic title, used for grouping related errors
|
|
44
|
+
debug_description: string // Detailed technical description of this error, used in debugging
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BundleInfo {
|
|
48
|
+
version: 1,
|
|
49
|
+
files: {
|
|
50
|
+
type: 'screen' | 'webcam' | 'mouse'
|
|
51
|
+
filename: string
|
|
52
|
+
}[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class Recorder extends EventEmitter {
|
|
56
|
+
private readonly rpc: NSRPC;
|
|
57
|
+
private readonly target: string;
|
|
58
|
+
|
|
59
|
+
static async newInstance(rpc: NSRPC, schema: RecorderSchema): Promise<Recorder> {
|
|
60
|
+
const target = 'Recorder_' + randomUUID();
|
|
61
|
+
const object = new Recorder(rpc, target);
|
|
62
|
+
|
|
63
|
+
schema.items.forEach(item => {
|
|
64
|
+
if (item.type == 'webcam') {
|
|
65
|
+
if (typeof item.camera != 'string') {
|
|
66
|
+
item.camera = item.camera.id
|
|
67
|
+
}
|
|
68
|
+
if (typeof item.microphone != 'string') {
|
|
69
|
+
item.microphone = item.microphone.id
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (item.type == 'windowBasedCrop') {
|
|
73
|
+
if (typeof item.window != 'number') {
|
|
74
|
+
item.window = item.window.id
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const weakRefObject = new WeakRef(object);
|
|
80
|
+
const onAbortInstance = rpc.registerClosure({
|
|
81
|
+
handler: (params) => { weakRefObject.deref()?.emit('abort', params.reason as AbortReason) },
|
|
82
|
+
prefix: 'Recorder.onAbort',
|
|
83
|
+
lifecycle: object
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await rpc.initialize({
|
|
87
|
+
target,
|
|
88
|
+
type: 'Recorder',
|
|
89
|
+
params: { schema, onAbortInstance },
|
|
90
|
+
lifecycle: object
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return object
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
constructor(rpc: NSRPC, target: string) {
|
|
97
|
+
super();
|
|
98
|
+
this.rpc = rpc;
|
|
99
|
+
this.target = target;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async prepare(): Promise<void> {
|
|
103
|
+
await this.rpc.perform({ target: this.target, action: 'prepare' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async start(): Promise<void> {
|
|
107
|
+
await this.rpc.perform({ target: this.target, action: 'start' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async stop(): Promise<RecordingResult> {
|
|
111
|
+
return await this.rpc.perform({ target: this.target, action: 'stop' }) as RecordingResult;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`NonstrictRPC initialize sends init request w/o parameters 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"id": Any<String>,
|
|
6
|
+
"nsrpc": 1,
|
|
7
|
+
"procedure": "init",
|
|
8
|
+
"target": "TheTarget",
|
|
9
|
+
"type": "TheType",
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
exports[`NonstrictRPC initialize sends init request w/parameters 1`] = `
|
|
14
|
+
{
|
|
15
|
+
"id": Any<String>,
|
|
16
|
+
"nsrpc": 1,
|
|
17
|
+
"params": {
|
|
18
|
+
"aParameter": "TheValue",
|
|
19
|
+
},
|
|
20
|
+
"procedure": "init",
|
|
21
|
+
"target": "TheTarget",
|
|
22
|
+
"type": "TheType",
|
|
23
|
+
}
|
|
24
|
+
`;
|
package/src/index.ts
ADDED