@miosa/sdk 1.2.5 → 1.2.6
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/README.md +107 -59
- package/dist/index.d.ts +331 -202
- package/dist/index.js +572 -603
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/client.ts +0 -293
- package/src/errors.ts +0 -136
- package/src/http.test.ts +0 -374
- package/src/http.ts +0 -390
- package/src/index.ts +0 -569
- package/src/resources/admin.ts +0 -348
- package/src/resources/analytics.ts +0 -60
- package/src/resources/api-keys.ts +0 -119
- package/src/resources/audit-log.ts +0 -64
- package/src/resources/benchmarks.ts +0 -104
- package/src/resources/builder-sessions.ts +0 -75
- package/src/resources/channels.ts +0 -143
- package/src/resources/checkpoints.ts +0 -225
- package/src/resources/command-center.ts +0 -73
- package/src/resources/community.ts +0 -103
- package/src/resources/completions.ts +0 -104
- package/src/resources/computer-auto-stop.ts +0 -43
- package/src/resources/computer-env.ts +0 -76
- package/src/resources/computer-logs.ts +0 -50
- package/src/resources/computer-osa.ts +0 -76
- package/src/resources/computer-ports.ts +0 -91
- package/src/resources/computer-terminal.ts +0 -61
- package/src/resources/computer-volumes.ts +0 -64
- package/src/resources/computer.ts +0 -551
- package/src/resources/computers.ts +0 -75
- package/src/resources/credits.ts +0 -43
- package/src/resources/cron-jobs.ts +0 -191
- package/src/resources/custom_domains.ts +0 -123
- package/src/resources/dashboard.ts +0 -49
- package/src/resources/databases.ts +0 -218
- package/src/resources/deployments.test.ts +0 -197
- package/src/resources/deployments.ts +0 -1208
- package/src/resources/desktop.ts +0 -134
- package/src/resources/devices.test.ts +0 -92
- package/src/resources/devices.ts +0 -291
- package/src/resources/egress.test.ts +0 -318
- package/src/resources/egressAudit.ts +0 -245
- package/src/resources/egressNetwork.ts +0 -450
- package/src/resources/egressSecrets.ts +0 -577
- package/src/resources/email.ts +0 -212
- package/src/resources/embeddings.ts +0 -36
- package/src/resources/events.ts +0 -296
- package/src/resources/exec.ts +0 -319
- package/src/resources/external-keys.ts +0 -79
- package/src/resources/files.test.ts +0 -339
- package/src/resources/files.ts +0 -220
- package/src/resources/flat-custom-domains.ts +0 -127
- package/src/resources/functions.ts +0 -178
- package/src/resources/health-checks.ts +0 -165
- package/src/resources/integrations.ts +0 -183
- package/src/resources/mcp.ts +0 -70
- package/src/resources/models.ts +0 -45
- package/src/resources/network_policy.ts +0 -73
- package/src/resources/open-computers/agents.ts +0 -91
- package/src/resources/open-computers/apps.ts +0 -102
- package/src/resources/open-computers/clusters.ts +0 -88
- package/src/resources/open-computers/desktop.ts +0 -34
- package/src/resources/open-computers/files.ts +0 -97
- package/src/resources/open-computers/hosts.ts +0 -85
- package/src/resources/open-computers/index.ts +0 -98
- package/src/resources/open-computers/jobs.ts +0 -75
- package/src/resources/open-computers/open_computers.test.ts +0 -288
- package/src/resources/open-computers/secrets.ts +0 -115
- package/src/resources/open-computers/terminal.ts +0 -33
- package/src/resources/open-computers/tunnels.ts +0 -87
- package/src/resources/open-computers/types.ts +0 -343
- package/src/resources/open-computers/workspaces.ts +0 -135
- package/src/resources/org-invites.ts +0 -189
- package/src/resources/project-auth.ts +0 -142
- package/src/resources/project-integrations.ts +0 -133
- package/src/resources/provider-defaults.ts +0 -89
- package/src/resources/regions.ts +0 -94
- package/src/resources/sandbox-templates.ts +0 -195
- package/src/resources/sandboxes.live.test.ts +0 -92
- package/src/resources/sandboxes.test.ts +0 -655
- package/src/resources/sandboxes.ts +0 -1437
- package/src/resources/settings.ts +0 -143
- package/src/resources/snapshots-standalone.ts +0 -51
- package/src/resources/storage.ts +0 -221
- package/src/resources/tenant.ts +0 -66
- package/src/resources/usage.ts +0 -85
- package/src/resources/volumes.ts +0 -117
- package/src/resources/webhooks.ts +0 -239
- package/src/resources/workspace-invites.ts +0 -188
- package/src/resources/workspace-members.test.ts +0 -121
- package/src/resources/workspace-members.ts +0 -143
- package/src/types.ts +0 -463
package/src/resources/email.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email — admin email campaigns, templates, and inbox surfaces.
|
|
3
|
-
*
|
|
4
|
-
* Sub-namespaces:
|
|
5
|
-
* client.email.campaigns — bulk email send-out lifecycle
|
|
6
|
-
* client.email.templates — reusable templates (keyed by name)
|
|
7
|
-
* client.email.inbox — inbound + outbound direct messages
|
|
8
|
-
*
|
|
9
|
-
* Routes: /admin/email-{campaigns,templates,inbox}/*
|
|
10
|
-
* Requires admin credential (msk_a_* / msk_p_* or admin JWT).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { HttpClient } from "../http.js";
|
|
14
|
-
|
|
15
|
-
function unwrap(data: unknown): Record<string, unknown> {
|
|
16
|
-
if (data && typeof data === "object") {
|
|
17
|
-
const d = data as Record<string, unknown>;
|
|
18
|
-
for (const k of [
|
|
19
|
-
"data",
|
|
20
|
-
"campaigns",
|
|
21
|
-
"templates",
|
|
22
|
-
"inbox",
|
|
23
|
-
"deliveries",
|
|
24
|
-
"items",
|
|
25
|
-
]) {
|
|
26
|
-
if (k in d) return d[k] as Record<string, unknown>;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return data as Record<string, unknown>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function unwrapList(data: unknown): Record<string, unknown>[] {
|
|
33
|
-
if (Array.isArray(data)) return data as Record<string, unknown>[];
|
|
34
|
-
if (data && typeof data === "object") {
|
|
35
|
-
const d = data as Record<string, unknown>;
|
|
36
|
-
for (const k of [
|
|
37
|
-
"data",
|
|
38
|
-
"campaigns",
|
|
39
|
-
"templates",
|
|
40
|
-
"inbox",
|
|
41
|
-
"deliveries",
|
|
42
|
-
"items",
|
|
43
|
-
]) {
|
|
44
|
-
if (Array.isArray(d[k])) return d[k] as Record<string, unknown>[];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function strip(obj: Record<string, unknown>): Record<string, unknown> {
|
|
51
|
-
return Object.fromEntries(
|
|
52
|
-
Object.entries(obj).filter(([, v]) => v !== undefined),
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ── Campaigns ──────────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
export class EmailCampaigns {
|
|
59
|
-
constructor(private readonly http: HttpClient) {}
|
|
60
|
-
|
|
61
|
-
async list(
|
|
62
|
-
filters: Record<string, string | number | boolean | undefined> = {},
|
|
63
|
-
): Promise<Record<string, unknown>[]> {
|
|
64
|
-
return unwrapList(
|
|
65
|
-
await this.http.get<unknown>(
|
|
66
|
-
"/admin/email-campaigns",
|
|
67
|
-
filters as Record<string, string | number | boolean | undefined>,
|
|
68
|
-
),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async create(
|
|
73
|
-
attrs: Record<string, unknown>,
|
|
74
|
-
): Promise<Record<string, unknown>> {
|
|
75
|
-
return unwrap(
|
|
76
|
-
await this.http.post<unknown>("/admin/email-campaigns", strip(attrs)),
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async recipientCount(
|
|
81
|
-
filters: Record<string, string | number | boolean | undefined> = {},
|
|
82
|
-
): Promise<Record<string, unknown>> {
|
|
83
|
-
return unwrap(
|
|
84
|
-
await this.http.get<unknown>(
|
|
85
|
-
"/admin/email-campaigns/recipient-count",
|
|
86
|
-
filters,
|
|
87
|
-
),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async send(
|
|
92
|
-
campaignId: string,
|
|
93
|
-
opts: Record<string, unknown> = {},
|
|
94
|
-
): Promise<Record<string, unknown>> {
|
|
95
|
-
return unwrap(
|
|
96
|
-
await this.http.post<unknown>(
|
|
97
|
-
`/admin/email-campaigns/${campaignId}/send`,
|
|
98
|
-
strip(opts),
|
|
99
|
-
),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async cancel(campaignId: string): Promise<Record<string, unknown>> {
|
|
104
|
-
return unwrap(
|
|
105
|
-
await this.http.post<unknown>(
|
|
106
|
-
`/admin/email-campaigns/${campaignId}/cancel`,
|
|
107
|
-
),
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async deliveries(
|
|
112
|
-
campaignId: string,
|
|
113
|
-
filters: Record<string, string | number | boolean | undefined> = {},
|
|
114
|
-
): Promise<Record<string, unknown>[]> {
|
|
115
|
-
return unwrapList(
|
|
116
|
-
await this.http.get<unknown>(
|
|
117
|
-
`/admin/email-campaigns/${campaignId}/deliveries`,
|
|
118
|
-
filters,
|
|
119
|
-
),
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ── Templates ──────────────────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
export class EmailTemplates {
|
|
127
|
-
constructor(private readonly http: HttpClient) {}
|
|
128
|
-
|
|
129
|
-
async list(
|
|
130
|
-
filters: Record<string, string | number | boolean | undefined> = {},
|
|
131
|
-
): Promise<Record<string, unknown>[]> {
|
|
132
|
-
return unwrapList(
|
|
133
|
-
await this.http.get<unknown>("/admin/email-templates", filters),
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async create(
|
|
138
|
-
key: string,
|
|
139
|
-
attrs: Record<string, unknown> = {},
|
|
140
|
-
): Promise<Record<string, unknown>> {
|
|
141
|
-
return unwrap(
|
|
142
|
-
await this.http.post<unknown>("/admin/email-templates", {
|
|
143
|
-
key,
|
|
144
|
-
...strip(attrs),
|
|
145
|
-
}),
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async update(
|
|
150
|
-
key: string,
|
|
151
|
-
attrs: Record<string, unknown>,
|
|
152
|
-
): Promise<Record<string, unknown>> {
|
|
153
|
-
return unwrap(
|
|
154
|
-
await this.http.put<unknown>(
|
|
155
|
-
`/admin/email-templates/${key}`,
|
|
156
|
-
strip(attrs),
|
|
157
|
-
),
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async reset(key: string): Promise<Record<string, unknown>> {
|
|
162
|
-
return unwrap(
|
|
163
|
-
await this.http.post<unknown>(`/admin/email-templates/${key}/reset`),
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ── Inbox ──────────────────────────────────────────────────────────────────
|
|
169
|
-
|
|
170
|
-
export class EmailInbox {
|
|
171
|
-
constructor(private readonly http: HttpClient) {}
|
|
172
|
-
|
|
173
|
-
async list(
|
|
174
|
-
filters: Record<string, string | number | boolean | undefined> = {},
|
|
175
|
-
): Promise<Record<string, unknown>[]> {
|
|
176
|
-
return unwrapList(
|
|
177
|
-
await this.http.get<unknown>("/admin/email-inbox", filters),
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async send(attrs: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
182
|
-
return unwrap(
|
|
183
|
-
await this.http.post<unknown>("/admin/email-inbox/send", strip(attrs)),
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async markRead(messageId: string): Promise<Record<string, unknown>> {
|
|
188
|
-
return unwrap(
|
|
189
|
-
await this.http.post<unknown>(`/admin/email-inbox/${messageId}/read`),
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async archive(messageId: string): Promise<Record<string, unknown>> {
|
|
194
|
-
return unwrap(
|
|
195
|
-
await this.http.post<unknown>(`/admin/email-inbox/${messageId}/archive`),
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ── Top-level facade ────────────────────────────────────────────────────────
|
|
201
|
-
|
|
202
|
-
export class Email {
|
|
203
|
-
readonly campaigns: EmailCampaigns;
|
|
204
|
-
readonly templates: EmailTemplates;
|
|
205
|
-
readonly inbox: EmailInbox;
|
|
206
|
-
|
|
207
|
-
constructor(http: HttpClient) {
|
|
208
|
-
this.campaigns = new EmailCampaigns(http);
|
|
209
|
-
this.templates = new EmailTemplates(http);
|
|
210
|
-
this.inbox = new EmailInbox(http);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Embeddings — OpenAI-compatible embedding vectors.
|
|
3
|
-
*
|
|
4
|
-
* Route: POST /intelligence/embeddings
|
|
5
|
-
* Requires an mki_* intelligence key.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { HttpClient } from "../http.js";
|
|
9
|
-
|
|
10
|
-
export interface EmbeddingCreateParams {
|
|
11
|
-
input: string | string[];
|
|
12
|
-
model: string;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class Embeddings {
|
|
17
|
-
constructor(private readonly http: HttpClient) {}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create one or more embedding vectors.
|
|
21
|
-
*
|
|
22
|
-
* Returns the full OpenAI-compatible envelope
|
|
23
|
-
* `{ object: "list", data: [...], model, usage }`.
|
|
24
|
-
*/
|
|
25
|
-
async create(
|
|
26
|
-
params: EmbeddingCreateParams,
|
|
27
|
-
): Promise<Record<string, unknown>> {
|
|
28
|
-
const body = Object.fromEntries(
|
|
29
|
-
Object.entries(params).filter(([, v]) => v !== undefined),
|
|
30
|
-
);
|
|
31
|
-
return this.http.post<Record<string, unknown>>(
|
|
32
|
-
"/intelligence/embeddings",
|
|
33
|
-
body,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/resources/events.ts
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
|
|
3
|
-
// ── Types ────────────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
/** Producer names that can be subscribed to. */
|
|
6
|
-
export type EventProducer =
|
|
7
|
-
| "window"
|
|
8
|
-
| "clipboard"
|
|
9
|
-
| "file"
|
|
10
|
-
| "process"
|
|
11
|
-
| "idle";
|
|
12
|
-
|
|
13
|
-
/** Options for `Events.subscribe()`. */
|
|
14
|
-
export interface EventSubscribeOptions {
|
|
15
|
-
/** At least one producer is required. */
|
|
16
|
-
subscribe: EventProducer[];
|
|
17
|
-
/** Paths to watch for the `file` producer. Defaults to `["/home/user"]`. */
|
|
18
|
-
paths?: string[];
|
|
19
|
-
/** Idle threshold in seconds for the `idle` producer. Defaults to 30. */
|
|
20
|
-
idleThresholdSec?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ── Emitted event payload types (indexed by event type string) ────────────────
|
|
24
|
-
|
|
25
|
-
export interface WindowFocusChangedPayload {
|
|
26
|
-
window_id: string;
|
|
27
|
-
pid: string;
|
|
28
|
-
title: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface WindowOpenedPayload {
|
|
32
|
-
window_id: string;
|
|
33
|
-
pid: string;
|
|
34
|
-
title: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface WindowClosedPayload {
|
|
38
|
-
window_id: string;
|
|
39
|
-
pid: string;
|
|
40
|
-
title: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface ClipboardChangedPayload {
|
|
44
|
-
size_bytes: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface FileCreatedPayload {
|
|
48
|
-
path: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface FileModifiedPayload {
|
|
52
|
-
path: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface FileDeletedPayload {
|
|
56
|
-
path: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface ProcessStartedPayload {
|
|
60
|
-
pid: number;
|
|
61
|
-
cmd: string;
|
|
62
|
-
ppid: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface ProcessStoppedPayload {
|
|
66
|
-
pid: number;
|
|
67
|
-
cmd: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface IdleInactivePayload {
|
|
71
|
-
idle_ms: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface IdleActivePayload {
|
|
75
|
-
idle_ms: number;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface ProducerUnavailablePayload {
|
|
79
|
-
producer: EventProducer;
|
|
80
|
-
reason: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Union of all typed event envelopes. */
|
|
84
|
-
export type ComputerEvent =
|
|
85
|
-
| {
|
|
86
|
-
type: "window.focus_changed";
|
|
87
|
-
timestamp: string;
|
|
88
|
-
payload: WindowFocusChangedPayload;
|
|
89
|
-
}
|
|
90
|
-
| { type: "window.opened"; timestamp: string; payload: WindowOpenedPayload }
|
|
91
|
-
| { type: "window.closed"; timestamp: string; payload: WindowClosedPayload }
|
|
92
|
-
| {
|
|
93
|
-
type: "clipboard.changed";
|
|
94
|
-
timestamp: string;
|
|
95
|
-
payload: ClipboardChangedPayload;
|
|
96
|
-
}
|
|
97
|
-
| { type: "file.created"; timestamp: string; payload: FileCreatedPayload }
|
|
98
|
-
| { type: "file.modified"; timestamp: string; payload: FileModifiedPayload }
|
|
99
|
-
| { type: "file.deleted"; timestamp: string; payload: FileDeletedPayload }
|
|
100
|
-
| {
|
|
101
|
-
type: "process.started";
|
|
102
|
-
timestamp: string;
|
|
103
|
-
payload: ProcessStartedPayload;
|
|
104
|
-
}
|
|
105
|
-
| {
|
|
106
|
-
type: "process.stopped";
|
|
107
|
-
timestamp: string;
|
|
108
|
-
payload: ProcessStoppedPayload;
|
|
109
|
-
}
|
|
110
|
-
| { type: "idle.inactive"; timestamp: string; payload: IdleInactivePayload }
|
|
111
|
-
| { type: "idle.active"; timestamp: string; payload: IdleActivePayload }
|
|
112
|
-
| {
|
|
113
|
-
type: "producer.unavailable";
|
|
114
|
-
timestamp: string;
|
|
115
|
-
payload: ProducerUnavailablePayload;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// ── EventStream ───────────────────────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
type EventMap = {
|
|
121
|
-
[K in ComputerEvent["type"]]: [
|
|
122
|
-
Extract<ComputerEvent, { type: K }>["payload"],
|
|
123
|
-
];
|
|
124
|
-
} & {
|
|
125
|
-
error: [Error];
|
|
126
|
-
close: [{ code: number; reason: string }];
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Typed event stream returned by `computer.events.subscribe()`.
|
|
131
|
-
*
|
|
132
|
-
* ```ts
|
|
133
|
-
* const stream = computer.events.subscribe({ subscribe: ["file", "process"] });
|
|
134
|
-
* stream.on("file.created", e => console.log("created:", e.path));
|
|
135
|
-
* stream.on("process.started", e => console.log("pid:", e.pid));
|
|
136
|
-
* stream.on("error", err => console.error(err));
|
|
137
|
-
* await stream.close();
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export class EventStream extends EventEmitter<EventMap> {
|
|
141
|
-
private ws: WebSocket | import("ws").WebSocket | null = null;
|
|
142
|
-
private closed = false;
|
|
143
|
-
|
|
144
|
-
/** @internal — use `Events.subscribe()` instead. */
|
|
145
|
-
constructor(ws: WebSocket | import("ws").WebSocket) {
|
|
146
|
-
super();
|
|
147
|
-
this.ws = ws;
|
|
148
|
-
this.attachHandlers();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private attachHandlers() {
|
|
152
|
-
if (!this.ws) return;
|
|
153
|
-
const ws = this.ws;
|
|
154
|
-
|
|
155
|
-
ws.onmessage = (ev: MessageEvent) => {
|
|
156
|
-
try {
|
|
157
|
-
const raw = typeof ev.data === "string" ? ev.data : String(ev.data);
|
|
158
|
-
const event = JSON.parse(raw) as ComputerEvent;
|
|
159
|
-
// EventEmitter.emit requires (event, ...args) — payload as first arg.
|
|
160
|
-
this.emit(event.type as keyof EventMap, event.payload as never);
|
|
161
|
-
} catch {
|
|
162
|
-
// Ignore malformed frames.
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
ws.onerror = (ev: Event) => {
|
|
167
|
-
const err =
|
|
168
|
-
ev instanceof ErrorEvent ? ev.error : new Error("WebSocket error");
|
|
169
|
-
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
ws.onclose = (ev: CloseEvent) => {
|
|
173
|
-
this.closed = true;
|
|
174
|
-
this.emit("close", { code: ev.code, reason: ev.reason });
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Close the event stream. Idempotent. */
|
|
179
|
-
async close(): Promise<void> {
|
|
180
|
-
if (this.closed || !this.ws) return;
|
|
181
|
-
this.closed = true;
|
|
182
|
-
this.ws.close(1000, "client closed");
|
|
183
|
-
this.ws = null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Whether the stream has been closed (by client or server). */
|
|
187
|
-
get isClosed(): boolean {
|
|
188
|
-
return this.closed;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ── Events resource ───────────────────────────────────────────────────────────
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Events resource attached to every `Computer` instance.
|
|
196
|
-
* Exposes `subscribe()` for real-time in-VM event streaming.
|
|
197
|
-
*/
|
|
198
|
-
export class Events {
|
|
199
|
-
private readonly baseUrl: string;
|
|
200
|
-
private readonly apiKey: string;
|
|
201
|
-
private readonly computerId: string;
|
|
202
|
-
|
|
203
|
-
constructor(baseUrl: string, apiKey: string, computerId: string) {
|
|
204
|
-
this.baseUrl = baseUrl;
|
|
205
|
-
this.apiKey = apiKey;
|
|
206
|
-
this.computerId = computerId;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Open a WebSocket subscription for the specified producers.
|
|
211
|
-
*
|
|
212
|
-
* ```ts
|
|
213
|
-
* const stream = computer.events.subscribe({
|
|
214
|
-
* subscribe: ["window", "clipboard", "file"],
|
|
215
|
-
* paths: ["/home/user", "/workspace"],
|
|
216
|
-
* });
|
|
217
|
-
* stream.on("window.focus_changed", e => ...);
|
|
218
|
-
* stream.on("file.created", e => ...);
|
|
219
|
-
* stream.on("error", err => ...);
|
|
220
|
-
* await stream.close();
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
|
-
subscribe(options: EventSubscribeOptions): EventStream {
|
|
224
|
-
const wsUrl = this.buildWsUrl(options);
|
|
225
|
-
const ws = createWebSocket(wsUrl, this.apiKey);
|
|
226
|
-
return new EventStream(ws);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private buildWsUrl(options: EventSubscribeOptions): string {
|
|
230
|
-
// Convert the HTTP base URL to a WS URL.
|
|
231
|
-
// baseUrl is something like "https://api.miosa.ai/api/v1"
|
|
232
|
-
const wsBase = this.baseUrl
|
|
233
|
-
.replace(/^https:\/\//, "wss://")
|
|
234
|
-
.replace(/^http:\/\//, "ws://")
|
|
235
|
-
.replace(/\/$/, "");
|
|
236
|
-
|
|
237
|
-
const params = new URLSearchParams();
|
|
238
|
-
params.set("subscribe", options.subscribe.join(","));
|
|
239
|
-
|
|
240
|
-
if (options.paths && options.paths.length > 0) {
|
|
241
|
-
params.set("paths", options.paths.join(","));
|
|
242
|
-
}
|
|
243
|
-
if (options.idleThresholdSec !== undefined) {
|
|
244
|
-
params.set("idle_threshold_sec", String(options.idleThresholdSec));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return `${wsBase}/computers/${this.computerId}/events?${params.toString()}`;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ── WebSocket factory (Node vs browser) ──────────────────────────────────────
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Creates a WebSocket connection. Uses the native browser `WebSocket` when
|
|
255
|
-
* running in a browser context, or the `ws` npm package in Node.js.
|
|
256
|
-
*
|
|
257
|
-
* The `Authorization: Bearer <key>` header is attached via a subprotocol
|
|
258
|
-
* trick in the browser (WS spec doesn't allow custom headers) — the server
|
|
259
|
-
* accepts the key in the `Sec-WebSocket-Protocol` header as a fallback.
|
|
260
|
-
* In Node.js the `Authorization` header is set directly.
|
|
261
|
-
*/
|
|
262
|
-
/**
|
|
263
|
-
* Creates a WebSocket connection with `Sec-WebSocket-Protocol: miosa-events-v1`.
|
|
264
|
-
*
|
|
265
|
-
* The subprotocol must be negotiated for the server to accept the connection.
|
|
266
|
-
* Future protocol versions (miosa-events-v2, etc.) can be added server-side
|
|
267
|
-
* without breaking v1 clients.
|
|
268
|
-
*
|
|
269
|
-
* In the browser the `Authorization` header cannot be set directly — the API
|
|
270
|
-
* key is passed as a secondary subprotocol entry (`bearer.<key>`). The server
|
|
271
|
-
* echoes only `miosa-events-v1` back.
|
|
272
|
-
*
|
|
273
|
-
* In Node.js the `Authorization` header is set directly.
|
|
274
|
-
*/
|
|
275
|
-
function createWebSocket(
|
|
276
|
-
url: string,
|
|
277
|
-
apiKey: string,
|
|
278
|
-
): WebSocket | import("ws").WebSocket {
|
|
279
|
-
const protocol = "miosa-events-v1";
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
typeof window !== "undefined" &&
|
|
283
|
-
typeof window.WebSocket !== "undefined"
|
|
284
|
-
) {
|
|
285
|
-
// Browser: real protocol first, then auth fallback subprotocol.
|
|
286
|
-
return new window.WebSocket(url, [protocol, `bearer.${apiKey}`]);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Node.js: use the `ws` package with a proper Authorization header.
|
|
290
|
-
// Dynamic require so bundlers targeting browsers don't break.
|
|
291
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
292
|
-
const WS = require("ws") as typeof import("ws").WebSocket;
|
|
293
|
-
return new WS(url, [protocol], {
|
|
294
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
295
|
-
});
|
|
296
|
-
}
|