@wirecat/zoom-cli 0.3.2 → 0.4.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.
Files changed (67) hide show
  1. package/README.md +33 -5
  2. package/dist/archive-commands.d.ts +2 -1
  3. package/dist/archive-commands.d.ts.map +1 -1
  4. package/dist/archive-commands.js +54 -10
  5. package/dist/archive-commands.js.map +1 -1
  6. package/dist/bin/zm.js +2 -0
  7. package/dist/bin/zm.js.map +1 -1
  8. package/dist/execution.d.ts.map +1 -1
  9. package/dist/execution.js +3 -0
  10. package/dist/execution.js.map +1 -1
  11. package/dist/index.d.ts +4 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +4 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/ordinary-file.d.ts +1 -0
  16. package/dist/ordinary-file.d.ts.map +1 -1
  17. package/dist/ordinary-file.js +13 -0
  18. package/dist/ordinary-file.js.map +1 -1
  19. package/dist/program.d.ts +7 -1
  20. package/dist/program.d.ts.map +1 -1
  21. package/dist/program.js +62 -0
  22. package/dist/program.js.map +1 -1
  23. package/dist/recording-archive.d.ts +2 -0
  24. package/dist/recording-archive.d.ts.map +1 -1
  25. package/dist/recording-archive.js +151 -79
  26. package/dist/recording-archive.js.map +1 -1
  27. package/dist/recording-partial-store.d.ts +20 -0
  28. package/dist/recording-partial-store.d.ts.map +1 -0
  29. package/dist/recording-partial-store.js +225 -0
  30. package/dist/recording-partial-store.js.map +1 -0
  31. package/dist/recording-promotion-store.d.ts +12 -0
  32. package/dist/recording-promotion-store.d.ts.map +1 -0
  33. package/dist/recording-promotion-store.js +92 -0
  34. package/dist/recording-promotion-store.js.map +1 -0
  35. package/dist/recording-schedule-commands.d.ts +12 -0
  36. package/dist/recording-schedule-commands.d.ts.map +1 -0
  37. package/dist/recording-schedule-commands.js +80 -0
  38. package/dist/recording-schedule-commands.js.map +1 -0
  39. package/dist/recording-schedule-store.d.ts +4 -0
  40. package/dist/recording-schedule-store.d.ts.map +1 -0
  41. package/dist/recording-schedule-store.js +141 -0
  42. package/dist/recording-schedule-store.js.map +1 -0
  43. package/dist/recording-schedule-systemd.d.ts +14 -0
  44. package/dist/recording-schedule-systemd.d.ts.map +1 -0
  45. package/dist/recording-schedule-systemd.js +173 -0
  46. package/dist/recording-schedule-systemd.js.map +1 -0
  47. package/dist/recording-schedule.d.ts +93 -0
  48. package/dist/recording-schedule.d.ts.map +1 -0
  49. package/dist/recording-schedule.js +121 -0
  50. package/dist/recording-schedule.js.map +1 -0
  51. package/dist/recording-sync-store.d.ts +3 -0
  52. package/dist/recording-sync-store.d.ts.map +1 -0
  53. package/dist/recording-sync-store.js +187 -0
  54. package/dist/recording-sync-store.js.map +1 -0
  55. package/dist/recording-sync.d.ts +10 -1
  56. package/dist/recording-sync.d.ts.map +1 -1
  57. package/dist/recording-sync.js +72 -41
  58. package/dist/recording-sync.js.map +1 -1
  59. package/dist/recordings.d.ts +9 -10
  60. package/dist/recordings.d.ts.map +1 -1
  61. package/dist/recordings.js +50 -13
  62. package/dist/recordings.js.map +1 -1
  63. package/dist/remote-meetings.d.ts +15 -0
  64. package/dist/remote-meetings.d.ts.map +1 -1
  65. package/dist/remote-meetings.js +73 -65
  66. package/dist/remote-meetings.js.map +1 -1
  67. package/package.json +3 -3
