@nonstrict/recordkit 0.1.0 → 0.2.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.
@@ -1,16 +1,10 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- import { EventEmitter } from 'events';
3
- export declare class IpcRecordKit extends EventEmitter {
1
+ import { NSRPC } from "./NonstrictRPC.js";
2
+ export declare class IpcRecordKit {
4
3
  private logMessages;
5
4
  private childProcess?;
6
- private responseHandler;
7
- private methodHandler;
8
- initialize(recordKitCliPath: string, logMessages?: boolean): Promise<void>;
5
+ readonly nsrpc: NSRPC;
6
+ constructor();
7
+ initialize(recordKitRpcPath: string, logMessages?: boolean): Promise<void>;
9
8
  private write;
10
- sendRequest(method: string, params: any): Promise<any>;
11
- sendNotification(method: string, params: any): Promise<void>;
12
- registerClosure(prefix: string, handler: (payload: any) => any): string;
13
- registerMethod(method: string, handler: (payload: any) => any): void;
14
- unregisterMethod(method: string): void;
15
9
  }
16
10
  //# sourceMappingURL=IpcRecordKit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IpcRecordKit.d.ts","sourceRoot":"","sources":["../src/IpcRecordKit.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAmBtC,qBAAa,YAAa,SAAQ,YAAY;IAC1C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAe;IAEpC,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,aAAa,CAAiD;IAEhE,UAAU,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAqEvF,OAAO,CAAC,KAAK;IAYP,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAQtD,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,GAAG,MAAM;IAMvE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,GAAG,IAAI;IAKpE,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAGzC"}
1
+ {"version":3,"file":"IpcRecordKit.d.ts","sourceRoot":"","sources":["../src/IpcRecordKit.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,qBAAa,YAAY;IACrB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;;IAMhB,UAAU,CAAC,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBvF,OAAO,CAAC,KAAK;CAUhB"}
@@ -1,120 +1,40 @@
1
- import { EventEmitter } from 'events';
2
- import { randomUUID } from 'crypto';
3
1
  import { spawn } from 'node:child_process';
4
2
  import * as readline from 'readline';
5
- export class IpcRecordKit extends EventEmitter {
3
+ import { NSRPC } from "./NonstrictRPC.js";
4
+ export class IpcRecordKit {
6
5
  constructor() {
7
- super(...arguments);
8
6
  this.logMessages = false;
9
- this.responseHandler = new Map();
10
- this.methodHandler = new Map();
7
+ this.nsrpc = new NSRPC((message) => this.write(message));
11
8
  }
12
- async initialize(recordKitCliPath, logMessages = false) {
9
+ async initialize(recordKitRpcPath, logMessages = false) {
13
10
  if (this.childProcess !== undefined) {
14
- throw new Error('RecordKit CLI: Already initialized.');
11
+ throw new Error('RecordKit RPC: Already initialized.');
15
12
  }
16
13
  this.logMessages = logMessages;
17
14
  this.childProcess = await new Promise((resolve, reject) => {
18
- const childProcess = spawn(recordKitCliPath);
15
+ const childProcess = spawn(recordKitRpcPath);
19
16
  childProcess.on('spawn', () => { resolve(childProcess); });
20
17
  childProcess.on('error', (error) => { reject(error); });
21
18
  });
22
19
  const { stdout } = this.childProcess;
23
20
  if (!stdout) {
24
- throw new Error('RecordKit CLI: No stdout stream on child process.');
21
+ throw new Error('RecordKit RPC: No stdout stream on child process.');
25
22
  }
26
23
  readline.createInterface({ input: stdout }).on('line', (line) => {
27
24
  if (logMessages) {
28
25
  console.log("< ", line.trimEnd());
29
26
  }
30
- const message = JSON.parse(line);
31
- if (message.jsonrpc !== '2.0') {
32
- console.error("Invalid JSON-RPC message: ", line);
33
- return;
34
- }
35
- if (message.result && message.id) {
36
- const responseHandler = this.responseHandler.get(message.id);
37
- if (!responseHandler) {
38
- console.error("Received response for unknown request: ", line);
39
- return;
40
- }
41
- responseHandler?.resolve(message.result);
42
- this.responseHandler.delete(message.id);
43
- }
44
- else if (message.error && message.id) {
45
- const responseHandler = this.responseHandler.get(message.id);
46
- if (!responseHandler) {
47
- console.error("Received response for unknown request: ", line);
48
- return;
49
- }
50
- responseHandler?.reject(message.error);
51
- this.responseHandler.delete(message.id);
52
- }
53
- else if (message.method) {
54
- const methodHandler = this.methodHandler.get(message.method);
55
- if (!methodHandler) {
56
- console.error("No handler installed for method: ", line);
57
- return;
58
- }
59
- try {
60
- const response = methodHandler(message.params); // TODO: Catch the error and send back over JSONRPC
61
- if (message.id && response) {
62
- this.write({ jsonrpc: '2.0', id: message.id, result: response });
63
- }
64
- else if (message.id) {
65
- // Missing response
66
- this.write({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: 'Method called, but no response produced.' } });
67
- }
68
- else if (response) {
69
- console.error("No response expected, throwing away result: ", response);
70
- }
71
- }
72
- catch (error) {
73
- if (message.id) {
74
- this.write({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: 'Method threw error.', data: error } });
75
- }
76
- else {
77
- console.error("Handler for notification with method '\(message.method)' threw an error:", error);
78
- }
79
- }
80
- }
81
- else {
82
- console.error("Invalid JSON-RPC message: ", line);
83
- }
27
+ this.nsrpc.receive(line);
84
28
  });
85
29
  }
86
30
  write(message) {
87
31
  const stdin = this.childProcess?.stdin;
88
32
  if (!stdin) {
89
- throw new Error('RecordKit CLI: Missing stdin stream.');
33
+ throw new Error('RecordKit RPC: Missing stdin stream.');
90
34
  }
91
- const stringifiedMessage = JSON.stringify(message);
92
35
  if (this.logMessages) {
93
- console.log("> ", stringifiedMessage);
36
+ console.log("> ", message);
94
37
  }
95
- stdin.write(stringifiedMessage + "\n");
96
- }
97
- async sendRequest(method, params) {
98
- const id = 'request_' + randomUUID();
99
- const response = new Promise((resolve, reject) => { this.responseHandler.set(id, { resolve, reject }); });
100
- this.write({ jsonrpc: '2.0', id, method, params });
101
- return response;
102
- }
103
- async sendNotification(method, params) {
104
- this.write({ jsonrpc: '2.0', method, params });
105
- }
106
- registerClosure(prefix, handler) {
107
- const methodName = prefix + '_' + randomUUID();
108
- this.registerMethod(methodName, handler);
109
- return methodName;
110
- }
111
- registerMethod(method, handler) {
112
- if (this.methodHandler.has(method)) {
113
- throw new Error(`Method already registered: ${method}`);
114
- }
115
- this.methodHandler.set(method, handler);
116
- }
117
- unregisterMethod(method) {
118
- this.methodHandler.delete(method);
38
+ stdin.write(message + "\n");
119
39
  }
120
40
  }
@@ -0,0 +1,40 @@
1
+ export interface NSRPCPerformClosureRequest {
2
+ nsrpc: number;
3
+ id?: string;
4
+ procedure: "perform";
5
+ target: string;
6
+ params?: Record<string, unknown>;
7
+ }
8
+ type ClosureTarget = (params: Record<string, unknown>) => Record<string, unknown> | void;
9
+ export declare class NSRPC {
10
+ private readonly send;
11
+ private responseHandlers;
12
+ private closureTargets;
13
+ constructor(send: (data: string) => void);
14
+ receive(data: string): void;
15
+ private sendMessage;
16
+ private sendResponse;
17
+ private sendRequest;
18
+ private handleRequest;
19
+ private handleClosureRequest;
20
+ initialize(args: {
21
+ target: string;
22
+ type: string;
23
+ params?: Record<string, unknown>;
24
+ lifecycle: Object;
25
+ }): Promise<void>;
26
+ perform(body: {
27
+ type?: string;
28
+ target?: string;
29
+ action?: string;
30
+ params?: Record<string, unknown>;
31
+ }): Promise<unknown>;
32
+ private release;
33
+ registerClosure(options: {
34
+ handler: ClosureTarget;
35
+ lifecycle: Object;
36
+ prefix: string;
37
+ }): string;
38
+ }
39
+ export {};
40
+ //# sourceMappingURL=NonstrictRPC.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NonstrictRPC.d.ts","sourceRoot":"","sources":["../src/NonstrictRPC.ts"],"names":[],"mappings":"AAyCA,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AA6DD,KAAK,aAAa,GAAG,CACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;AAEpC,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,OAAO,CAAC,gBAAgB,CAAyC;IACjE,OAAO,CAAC,cAAc,CAAyC;gBAEnD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;IAIxC,OAAO,CAAC,IAAI,EAAE,MAAM;IA+BpB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,YAAY;YAON,WAAW;IAezB,OAAO,CAAC,aAAa;IAqCrB,OAAO,CAAC,oBAAoB;IAqCtB,UAAU,CAAC,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,SAAS,EAAE,MAAM,CAAC;KACnB;IAcK,OAAO,CAAC,IAAI,EAAE;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,GAAG,OAAO,CAAC,OAAO,CAAC;YAON,OAAO;IASrB,eAAe,CAAC,OAAO,EAAE;QACvB,OAAO,EAAE,aAAa,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,MAAM;CAUX"}
@@ -0,0 +1,155 @@
1
+ import { randomUUID } from "crypto";
2
+ import { finalizationRegistry } from "./finalizationRegistry.js";
3
+ export class NSRPC {
4
+ constructor(send) {
5
+ this.responseHandlers = new Map();
6
+ this.closureTargets = new Map();
7
+ this.send = send;
8
+ }
9
+ receive(data) {
10
+ // TODO: For now we just assume the message is a valid NSRPC message, but we should:
11
+ // - Handle invalid JSON comming in
12
+ // - Check if the nsrpc property is set to a number in the range of 1..<2
13
+ // - Validate the message against the defined interfaces above
14
+ const message = JSON.parse(data);
15
+ if ("status" in message) {
16
+ // This is a response, dispatch it so it can be handled
17
+ const responseHandler = this.responseHandlers.get(message.id);
18
+ this.responseHandlers.delete(message.id);
19
+ if (responseHandler === undefined) {
20
+ // TODO: Got a response for a request we don't know about, log this
21
+ return;
22
+ }
23
+ if ("error" in message) {
24
+ responseHandler.reject(message.error);
25
+ }
26
+ else {
27
+ responseHandler.resolve(message.result);
28
+ }
29
+ }
30
+ else {
31
+ // This is a request
32
+ const responseBody = this.handleRequest(message);
33
+ if (responseBody !== undefined) {
34
+ this.sendResponse(message.id, responseBody);
35
+ }
36
+ }
37
+ }
38
+ /* Sending helpers */
39
+ sendMessage(message) {
40
+ this.send(JSON.stringify(message));
41
+ }
42
+ sendResponse(id, response) {
43
+ if (id === undefined) {
44
+ return;
45
+ }
46
+ this.sendMessage({ ...response, nsrpc: 1, id });
47
+ }
48
+ async sendRequest(request) {
49
+ const id = "req_" + randomUUID();
50
+ const response = new Promise((resolve, reject) => {
51
+ this.responseHandlers.set(id, { resolve, reject });
52
+ });
53
+ this.sendMessage({ ...request, nsrpc: 1, id });
54
+ return response;
55
+ }
56
+ /* Request handling */
57
+ handleRequest(request) {
58
+ switch (request.procedure) {
59
+ case "init":
60
+ return {
61
+ status: 501,
62
+ error: {
63
+ debugDescription: "Init procedure not implemented.",
64
+ userMessage: "Failed to communicate with external process. (Procedure not implemented)",
65
+ },
66
+ };
67
+ case "perform":
68
+ if ("action" in request) {
69
+ return {
70
+ status: 501,
71
+ error: {
72
+ debugDescription: "Perform procedure for (static) methods not implemented.",
73
+ userMessage: "Failed to communicate with external process. (Procedure not implemented)",
74
+ },
75
+ };
76
+ }
77
+ else {
78
+ return this.handleClosureRequest(request);
79
+ }
80
+ case "release":
81
+ return {
82
+ status: 501,
83
+ error: {
84
+ debugDescription: "Release procedure not implemented.",
85
+ userMessage: "Failed to communicate with external process. (Procedure not implemented)",
86
+ },
87
+ };
88
+ }
89
+ }
90
+ handleClosureRequest(request) {
91
+ const handler = this.closureTargets.get(request.target);
92
+ if (handler === undefined) {
93
+ return {
94
+ status: 404,
95
+ error: {
96
+ debugDescription: `Perform target '${request.target}' not found.`,
97
+ userMessage: "Failed to communicate with external process. (Target not found)",
98
+ },
99
+ };
100
+ }
101
+ try {
102
+ const rawresult = handler(request.params ?? {});
103
+ const result = rawresult === undefined ? undefined : rawresult;
104
+ return {
105
+ status: 200,
106
+ result,
107
+ };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ status: 202,
112
+ // 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.)
113
+ error: {
114
+ debugDescription: `${error}`,
115
+ userMessage: "Handler failed to perform request.",
116
+ underlyingError: error,
117
+ },
118
+ };
119
+ }
120
+ }
121
+ /* Perform remote procedures */
122
+ async initialize(args) {
123
+ const target = args.target;
124
+ finalizationRegistry.register(args.lifecycle, async () => {
125
+ await this.release(target);
126
+ });
127
+ await this.sendRequest({
128
+ target: args.target,
129
+ type: args.type,
130
+ params: args.params,
131
+ procedure: "init",
132
+ });
133
+ }
134
+ async perform(body) {
135
+ return await this.sendRequest({
136
+ ...body,
137
+ procedure: "perform",
138
+ });
139
+ }
140
+ async release(target) {
141
+ await this.sendRequest({
142
+ procedure: "release",
143
+ target,
144
+ });
145
+ }
146
+ /* Register locally available targets/actions */
147
+ registerClosure(options) {
148
+ const target = `target_${options.prefix}_${randomUUID()}`;
149
+ this.closureTargets.set(target, options.handler);
150
+ finalizationRegistry.register(options.lifecycle, () => {
151
+ this.closureTargets.delete(target);
152
+ });
153
+ return target;
154
+ }
155
+ }
@@ -1,11 +1,34 @@
1
- import { IpcRecordKit } from "./IpcRecordKit.js";
2
- import { RecordingDiscovery } from "./RecordingDiscovery.js";
3
- import { RecordingSession, RecordingSchema, AbortReason } from "./RecordingSession.js";
1
+ import { RecordingSession, RecordingSchema } from "./RecordingSession.js";
2
+ interface Window {
3
+ id: number;
4
+ title?: string;
5
+ frame: Bounds;
6
+ level: number;
7
+ applicationProcessID?: number;
8
+ applicationName?: string;
9
+ }
10
+ interface Camera {
11
+ id: string;
12
+ localizedName: string;
13
+ modelID: string;
14
+ manufacturer: string;
15
+ availability: DeviceAvailability;
16
+ }
17
+ type Microphone = Camera;
18
+ type DeviceAvailability = 'available' | 'lidClosed' | 'notConnected' | 'unknownSuspended';
19
+ interface Bounds {
20
+ x: number;
21
+ y: number;
22
+ width: number;
23
+ height: number;
24
+ }
4
25
  declare class RecordKit {
5
- ipcRecordKitCli: IpcRecordKit;
6
- initialize(cliPath: string, logRpcMessages?: boolean): Promise<void>;
7
- RecordingSession(schema: RecordingSchema, onAbort: (reason: AbortReason) => void): Promise<RecordingSession>;
8
- RecordingDiscovery: typeof RecordingDiscovery;
26
+ private ipcRecordKit;
27
+ initialize(rpcBinaryPath: string, logRpcMessages?: boolean): Promise<void>;
28
+ getWindows(): Promise<Window[]>;
29
+ getCameras(): Promise<Camera[]>;
30
+ getMicrophones(): Promise<Microphone[]>;
31
+ createSession(schema: RecordingSchema): Promise<RecordingSession>;
9
32
  }
10
33
  export declare let recordkit: RecordKit;
11
34
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"RecordKit.d.ts","sourceRoot":"","sources":["../src/RecordKit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEvF,cAAM,SAAS;IACX,eAAe,eAAqB;IAE9B,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3E,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI;IAItF,kBAAkB,4BAAqB;CAC1C;AAED,eAAO,IAAI,SAAS,WAAkB,CAAC"}
1
+ {"version":3,"file":"RecordKit.d.ts","sourceRoot":"","sources":["../src/RecordKit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAe,MAAM,uBAAuB,CAAC;AAEvF,UAAU,MAAM;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,UAAU,MAAM;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,kBAAkB,CAAA;CACnC;AAED,KAAK,UAAU,GAAG,MAAM,CAAA;AAExB,KAAK,kBAAkB,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,GAAG,kBAAkB,CAAA;AAEzF,UAAU,MAAM;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,cAAM,SAAS;IACX,OAAO,CAAC,YAAY,CAAqB;IAEnC,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjF,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI/B,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI/B,cAAc,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAIvC,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAG1E;AAED,eAAO,IAAI,SAAS,WAAkB,CAAC"}
package/dist/RecordKit.js CHANGED
@@ -1,17 +1,24 @@
1
1
  import { IpcRecordKit } from "./IpcRecordKit.js";
2
- import { RecordingDiscovery } from "./RecordingDiscovery.js";
3
2
  import { RecordingSession } from "./RecordingSession.js";
4
3
  class RecordKit {
5
4
  constructor() {
6
- this.ipcRecordKitCli = new IpcRecordKit();
7
- this.RecordingDiscovery = RecordingDiscovery;
5
+ this.ipcRecordKit = new IpcRecordKit();
8
6
  }
9
- async initialize(cliPath, logRpcMessages = false) {
10
- console.log('Initializing with path:', cliPath);
11
- return this.ipcRecordKitCli.initialize(cliPath, logRpcMessages);
7
+ async initialize(rpcBinaryPath, logRpcMessages = false) {
8
+ console.log('Initializing with path:', rpcBinaryPath);
9
+ return this.ipcRecordKit.initialize(rpcBinaryPath, logRpcMessages);
12
10
  }
13
- async RecordingSession(schema, onAbort) {
14
- return RecordingSession.newInstance(this.ipcRecordKitCli, schema, onAbort);
11
+ async getWindows() {
12
+ return await this.ipcRecordKit.nsrpc.perform({ type: 'RecordingSession', action: 'getWindows' });
13
+ }
14
+ async getCameras() {
15
+ return await this.ipcRecordKit.nsrpc.perform({ type: 'RecordingSession', action: 'getVideoDevices' });
16
+ }
17
+ async getMicrophones() {
18
+ return await this.ipcRecordKit.nsrpc.perform({ type: 'RecordingSession', action: 'getAudioDevices' });
19
+ }
20
+ async createSession(schema) {
21
+ return RecordingSession.newInstance(this.ipcRecordKit.nsrpc, schema);
15
22
  }
16
23
  }
17
24
  export let recordkit = new RecordKit();
@@ -1,11 +1,24 @@
1
- import { IpcRecordKit } from "./IpcRecordKit.js";
2
- export type RecordingSchema = WindowBasedCropSchema;
3
- export interface WindowBasedCropSchema {
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ import { NSRPC } from "./NonstrictRPC.js";
4
+ import { EventEmitter } from "stream";
5
+ export interface RecordingSchema {
6
+ outputDirectory?: string;
7
+ recorders: Recorder[];
8
+ }
9
+ export type Recorder = WindowBasedCropRecorder | MovieRecorder;
10
+ export interface MovieRecorder {
11
+ type: 'movie';
12
+ filename?: string;
13
+ microphoneID: string;
14
+ cameraID: string;
15
+ }
16
+ export interface WindowBasedCropRecorder {
4
17
  type: 'windowBasedCrop';
18
+ filename?: string;
5
19
  windowID: number;
6
20
  outputDirectory?: string;
7
21
  bundleName?: string;
8
- filename?: string;
9
22
  }
10
23
  export type AbortReason = {
11
24
  reason: 'userStopped';
@@ -46,13 +59,13 @@ export interface CMTimeRange {
46
59
  start: CMTime;
47
60
  duration: CMTime;
48
61
  }
49
- export declare class RecordingSession {
50
- private readonly ipcRecordKit;
51
- private readonly instance;
52
- static newInstance(ipcRecordKit: IpcRecordKit, schema: RecordingSchema, onAbort: (reason: AbortReason) => void): Promise<RecordingSession>;
53
- constructor(ipcRecordKit: IpcRecordKit, instance: string);
62
+ export declare class RecordingSession extends EventEmitter {
63
+ private readonly rpc;
64
+ private readonly target;
65
+ static newInstance(rpc: NSRPC, schema: RecordingSchema): Promise<RecordingSession>;
66
+ constructor(rpc: NSRPC, target: string);
54
67
  prepare(): Promise<void>;
55
- record(): Promise<void>;
56
- finish(): Promise<RecordingResult>;
68
+ start(): Promise<void>;
69
+ stop(): Promise<RecordingResult>;
57
70
  }
58
71
  //# sourceMappingURL=RecordingSession.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RecordingSession.d.ts","sourceRoot":"","sources":["../src/RecordingSession.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,MAAM,MAAM,eAAe,GACvB,qBAAqB,CAAA;AAEzB,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,iBAAiB,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAEhB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;CAAE,GACnD;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;CAAE,GACnE;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;CAAE,CAAA;AAEzC,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,OAAO;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC5D,UAAU,EAAE,WAAW,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,MAAM;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,gBAAgB;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;WAErB,WAAW,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;gBAapI,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM;IAKlD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC;CAG3C"}
1
+ {"version":3,"file":"RecordingSession.d.ts","sourceRoot":"","sources":["../src/RecordingSession.ts"],"names":[],"mappings":";;AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,eAAe;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,QAAQ,EAAE,CAAA;CACxB;AAED,MAAM,MAAM,QAAQ,GAChB,uBAAuB,GACvB,aAAa,CAAA;AAEjB,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,iBAAiB,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAEhB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;CAAE,GACnD;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;CAAE,GACnE;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;CAAE,CAAA;AAEzC,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,OAAO;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC5D,UAAU,EAAE,WAAW,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,MAAM;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,gBAAiB,SAAQ,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;WAEnB,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;gBAqB5E,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;IAMhC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC;CAGzC"}
@@ -1,28 +1,35 @@
1
1
  import { randomUUID } from "crypto";
2
- import { finalizationRegistry } from "./finalizationRegistry.js";
3
- export class RecordingSession {
4
- static async newInstance(ipcRecordKit, schema, onAbort) {
5
- let instance = 'RecordingSession_' + randomUUID();
6
- const onAbortInstance = ipcRecordKit.registerClosure('RecordingSession.onAbort', onAbort);
7
- await ipcRecordKit.sendRequest('RecordingSession.newInstance', { instance, schema, onAbortInstance });
8
- const object = new RecordingSession(ipcRecordKit, instance);
9
- finalizationRegistry.register(object, async () => {
10
- await ipcRecordKit.sendRequest('RecordingSession.releaseInstance', { instance });
11
- ipcRecordKit.unregisterMethod(onAbortInstance);
2
+ import { EventEmitter } from "stream";
3
+ export class RecordingSession extends EventEmitter {
4
+ static async newInstance(rpc, schema) {
5
+ const target = 'RecordingSession_' + randomUUID();
6
+ const object = new RecordingSession(rpc, target);
7
+ const weakRefObject = new WeakRef(object);
8
+ const onAbortInstance = rpc.registerClosure({
9
+ handler: (params) => { weakRefObject.deref()?.emit('abort', params.reason); },
10
+ prefix: 'RecordingSession.onAbort',
11
+ lifecycle: object
12
+ });
13
+ await rpc.initialize({
14
+ target,
15
+ type: 'RecordingSession',
16
+ params: { schema, onAbortInstance },
17
+ lifecycle: object
12
18
  });
13
19
  return object;
14
20
  }
15
- constructor(ipcRecordKit, instance) {
16
- this.ipcRecordKit = ipcRecordKit;
17
- this.instance = instance;
21
+ constructor(rpc, target) {
22
+ super();
23
+ this.rpc = rpc;
24
+ this.target = target;
18
25
  }
19
26
  async prepare() {
20
- return this.ipcRecordKit.sendRequest('RecordingSession.prepare', { instance: this.instance });
27
+ await this.rpc.perform({ target: this.target, action: 'prepare' });
21
28
  }
22
- async record() {
23
- return this.ipcRecordKit.sendRequest('RecordingSession.record', { instance: this.instance });
29
+ async start() {
30
+ await this.rpc.perform({ target: this.target, action: 'start' });
24
31
  }
25
- async finish() {
26
- return this.ipcRecordKit.sendRequest('RecordingSession.finish', { instance: this.instance });
32
+ async stop() {
33
+ return await this.rpc.perform({ target: this.target, action: 'stop' });
27
34
  }
28
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonstrict/recordkit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "RecordKit gives you hassle-free recording in Electron apps on macOS.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,6 +12,7 @@
12
12
  "node": ">=18.16"
13
13
  },
14
14
  "scripts": {
15
+ "test": "jest",
15
16
  "build": "tsc",
16
17
  "clean": "rm -rf dist",
17
18
  "prepublish": "npm run clean && npm run build"
@@ -34,7 +35,10 @@
34
35
  },
35
36
  "license": "SEE LICENSE IN License.md",
36
37
  "devDependencies": {
38
+ "@types/jest": "^29.5.12",
37
39
  "@types/node": "18.19.1",
40
+ "jest": "^29.7.0",
41
+ "ts-jest": "^29.1.2",
38
42
  "typescript": "^5.3.3"
39
43
  },
40
44
  "volta": {
@@ -1,19 +0,0 @@
1
- interface Window {
2
- windowID: number;
3
- title?: string;
4
- frame: Bounds;
5
- level: number;
6
- applicationProcessID?: number;
7
- applicationName?: string;
8
- }
9
- interface Bounds {
10
- x: number;
11
- y: number;
12
- width: number;
13
- height: number;
14
- }
15
- export declare class RecordingDiscovery {
16
- static getWindows(): Promise<Window[]>;
17
- }
18
- export {};
19
- //# sourceMappingURL=RecordingDiscovery.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RecordingDiscovery.d.ts","sourceRoot":"","sources":["../src/RecordingDiscovery.ts"],"names":[],"mappings":"AAEA,UAAU,MAAM;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,UAAU,MAAM;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,kBAAkB;WACd,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAG/C"}
@@ -1,6 +0,0 @@
1
- import { recordkit } from "./RecordKit.js";
2
- export class RecordingDiscovery {
3
- static async getWindows() {
4
- return recordkit.ipcRecordKitCli.sendRequest('RecordingDiscovery.getWindows', {});
5
- }
6
- }