@nonstrict/recordkit 0.1.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.
package/License.md ADDED
@@ -0,0 +1 @@
1
+ All rights reserved by Nonstrict B.V.
@@ -0,0 +1,16 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { EventEmitter } from 'events';
3
+ export declare class IpcRecordKit extends EventEmitter {
4
+ private logMessages;
5
+ private childProcess?;
6
+ private responseHandler;
7
+ private methodHandler;
8
+ initialize(recordKitCliPath: string, logMessages?: boolean): Promise<void>;
9
+ 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
+ }
16
+ //# sourceMappingURL=IpcRecordKit.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,120 @@
1
+ import { EventEmitter } from 'events';
2
+ import { randomUUID } from 'crypto';
3
+ import { spawn } from 'node:child_process';
4
+ import * as readline from 'readline';
5
+ export class IpcRecordKit extends EventEmitter {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.logMessages = false;
9
+ this.responseHandler = new Map();
10
+ this.methodHandler = new Map();
11
+ }
12
+ async initialize(recordKitCliPath, logMessages = false) {
13
+ if (this.childProcess !== undefined) {
14
+ throw new Error('RecordKit CLI: Already initialized.');
15
+ }
16
+ this.logMessages = logMessages;
17
+ this.childProcess = await new Promise((resolve, reject) => {
18
+ const childProcess = spawn(recordKitCliPath);
19
+ childProcess.on('spawn', () => { resolve(childProcess); });
20
+ childProcess.on('error', (error) => { reject(error); });
21
+ });
22
+ const { stdout } = this.childProcess;
23
+ if (!stdout) {
24
+ throw new Error('RecordKit CLI: No stdout stream on child process.');
25
+ }
26
+ readline.createInterface({ input: stdout }).on('line', (line) => {
27
+ if (logMessages) {
28
+ console.log("< ", line.trimEnd());
29
+ }
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
+ }
84
+ });
85
+ }
86
+ write(message) {
87
+ const stdin = this.childProcess?.stdin;
88
+ if (!stdin) {
89
+ throw new Error('RecordKit CLI: Missing stdin stream.');
90
+ }
91
+ const stringifiedMessage = JSON.stringify(message);
92
+ if (this.logMessages) {
93
+ console.log("> ", stringifiedMessage);
94
+ }
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);
119
+ }
120
+ }
@@ -0,0 +1,12 @@
1
+ import { IpcRecordKit } from "./IpcRecordKit.js";
2
+ import { RecordingDiscovery } from "./RecordingDiscovery.js";
3
+ import { RecordingSession, RecordingSchema, AbortReason } from "./RecordingSession.js";
4
+ 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;
9
+ }
10
+ export declare let recordkit: RecordKit;
11
+ export {};
12
+ //# sourceMappingURL=RecordKit.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,17 @@
1
+ import { IpcRecordKit } from "./IpcRecordKit.js";
2
+ import { RecordingDiscovery } from "./RecordingDiscovery.js";
3
+ import { RecordingSession } from "./RecordingSession.js";
4
+ class RecordKit {
5
+ constructor() {
6
+ this.ipcRecordKitCli = new IpcRecordKit();
7
+ this.RecordingDiscovery = RecordingDiscovery;
8
+ }
9
+ async initialize(cliPath, logRpcMessages = false) {
10
+ console.log('Initializing with path:', cliPath);
11
+ return this.ipcRecordKitCli.initialize(cliPath, logRpcMessages);
12
+ }
13
+ async RecordingSession(schema, onAbort) {
14
+ return RecordingSession.newInstance(this.ipcRecordKitCli, schema, onAbort);
15
+ }
16
+ }
17
+ export let recordkit = new RecordKit();
@@ -0,0 +1,19 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,6 @@
1
+ import { recordkit } from "./RecordKit.js";
2
+ export class RecordingDiscovery {
3
+ static async getWindows() {
4
+ return recordkit.ipcRecordKitCli.sendRequest('RecordingDiscovery.getWindows', {});
5
+ }
6
+ }
@@ -0,0 +1,58 @@
1
+ import { IpcRecordKit } from "./IpcRecordKit.js";
2
+ export type RecordingSchema = WindowBasedCropSchema;
3
+ export interface WindowBasedCropSchema {
4
+ type: 'windowBasedCrop';
5
+ windowID: number;
6
+ outputDirectory?: string;
7
+ bundleName?: string;
8
+ filename?: string;
9
+ }
10
+ export type AbortReason = {
11
+ reason: 'userStopped';
12
+ result: RecordingResult;
13
+ } | {
14
+ reason: 'interrupted';
15
+ result: RecordingResult;
16
+ error: RKError;
17
+ } | {
18
+ reason: 'failed';
19
+ error: RKError;
20
+ };
21
+ export interface RecordingResult {
22
+ url: string;
23
+ info: RKBundleInfo;
24
+ }
25
+ export interface RKError {
26
+ errorGroup: string;
27
+ debugDescription: string;
28
+ userMessage?: string;
29
+ }
30
+ export interface RKBundleInfo {
31
+ files: {
32
+ type: 'video';
33
+ filename: string;
34
+ offset: CMTime;
35
+ }[];
36
+ timeRanges: CMTimeRange[];
37
+ }
38
+ export interface CMTime {
39
+ value: number;
40
+ timescale: number;
41
+ flags: number;
42
+ epoch: number;
43
+ seconds: number;
44
+ }
45
+ export interface CMTimeRange {
46
+ start: CMTime;
47
+ duration: CMTime;
48
+ }
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);
54
+ prepare(): Promise<void>;
55
+ record(): Promise<void>;
56
+ finish(): Promise<RecordingResult>;
57
+ }
58
+ //# sourceMappingURL=RecordingSession.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,28 @@
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);
12
+ });
13
+ return object;
14
+ }
15
+ constructor(ipcRecordKit, instance) {
16
+ this.ipcRecordKit = ipcRecordKit;
17
+ this.instance = instance;
18
+ }
19
+ async prepare() {
20
+ return this.ipcRecordKit.sendRequest('RecordingSession.prepare', { instance: this.instance });
21
+ }
22
+ async record() {
23
+ return this.ipcRecordKit.sendRequest('RecordingSession.record', { instance: this.instance });
24
+ }
25
+ async finish() {
26
+ return this.ipcRecordKit.sendRequest('RecordingSession.finish', { instance: this.instance });
27
+ }
28
+ }
@@ -0,0 +1,4 @@
1
+ type Destructor = () => void;
2
+ export declare const finalizationRegistry: FinalizationRegistry<Destructor>;
3
+ export {};
4
+ //# sourceMappingURL=finalizationRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalizationRegistry.d.ts","sourceRoot":"","sources":["../src/finalizationRegistry.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG,MAAM,IAAI,CAAA;AAC5B,eAAO,MAAM,oBAAoB,kCAAqF,CAAA"}
@@ -0,0 +1 @@
1
+ export const finalizationRegistry = new FinalizationRegistry(async (destructor) => { await destructor(); });
@@ -0,0 +1,2 @@
1
+ export { recordkit } from './RecordKit.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { recordkit } from './RecordKit.js';
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@nonstrict/recordkit",
3
+ "version": "0.1.0",
4
+ "description": "RecordKit gives you hassle-free recording in Electron apps on macOS.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "engines": {
12
+ "node": ">=18.16"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "clean": "rm -rf dist",
17
+ "prepublish": "npm run clean && npm run build"
18
+ },
19
+ "keywords": [
20
+ "recording",
21
+ "screen",
22
+ "recorder",
23
+ "webcam",
24
+ "microphone",
25
+ "electron",
26
+ "macos",
27
+ "avfoundation",
28
+ "screencapturekit"
29
+ ],
30
+ "author": {
31
+ "name": "Nonstrict B.V.",
32
+ "email": "team+recordkit@nonstrict.com",
33
+ "url": "https://nonstrict.eu"
34
+ },
35
+ "license": "SEE LICENSE IN License.md",
36
+ "devDependencies": {
37
+ "@types/node": "18.19.1",
38
+ "typescript": "^5.3.3"
39
+ },
40
+ "volta": {
41
+ "node": "18.19.1"
42
+ }
43
+ }