@@ -0,0 +1,14 @@
1
+ import { type RecordingScheduleBackend } from "./recording-schedule.js";
2
+ export type RecordingScheduleExecutor = (executable: string, args: readonly string[], options: {
3
+ shell: false;
4
+ signal?: AbortSignal;
5
+ }) => Promise<{
6
+ exitCode: number;
7
+ }>;
8
+ export declare const createSystemdRecordingArchiveScheduler: (options: {
9
+ unitDirectory: string;
10
+ platform?: NodeJS.Platform;
11
+ execute?: RecordingScheduleExecutor;
12
+ systemctl?: string;
13
+ }) => RecordingScheduleBackend;
14
+ //# sourceMappingURL=recording-schedule-systemd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-schedule-systemd.d.ts","sourceRoot":"","sources":["../src/recording-schedule-systemd.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,wBAAwB,EAG9B,MAAM,yBAAyB,CAAA;AAGhC,MAAM,MAAM,yBAAyB,GAAG,CACtC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAC5C,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAqGlC,eAAO,MAAM,sCAAsC,YAAa;IAC9D,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,OAAO,CAAC,EAAE,yBAAyB,CAAA;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,KAAG,wBA2EH,CAAA"}
@@ -0,0 +1,173 @@
1
+ import { execFile } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { link, open, unlink } from "node:fs/promises";
4
+ import { isAbsolute, join } from "node:path";
5
+ import { checkCancelled, ZoomError } from "./errors.js";
6
+ import { openOrdinaryFile, syncDirectory } from "./ordinary-file.js";
7
+ import { parseRecordingScheduleJob, } from "./recording-schedule.js";
8
+ import { recordingScheduleDirectory } from "./recording-schedule-store.js";
9
+ const execute = (executable, args, options) => new Promise((resolve, reject) => {
10
+ execFile(executable, [...args], { shell: false, signal: options.signal, timeout: 30000, maxBuffer: 65536, encoding: "utf8" }, (error) => {
11
+ if (!error)
12
+ return resolve({ exitCode: 0 });
13
+ if (typeof error.code === "number")
14
+ return resolve({ exitCode: error.code });
15
+ if (error.code === "ENOENT")
16
+ return reject(new ZoomError("configuration_error", "systemctl is unavailable"));
17
+ reject(new ZoomError("outcome_unknown", "archive timer command was interrupted; inspect its saved pending job and systemctl status"));
18
+ });
19
+ });
20
+ const missing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
21
+ const quote = (value, expandDollar) => `"${value
22
+ .replaceAll("\\", "\\\\")
23
+ .replaceAll('"', '\\"')
24
+ .replaceAll("%", "%%")
25
+ .replaceAll("$", () => (expandDollar ? "$$" : "$"))
26
+ .replaceAll("\t", "\\t")}"`;
27
+ const unitPlan = (value) => {
28
+ const job = parseRecordingScheduleJob(value);
29
+ const name = `zm-archive-${job.schedule.id}`;
30
+ const serviceName = `${name}.service`;
31
+ const timerName = `${name}.timer`;
32
+ const owner = `# WireCat Zoom archive ${job.schedule.id}`;
33
+ const args = [
34
+ job.capture.runtime,
35
+ job.capture.cliPath,
36
+ "recordings",
37
+ "schedule",
38
+ "run",
39
+ job.schedule.id,
40
+ "--jsonl",
41
+ "--timeout",
42
+ "6h",
43
+ "--no-input",
44
+ ];
45
+ const timeZone = new Intl.DateTimeFormat("en", { timeZone: job.schedule.timeZone }).resolvedOptions().timeZone;
46
+ return {
47
+ serviceName,
48
+ timerName,
49
+ service: `${owner}\n[Unit]\nDescription=WireCat Zoom recording archive\n\n[Service]\nType=oneshot\nUMask=0077\nTimeoutStartSec=6h\nEnvironment=${quote(`ZM_CONFIG_DIR=${job.capture.configDir}`, false)}\nExecStart=${args.map((value) => quote(value, true)).join(" ")}\n`,
50
+ timer: `${owner}\n[Unit]\nDescription=WireCat Zoom recording archive timer\n\n[Timer]\nOnCalendar=*-*-* ${job.schedule.dailyAt}:00 ${timeZone}\nPersistent=true\nUnit=${serviceName}\n\n[Install]\nWantedBy=timers.target\n`,
51
+ };
52
+ };
53
+ const maximumUnitBytes = 64 * 1024;
54
+ const existingUnit = async (path) => {
55
+ let handle;
56
+ try {
57
+ handle = await openOrdinaryFile(path);
58
+ }
59
+ catch (error) {
60
+ if (missing(error))
61
+ return null;
62
+ throw error;
63
+ }
64
+ try {
65
+ const bytes = Buffer.alloc(maximumUnitBytes + 1);
66
+ const size = (await handle.read(bytes, 0, bytes.length, 0)).bytesRead;
67
+ if (size > maximumUnitBytes)
68
+ throw new ZoomError("validation_error", "archive timer unit exceeds its byte limit");
69
+ return bytes.subarray(0, size).toString("utf8");
70
+ }
71
+ finally {
72
+ await handle.close();
73
+ }
74
+ };
75
+ const writeUnit = async (directory, name, content, signal) => {
76
+ if (Buffer.byteLength(content) > maximumUnitBytes)
77
+ throw new ZoomError("validation_error", "archive timer unit exceeds its byte limit");
78
+ const path = join(directory, name);
79
+ const current = await existingUnit(path);
80
+ if (current !== null) {
81
+ if (current !== content)
82
+ throw new ZoomError("validation_error", "archive timer unit is foreign or was edited; it was preserved");
83
+ await syncDirectory(directory);
84
+ return;
85
+ }
86
+ checkCancelled(signal);
87
+ const temp = join(directory, `.zoom-schedule-unit-${randomUUID()}.tmp`);
88
+ const handle = await open(temp, "wx", 0o600);
89
+ try {
90
+ try {
91
+ await handle.writeFile(content);
92
+ await handle.sync();
93
+ }
94
+ finally {
95
+ await handle.close();
96
+ }
97
+ checkCancelled(signal);
98
+ await link(temp, path);
99
+ await syncDirectory(directory);
100
+ }
101
+ finally {
102
+ await unlink(temp).catch(() => undefined);
103
+ }
104
+ };
105
+ export const createSystemdRecordingArchiveScheduler = (options) => {
106
+ const directory = options.unitDirectory;
107
+ const executable = options.systemctl ?? "/usr/bin/systemctl";
108
+ const platform = options.platform ?? process.platform;
109
+ const executor = options.execute ?? execute;
110
+ if (!isAbsolute(directory) || !isAbsolute(executable) || /[\0\r\n]/.test(directory + executable))
111
+ throw new ZoomError("validation_error", "systemd archive paths must be absolute");
112
+ const assertSupported = () => {
113
+ if (platform !== "linux")
114
+ throw new ZoomError("configuration_error", "automatic archive scheduling currently supports Linux user systemd only");
115
+ };
116
+ const checked = async (args, expected, signal) => {
117
+ checkCancelled(signal);
118
+ let result;
119
+ try {
120
+ result = await executor(executable, ["--user", ...args], { shell: false, signal });
121
+ }
122
+ catch (error) {
123
+ if (error instanceof ZoomError)
124
+ throw error;
125
+ throw new ZoomError("outcome_unknown", "archive timer command did not acknowledge completion; inspect its saved job and systemctl status");
126
+ }
127
+ if (result.exitCode !== expected)
128
+ throw new ZoomError("outcome_unknown", "archive timer state was not verified; inspect its saved job and systemctl status");
129
+ };
130
+ return {
131
+ backend: "systemd-user",
132
+ plan: unitPlan,
133
+ assertSupported,
134
+ async install(value, active) {
135
+ assertSupported();
136
+ const job = parseRecordingScheduleJob(value);
137
+ const units = unitPlan(job);
138
+ checkCancelled(active.signal);
139
+ for (const path of [job.capture.runtime, job.capture.cliPath]) {
140
+ const handle = await openOrdinaryFile(path);
141
+ try {
142
+ const stat = await handle.stat();
143
+ if (path === job.capture.runtime && (stat.mode & 0o111) === 0)
144
+ throw new ZoomError("validation_error", "scheduled runtime must be executable");
145
+ }
146
+ finally {
147
+ await handle.close();
148
+ }
149
+ }
150
+ await recordingScheduleDirectory(directory, true);
151
+ await writeUnit(directory, units.serviceName, units.service, active.signal);
152
+ await writeUnit(directory, units.timerName, units.timer, active.signal);
153
+ await checked(["daemon-reload"], 0, active.signal);
154
+ await checked(["enable", "--now", units.timerName], 0, active.signal);
155
+ await checked(["is-enabled", "--quiet", units.timerName], 0, active.signal);
156
+ await checked(["is-active", "--quiet", units.timerName], 0, active.signal);
157
+ },
158
+ async disable(value, active) {
159
+ assertSupported();
160
+ const units = unitPlan(value);
161
+ checkCancelled(active.signal);
162
+ if (!(await recordingScheduleDirectory(directory, false)))
163
+ throw new ZoomError("not_found", "archive timer units were not found");
164
+ if ((await existingUnit(join(directory, units.serviceName))) !== units.service ||
165
+ (await existingUnit(join(directory, units.timerName))) !== units.timer)
166
+ throw new ZoomError("validation_error", "archive timer units are foreign or were edited; they were preserved");
167
+ await checked(["disable", "--now", units.timerName], 0, active.signal);
168
+ await checked(["is-enabled", "--quiet", units.timerName], 1, active.signal);
169
+ await checked(["is-active", "--quiet", units.timerName], 3, active.signal);
170
+ },
171
+ };
172
+ };
173
+ //# sourceMappingURL=recording-schedule-systemd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-schedule-systemd.js","sourceRoot":"","sources":["../src/recording-schedule-systemd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { type RecordingArchiveSchedule } from "@wirecat/cli-meetings";
2
+ import { type ArchiveSyncReceipt } from "./recording-sync.js";
3
+ import type { ZoomRecordings } from "./recordings.js";
4
+ export interface RecordingScheduleCapture {
5
+ profile: string;
6
+ clientId: string;
7
+ configDir: string;
8
+ runtime: string;
9
+ cliPath: string;
10
+ }
11
+ export interface RecordingScheduleJob {
12
+ version: 1;
13
+ schedule: RecordingArchiveSchedule;
14
+ capture: RecordingScheduleCapture;
15
+ state: "pending" | "enabled" | "disabled";
16
+ }
17
+ export interface RecordingScheduleStore {
18
+ list(options: {
19
+ signal?: AbortSignal;
20
+ }): Promise<RecordingScheduleJob[]>;
21
+ load(id: string, options: {
22
+ signal?: AbortSignal;
23
+ }): Promise<RecordingScheduleJob | null>;
24
+ save(job: RecordingScheduleJob, options: {
25
+ signal?: AbortSignal;
26
+ }): Promise<void>;
27
+ }
28
+ export interface RecordingScheduleUnits {
29
+ serviceName: string;
30
+ timerName: string;
31
+ service: string;
32
+ timer: string;
33
+ }
34
+ export interface RecordingScheduleBackend {
35
+ readonly backend: string;
36
+ plan(job: RecordingScheduleJob): RecordingScheduleUnits;
37
+ assertSupported(): void;
38
+ install(job: RecordingScheduleJob, options: {
39
+ signal?: AbortSignal;
40
+ }): Promise<void>;
41
+ disable(job: RecordingScheduleJob, options: {
42
+ signal?: AbortSignal;
43
+ }): Promise<void>;
44
+ }
45
+ export interface RecordingScheduleCreateInput {
46
+ name: string;
47
+ destination: string;
48
+ from: string;
49
+ dailyAt: string;
50
+ timeZone: string;
51
+ lookbackDays?: number;
52
+ maxBytes?: number;
53
+ }
54
+ export interface RecordingScheduleReceipt {
55
+ applied: boolean;
56
+ backend: string;
57
+ job: RecordingScheduleJob;
58
+ units?: RecordingScheduleUnits;
59
+ timerState: "not_checked" | "verified_enabled" | "verified_disabled";
60
+ }
61
+ export interface RecordingScheduleService {
62
+ create(input: RecordingScheduleCreateInput, capture: RecordingScheduleCapture, options?: {
63
+ apply?: boolean;
64
+ dryRun?: boolean;
65
+ offline?: boolean;
66
+ signal?: AbortSignal;
67
+ }): Promise<RecordingScheduleReceipt>;
68
+ list(options?: {
69
+ signal?: AbortSignal;
70
+ }): Promise<RecordingScheduleJob[]>;
71
+ show(id: string, options?: {
72
+ signal?: AbortSignal;
73
+ }): Promise<RecordingScheduleJob>;
74
+ run(id: string, options?: {
75
+ offline?: boolean;
76
+ signal?: AbortSignal;
77
+ }): AsyncIterable<ArchiveSyncReceipt>;
78
+ disable(id: string, options?: {
79
+ apply?: boolean;
80
+ dryRun?: boolean;
81
+ signal?: AbortSignal;
82
+ }): Promise<RecordingScheduleReceipt>;
83
+ }
84
+ export declare const validateRecordingScheduleId: (id: string) => string;
85
+ export declare const snapshotRecordingScheduleCapture: (value: RecordingScheduleCapture) => Readonly<RecordingScheduleCapture>;
86
+ export declare const parseRecordingScheduleJob: (value: unknown) => RecordingScheduleJob;
87
+ export declare const createRecordingScheduleService: (deps: {
88
+ store: RecordingScheduleStore;
89
+ backend: RecordingScheduleBackend;
90
+ recordings: (job: RecordingScheduleJob) => Pick<ZoomRecordings, "list" | "download"> & Partial<Pick<ZoomRecordings, "catalog">>;
91
+ now?: () => number;
92
+ }) => RecordingScheduleService;
93
+ //# sourceMappingURL=recording-schedule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-schedule.d.ts","sourceRoot":"","sources":["../src/recording-schedule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { installRecordingArchiveSchedule, parseRecordingArchiveSchedule, planRecordingArchiveSchedule, recordingArchiveScheduleWindow, } from "@wirecat/cli-meetings";
3
+ import { profileAccountKey } from "./config.js";
4
+ import { checkCancelled, ZoomError } from "./errors.js";
5
+ import { syncRecordingArchive } from "./recording-sync.js";
6
+ const text = (value, limit) => typeof value === "string" && !!value.trim() && value.length <= limit && !/[\0\r\n]/.test(value);
7
+ const absolute = (value) => text(value, 4096) && isAbsolute(value);
8
+ export const validateRecordingScheduleId = (id) => {
9
+ if (typeof id !== "string" || !/^[a-f0-9]{64}$/.test(id))
10
+ throw new ZoomError("validation_error", "archive schedule id must be 64 lowercase hexadecimal characters");
11
+ return id;
12
+ };
13
+ export const snapshotRecordingScheduleCapture = (value) => {
14
+ if (!value || typeof value !== "object" || Array.isArray(value))
15
+ throw new ZoomError("validation_error", "archive schedule capture is invalid");
16
+ const { profile, clientId, configDir, runtime, cliPath } = value;
17
+ if (!text(profile, 64) ||
18
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(profile) ||
19
+ !text(clientId, 200) ||
20
+ /\s/.test(clientId) ||
21
+ !absolute(configDir) ||
22
+ !absolute(runtime) ||
23
+ !absolute(cliPath))
24
+ throw new ZoomError("validation_error", "archive schedule requires a profile, public client id and absolute config/runtime/CLI paths");
25
+ return Object.freeze({ profile, clientId, configDir, runtime, cliPath });
26
+ };
27
+ export const parseRecordingScheduleJob = (value) => {
28
+ if (!value || typeof value !== "object" || Array.isArray(value))
29
+ throw new ZoomError("validation_error", "archive schedule job is invalid");
30
+ const row = value;
31
+ if (row.version !== 1 ||
32
+ Object.keys(row).some((key) => !["version", "schedule", "capture", "state"].includes(key)) ||
33
+ !["pending", "enabled", "disabled"].includes(row.state))
34
+ throw new ZoomError("validation_error", "archive schedule job is invalid");
35
+ const capture = snapshotRecordingScheduleCapture(row.capture);
36
+ if (Object.keys(row.capture).some((key) => !["profile", "clientId", "configDir", "runtime", "cliPath"].includes(key)))
37
+ throw new ZoomError("validation_error", "archive schedule capture is invalid");
38
+ const schedule = parseRecordingArchiveSchedule(row.schedule);
39
+ if (schedule.scope.provider !== "zoom" ||
40
+ schedule.scope.account !== profileAccountKey(capture.profile) ||
41
+ !absolute(schedule.destination))
42
+ throw new ZoomError("validation_error", "archive schedule capture does not match its provider scope");
43
+ const job = Object.freeze({ version: 1, schedule, capture, state: row.state });
44
+ if (Buffer.byteLength(JSON.stringify(job)) > 32 * 1024)
45
+ throw new ZoomError("validation_error", "archive schedule metadata exceeds its byte limit");
46
+ return job;
47
+ };
48
+ export const createRecordingScheduleService = (deps) => {
49
+ const show = async (id, options = {}) => {
50
+ validateRecordingScheduleId(id);
51
+ checkCancelled(options.signal);
52
+ const found = await deps.store.load(id, options);
53
+ if (!found)
54
+ throw new ZoomError("not_found", "archive schedule was not found");
55
+ const job = parseRecordingScheduleJob(found);
56
+ if (job.schedule.id !== id)
57
+ throw new ZoomError("validation_error", "archive schedule storage returned a different job");
58
+ return job;
59
+ };
60
+ return {
61
+ async create(input, context, options = {}) {
62
+ options = { ...options };
63
+ checkCancelled(options.signal);
64
+ const capture = snapshotRecordingScheduleCapture(context);
65
+ const schedule = planRecordingArchiveSchedule({ ...input, scope: { provider: "zoom", account: profileAccountKey(capture.profile) } }, { now: deps.now?.() ?? Date.now() });
66
+ const job = parseRecordingScheduleJob({ version: 1, schedule, capture, state: "pending" });
67
+ const backend = deps.backend.backend;
68
+ const units = deps.backend.plan(job);
69
+ if (options.apply !== true || options.dryRun === true)
70
+ return { applied: false, backend, job, units, timerState: "not_checked" };
71
+ if (options.offline)
72
+ throw new ZoomError("configuration_error", "archive schedule installation requires online mode");
73
+ deps.backend.assertSupported();
74
+ const existing = await deps.store.load(schedule.id, { signal: options.signal });
75
+ if (existing && JSON.stringify(parseRecordingScheduleJob(existing).capture) !== JSON.stringify(capture))
76
+ throw new ZoomError("validation_error", "archive schedule id belongs to a different captured runtime or client; choose a new name");
77
+ await deps.store.save(job, { signal: options.signal });
78
+ await installRecordingArchiveSchedule({ backend, install: async (_, active) => deps.backend.install(job, active) }, schedule, { apply: true, signal: options.signal });
79
+ const enabled = parseRecordingScheduleJob({ ...job, state: "enabled" });
80
+ await deps.store.save(enabled, {});
81
+ return { applied: true, backend, job: enabled, units, timerState: "verified_enabled" };
82
+ },
83
+ async list(options = {}) {
84
+ checkCancelled(options.signal);
85
+ return (await deps.store.list(options)).map(parseRecordingScheduleJob);
86
+ },
87
+ show,
88
+ async *run(id, options = {}) {
89
+ options = { ...options };
90
+ if (options.offline)
91
+ throw new ZoomError("configuration_error", "archive schedule run requires online mode");
92
+ const job = await show(id, { signal: options.signal });
93
+ const window = recordingArchiveScheduleWindow(job.schedule, { now: deps.now?.() ?? Date.now() });
94
+ if (!window)
95
+ return;
96
+ const recordings = deps.recordings(job);
97
+ yield* syncRecordingArchive(recordings, {
98
+ ...window,
99
+ destination: job.schedule.destination,
100
+ maxBytes: job.schedule.maxBytes,
101
+ account: job.schedule.scope.account,
102
+ resume: true,
103
+ signal: options.signal,
104
+ now: deps.now,
105
+ });
106
+ },
107
+ async disable(id, options = {}) {
108
+ options = { ...options };
109
+ const job = await show(id, { signal: options.signal });
110
+ const backend = deps.backend.backend;
111
+ if (options.apply !== true || options.dryRun === true)
112
+ return { applied: false, backend, job, timerState: "not_checked" };
113
+ deps.backend.assertSupported();
114
+ await deps.backend.disable(job, { signal: options.signal });
115
+ const disabled = parseRecordingScheduleJob({ ...job, state: "disabled" });
116
+ await deps.store.save(disabled, {});
117
+ return { applied: true, backend, job: disabled, timerState: "verified_disabled" };
118
+ },
119
+ };
120
+ };
121
+ //# sourceMappingURL=recording-schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-schedule.js","sourceRoot":"","sources":["../src/recording-schedule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type RecordingSyncCheckpointStore } from "@wirecat/cli-meetings";
2
+ export declare function fileRecordingSyncCheckpointStore(path: string): RecordingSyncCheckpointStore;
3
+ //# sourceMappingURL=recording-sync-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-sync-store.d.ts","sourceRoot":"","sources":["../src/recording-sync-store.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,4BAA4B,EAElC,MAAM,uBAAuB,CAAA;AA6D9B,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,4BAA4B,CAiH3F"}
@@ -0,0 +1,187 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstat, mkdir, open, rename, rmdir, unlink } from "node:fs/promises";
3
+ import { hostname } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { parseRecordingSyncCheckpoint, } from "@wirecat/cli-meetings";
6
+ import { ZoomError } from "./errors.js";
7
+ import { openOrdinaryFile } from "./ordinary-file.js";
8
+ const maxBytes = 8 * 1024 ** 2;
9
+ const ownerByteLimit = 32 * 1024;
10
+ const code = (error) => error?.code;
11
+ const conflict = (message) => {
12
+ throw new ZoomError("configuration_error", message);
13
+ };
14
+ const boundedJson = async (path, limit) => {
15
+ const file = await openOrdinaryFile(path);
16
+ try {
17
+ const size = (await file.stat()).size;
18
+ if (size > limit)
19
+ throw new ZoomError("validation_error", "archive checkpoint exceeds its byte limit");
20
+ const content = Buffer.alloc(size + 1);
21
+ const { bytesRead } = await file.read(content, 0, content.length, 0);
22
+ if (bytesRead !== size || bytesRead > limit)
23
+ throw new ZoomError("validation_error", "archive checkpoint changed while reading");
24
+ try {
25
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(content.subarray(0, bytesRead)));
26
+ }
27
+ catch {
28
+ throw new ZoomError("validation_error", "archive checkpoint contains invalid JSON");
29
+ }
30
+ }
31
+ finally {
32
+ await file.close();
33
+ }
34
+ };
35
+ const ownerOf = (value) => {
36
+ if (!value || typeof value !== "object" || Array.isArray(value))
37
+ return conflict("archive checkpoint lock is unrecognized");
38
+ const row = value;
39
+ if (row.version !== 1 ||
40
+ !Number.isSafeInteger(row.pid) ||
41
+ Number(row.pid) <= 0 ||
42
+ typeof row.host !== "string" ||
43
+ !row.host ||
44
+ typeof row.token !== "string" ||
45
+ !/^[a-f0-9-]{36}$/.test(row.token) ||
46
+ typeof row.jobId !== "string" ||
47
+ !row.jobId)
48
+ return conflict("archive checkpoint lock is unrecognized");
49
+ return row;
50
+ };
51
+ const alive = (pid) => {
52
+ try {
53
+ process.kill(pid, 0);
54
+ return true;
55
+ }
56
+ catch (error) {
57
+ return code(error) !== "ESRCH";
58
+ }
59
+ };
60
+ export function fileRecordingSyncCheckpointStore(path) {
61
+ if (typeof path !== "string" || !path.trim())
62
+ throw new ZoomError("validation_error", "archive checkpoint path is required");
63
+ const checkpointPath = resolve(path);
64
+ const lockPath = `${checkpointPath}.lock`;
65
+ const marker = join(lockPath, "owner.json");
66
+ const recoveryPath = `${lockPath}.recovery`;
67
+ const owners = new Map();
68
+ const readOwner = async () => {
69
+ try {
70
+ return ownerOf(await boundedJson(marker, ownerByteLimit));
71
+ }
72
+ catch {
73
+ return conflict("archive checkpoint lock is missing, incomplete or unrecognized");
74
+ }
75
+ };
76
+ const own = async (jobId, token) => {
77
+ const held = owners.get(token);
78
+ if (!held || held.jobId !== jobId)
79
+ return conflict("archive checkpoint lease is not owned");
80
+ const actual = await readOwner();
81
+ if (actual.token !== token || actual.jobId !== jobId || actual.host !== held.host || actual.pid !== held.pid)
82
+ return conflict("archive checkpoint lease changed");
83
+ };
84
+ const read = async () => {
85
+ try {
86
+ return parseRecordingSyncCheckpoint(await boundedJson(checkpointPath, maxBytes));
87
+ }
88
+ catch (error) {
89
+ if (code(error) === "ENOENT")
90
+ return null;
91
+ throw error;
92
+ }
93
+ };
94
+ return {
95
+ async acquire(jobId) {
96
+ if (typeof jobId !== "string" || !jobId.trim() || jobId.length > 4096)
97
+ throw new ZoomError("validation_error", "archive checkpoint job id is required");
98
+ await mkdir(dirname(checkpointPath), { recursive: true, mode: 0o700 });
99
+ try {
100
+ await mkdir(lockPath, { mode: 0o700 });
101
+ }
102
+ catch (error) {
103
+ if (code(error) !== "EEXIST")
104
+ throw error;
105
+ const folder = await lstat(lockPath);
106
+ if (!folder.isDirectory() || folder.isSymbolicLink())
107
+ return conflict("archive checkpoint lock must be an ordinary directory");
108
+ try {
109
+ await mkdir(recoveryPath, { mode: 0o700 });
110
+ }
111
+ catch (error) {
112
+ if (code(error) === "EEXIST")
113
+ return conflict("archive checkpoint recovery is already active");
114
+ throw error;
115
+ }
116
+ try {
117
+ const prior = await readOwner();
118
+ if (prior.host !== hostname() || alive(prior.pid))
119
+ return conflict("archive checkpoint job is already active or owned by another host");
120
+ const verified = await readOwner();
121
+ if (verified.token !== prior.token)
122
+ return conflict("archive checkpoint lock changed during stale recovery");
123
+ await unlink(marker);
124
+ await rmdir(lockPath);
125
+ await mkdir(lockPath, { mode: 0o700 });
126
+ }
127
+ finally {
128
+ await rmdir(recoveryPath);
129
+ }
130
+ }
131
+ const owner = { version: 1, pid: process.pid, host: hostname(), token: randomUUID(), jobId };
132
+ const file = await open(marker, "wx", 0o600);
133
+ try {
134
+ await file.writeFile(JSON.stringify(owner));
135
+ await file.sync();
136
+ }
137
+ finally {
138
+ await file.close();
139
+ }
140
+ owners.set(owner.token, owner);
141
+ return {
142
+ token: owner.token,
143
+ async release() {
144
+ await own(jobId, owner.token);
145
+ await unlink(marker);
146
+ await rmdir(lockPath);
147
+ owners.delete(owner.token);
148
+ },
149
+ };
150
+ },
151
+ async read(jobId, token) {
152
+ await own(jobId, token);
153
+ return read();
154
+ },
155
+ async save(jobId, token, expectedRevision, value) {
156
+ await own(jobId, token);
157
+ const state = parseRecordingSyncCheckpoint(value);
158
+ const current = await read();
159
+ if ((current?.revision ?? null) !== expectedRevision || state.revision !== (expectedRevision ?? 0) + 1)
160
+ return conflict("archive checkpoint revision changed");
161
+ const content = JSON.stringify(state);
162
+ if (Buffer.byteLength(content) > maxBytes)
163
+ throw new ZoomError("validation_error", "archive checkpoint exceeds its byte limit");
164
+ const temporary = `${checkpointPath}.${token}.tmp`;
165
+ const file = await open(temporary, "wx", 0o600);
166
+ try {
167
+ await file.writeFile(content);
168
+ await file.sync();
169
+ }
170
+ finally {
171
+ await file.close();
172
+ }
173
+ await own(jobId, token);
174
+ await rename(temporary, checkpointPath);
175
+ if (process.platform !== "win32") {
176
+ const directory = await open(dirname(checkpointPath), "r");
177
+ try {
178
+ await directory.sync();
179
+ }
180
+ finally {
181
+ await directory.close();
182
+ }
183
+ }
184
+ },
185
+ };
186
+ }
187
+ //# sourceMappingURL=recording-sync-store.js.map