@meshagent/meshagent 0.35.5 → 0.35.7
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/CHANGELOG.md +16 -0
- package/dist/browser/containers-client.d.ts +79 -2
- package/dist/browser/containers-client.js +341 -19
- package/dist/browser/database-client.d.ts +95 -24
- package/dist/browser/database-client.js +150 -49
- package/dist/browser/messaging-client.d.ts +33 -52
- package/dist/browser/messaging-client.js +180 -184
- package/dist/browser/participant.d.ts +5 -3
- package/dist/browser/participant.js +9 -1
- package/dist/browser/room-client.js +2 -0
- package/dist/browser/room-event.d.ts +6 -2
- package/dist/browser/room-event.js +4 -2
- package/dist/browser/secrets-client.d.ts +86 -16
- package/dist/browser/secrets-client.js +243 -44
- package/dist/browser/storage-client.d.ts +17 -4
- package/dist/browser/storage-client.js +141 -30
- package/dist/esm/containers-client.d.ts +79 -2
- package/dist/esm/containers-client.js +341 -19
- package/dist/esm/database-client.d.ts +95 -24
- package/dist/esm/database-client.js +150 -49
- package/dist/esm/messaging-client.d.ts +33 -52
- package/dist/esm/messaging-client.js +179 -180
- package/dist/esm/participant.d.ts +5 -3
- package/dist/esm/participant.js +9 -1
- package/dist/esm/room-client.js +2 -0
- package/dist/esm/room-event.d.ts +6 -2
- package/dist/esm/room-event.js +4 -2
- package/dist/esm/secrets-client.d.ts +86 -16
- package/dist/esm/secrets-client.js +243 -44
- package/dist/esm/storage-client.d.ts +17 -4
- package/dist/esm/storage-client.js +140 -30
- package/dist/node/containers-client.d.ts +79 -2
- package/dist/node/containers-client.js +341 -19
- package/dist/node/database-client.d.ts +95 -24
- package/dist/node/database-client.js +150 -49
- package/dist/node/messaging-client.d.ts +33 -52
- package/dist/node/messaging-client.js +180 -184
- package/dist/node/participant.d.ts +5 -3
- package/dist/node/participant.js +9 -1
- package/dist/node/room-client.js +2 -0
- package/dist/node/room-event.d.ts +6 -2
- package/dist/node/room-event.js +4 -2
- package/dist/node/secrets-client.d.ts +86 -16
- package/dist/node/secrets-client.js +243 -44
- package/dist/node/storage-client.d.ts +17 -4
- package/dist/node/storage-client.js +141 -30
- package/package.json +1 -1
|
@@ -1,82 +1,281 @@
|
|
|
1
1
|
import { BinaryContent, EmptyContent, FileContent, JsonContent } from "./response";
|
|
2
|
+
import { RoomServerException } from "./room-server-client";
|
|
3
|
+
import { unpackMessage } from "./utils";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
2
7
|
export class SecretsClient {
|
|
3
|
-
constructor({ room }) {
|
|
8
|
+
constructor({ room, oauthTokenRequestHandler, secretRequestHandler, }) {
|
|
9
|
+
this._oauthRequestHandler = this._handleClientOAuthTokenRequest.bind(this);
|
|
10
|
+
this._secretRequestHandler = this._handleClientSecretRequest.bind(this);
|
|
4
11
|
this.client = room;
|
|
12
|
+
this.oauthTokenRequestHandler = oauthTokenRequestHandler;
|
|
13
|
+
this.secretRequestHandler = secretRequestHandler;
|
|
14
|
+
this.client.protocol.addHandler("secrets.request_oauth_token", this._oauthRequestHandler);
|
|
15
|
+
this.client.protocol.addHandler("secrets.request_secret", this._secretRequestHandler);
|
|
5
16
|
}
|
|
6
17
|
unexpectedResponse(operation) {
|
|
7
|
-
return new
|
|
18
|
+
return new RoomServerException(`unexpected return type from secrets.${operation}`);
|
|
8
19
|
}
|
|
9
|
-
async
|
|
10
|
-
|
|
20
|
+
async invoke(operation, input) {
|
|
21
|
+
return await this.client.invoke({
|
|
11
22
|
toolkit: "secrets",
|
|
12
|
-
tool:
|
|
13
|
-
input
|
|
14
|
-
data,
|
|
15
|
-
headers: {
|
|
16
|
-
secret_id: secretId,
|
|
17
|
-
type: mimeType ?? null,
|
|
18
|
-
name: name ?? null,
|
|
19
|
-
delegated_to: delegatedTo ?? null,
|
|
20
|
-
for_identity: forIdentity ?? null,
|
|
21
|
-
has_data: true,
|
|
22
|
-
},
|
|
23
|
-
}),
|
|
23
|
+
tool: operation,
|
|
24
|
+
input,
|
|
24
25
|
});
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
}
|
|
27
|
+
serializeConnectorRef(connector) {
|
|
28
|
+
if (connector == null) {
|
|
29
|
+
return null;
|
|
27
30
|
}
|
|
28
|
-
|
|
31
|
+
return {
|
|
32
|
+
openai_connector_id: connector.openaiConnectorId ?? null,
|
|
33
|
+
server_url: connector.serverUrl ?? null,
|
|
34
|
+
client_secret_id: connector.clientSecretId ?? null,
|
|
35
|
+
};
|
|
29
36
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
serializeOAuthConfig(oauth) {
|
|
38
|
+
if (oauth == null) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
client_id: oauth.client_id,
|
|
43
|
+
client_secret: oauth.client_secret ?? null,
|
|
44
|
+
authorization_endpoint: oauth.authorization_endpoint,
|
|
45
|
+
token_endpoint: oauth.token_endpoint,
|
|
46
|
+
no_pkce: oauth.no_pkce ?? null,
|
|
47
|
+
scopes: oauth.scopes ?? null,
|
|
36
48
|
};
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
}
|
|
50
|
+
parseAccessToken(operation, response) {
|
|
51
|
+
if (!(response instanceof JsonContent)) {
|
|
52
|
+
throw this.unexpectedResponse(operation);
|
|
53
|
+
}
|
|
54
|
+
const token = response.json["access_token"];
|
|
55
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
39
56
|
return null;
|
|
40
57
|
}
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
return token;
|
|
59
|
+
}
|
|
60
|
+
parseSecretInfo(value) {
|
|
61
|
+
if (!isRecord(value) || typeof value["id"] !== "string" || typeof value["name"] !== "string" || typeof value["type"] !== "string") {
|
|
62
|
+
throw this.unexpectedResponse("list_secrets");
|
|
43
63
|
}
|
|
44
|
-
|
|
64
|
+
const delegatedTo = value["delegated_to"];
|
|
65
|
+
if (delegatedTo !== undefined && delegatedTo !== null && typeof delegatedTo !== "string") {
|
|
66
|
+
throw this.unexpectedResponse("list_secrets");
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
id: value["id"],
|
|
70
|
+
name: value["name"],
|
|
71
|
+
type: value["type"],
|
|
72
|
+
delegatedTo: delegatedTo,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async _handleClientOAuthTokenRequest(protocol, messageId, type, bytes) {
|
|
76
|
+
void protocol;
|
|
77
|
+
void messageId;
|
|
78
|
+
void type;
|
|
79
|
+
if (bytes == null) {
|
|
80
|
+
throw new RoomServerException("invalid secrets.request_oauth_token payload");
|
|
81
|
+
}
|
|
82
|
+
if (this.oauthTokenRequestHandler == null) {
|
|
83
|
+
throw new RoomServerException("No oauth token handler registered");
|
|
84
|
+
}
|
|
85
|
+
const [request] = unpackMessage(bytes);
|
|
86
|
+
const requestId = request["request_id"];
|
|
87
|
+
const challenge = request["challenge"];
|
|
88
|
+
const requestPayload = request["request"];
|
|
89
|
+
if (typeof requestId !== "string" || !isRecord(requestPayload)) {
|
|
90
|
+
throw new RoomServerException("invalid secrets.request_oauth_token payload");
|
|
91
|
+
}
|
|
92
|
+
const oauth = requestPayload["oauth"];
|
|
93
|
+
if (!isRecord(oauth) || typeof oauth["authorization_endpoint"] !== "string" || typeof oauth["token_endpoint"] !== "string") {
|
|
94
|
+
throw new RoomServerException("invalid secrets.request_oauth_token payload");
|
|
95
|
+
}
|
|
96
|
+
const scopes = oauth["scopes"];
|
|
97
|
+
if (scopes !== undefined && scopes !== null && (!Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string"))) {
|
|
98
|
+
throw new RoomServerException("invalid secrets.request_oauth_token payload");
|
|
99
|
+
}
|
|
100
|
+
Promise.resolve(this.oauthTokenRequestHandler({
|
|
101
|
+
requestId,
|
|
102
|
+
authorizationEndpoint: oauth["authorization_endpoint"],
|
|
103
|
+
tokenEndpoint: oauth["token_endpoint"],
|
|
104
|
+
challenge: typeof challenge === "string" ? challenge : null,
|
|
105
|
+
scopes: scopes ?? null,
|
|
106
|
+
clientId: typeof oauth["client_id"] === "string" ? oauth["client_id"] : null,
|
|
107
|
+
})).catch((error) => {
|
|
108
|
+
console.warn("OAuth token request handler threw", error);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async _handleClientSecretRequest(protocol, messageId, type, bytes) {
|
|
112
|
+
void protocol;
|
|
113
|
+
void messageId;
|
|
114
|
+
void type;
|
|
115
|
+
if (bytes == null) {
|
|
116
|
+
throw new RoomServerException("invalid secrets.request_secret payload");
|
|
117
|
+
}
|
|
118
|
+
if (this.secretRequestHandler == null) {
|
|
119
|
+
throw new RoomServerException("No secret handler registered");
|
|
120
|
+
}
|
|
121
|
+
const [request] = unpackMessage(bytes);
|
|
122
|
+
const requestId = request["request_id"];
|
|
123
|
+
const requestPayload = request["request"];
|
|
124
|
+
if (typeof requestId !== "string" || !isRecord(requestPayload) || typeof requestPayload["url"] !== "string" || typeof requestPayload["type"] !== "string") {
|
|
125
|
+
throw new RoomServerException("invalid secrets.request_secret payload");
|
|
126
|
+
}
|
|
127
|
+
Promise.resolve(this.secretRequestHandler({
|
|
128
|
+
requestId,
|
|
129
|
+
url: requestPayload["url"],
|
|
130
|
+
type: requestPayload["type"],
|
|
131
|
+
delegateTo: typeof requestPayload["delegate_to"] === "string" ? requestPayload["delegate_to"] : null,
|
|
132
|
+
})).catch((error) => {
|
|
133
|
+
console.warn("Secret request handler threw", error);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async provideOAuthAuthorization({ requestId, code, }) {
|
|
137
|
+
const response = await this.invoke("provide_oauth_authorization", {
|
|
138
|
+
request_id: requestId,
|
|
139
|
+
code,
|
|
140
|
+
error: null,
|
|
141
|
+
});
|
|
142
|
+
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
throw this.unexpectedResponse("provide_oauth_authorization");
|
|
146
|
+
}
|
|
147
|
+
async rejectOAuthAuthorization({ requestId, error, }) {
|
|
148
|
+
const response = await this.invoke("provide_oauth_authorization", {
|
|
149
|
+
request_id: requestId,
|
|
150
|
+
code: null,
|
|
151
|
+
error,
|
|
152
|
+
});
|
|
153
|
+
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
throw this.unexpectedResponse("provide_oauth_authorization");
|
|
157
|
+
}
|
|
158
|
+
async provideSecret({ requestId, data, }) {
|
|
159
|
+
const response = await this.invoke("provide_secret", new BinaryContent({
|
|
160
|
+
data,
|
|
161
|
+
headers: {
|
|
162
|
+
request_id: requestId,
|
|
163
|
+
error: null,
|
|
164
|
+
},
|
|
165
|
+
}));
|
|
166
|
+
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
throw this.unexpectedResponse("provide_secret");
|
|
170
|
+
}
|
|
171
|
+
async rejectSecret({ requestId, error, }) {
|
|
172
|
+
const response = await this.invoke("provide_secret", new BinaryContent({
|
|
173
|
+
data: new Uint8Array(0),
|
|
174
|
+
headers: {
|
|
175
|
+
request_id: requestId,
|
|
176
|
+
error,
|
|
177
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
throw this.unexpectedResponse("provide_secret");
|
|
183
|
+
}
|
|
184
|
+
async getOfflineOAuthToken({ connector, oauth, delegatedTo, delegatedBy, }) {
|
|
185
|
+
const response = await this.invoke("get_offline_oauth_token", {
|
|
186
|
+
connector: this.serializeConnectorRef(connector),
|
|
187
|
+
oauth: this.serializeOAuthConfig(oauth),
|
|
188
|
+
delegated_to: delegatedTo ?? null,
|
|
189
|
+
delegated_by: delegatedBy ?? null,
|
|
190
|
+
});
|
|
191
|
+
return this.parseAccessToken("get_offline_oauth_token", response);
|
|
192
|
+
}
|
|
193
|
+
async requestOAuthToken({ connector, oauth, timeout = 60 * 5, fromParticipantId, redirectUri, delegateTo, }) {
|
|
194
|
+
const response = await this.invoke("request_oauth_token", {
|
|
195
|
+
connector: this.serializeConnectorRef(connector),
|
|
196
|
+
oauth: this.serializeOAuthConfig(oauth),
|
|
197
|
+
redirect_uri: typeof redirectUri === "string" ? redirectUri : redirectUri.toString(),
|
|
198
|
+
timeout,
|
|
199
|
+
participant_id: fromParticipantId,
|
|
200
|
+
delegate_to: delegateTo ?? null,
|
|
201
|
+
});
|
|
202
|
+
return this.parseAccessToken("request_oauth_token", response);
|
|
45
203
|
}
|
|
46
204
|
async listSecrets() {
|
|
47
|
-
const response = await this.
|
|
205
|
+
const response = await this.invoke("list_secrets", {});
|
|
48
206
|
if (!(response instanceof JsonContent)) {
|
|
49
207
|
throw this.unexpectedResponse("list_secrets");
|
|
50
208
|
}
|
|
51
|
-
const secrets =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
delegatedTo: item.delegated_to,
|
|
57
|
-
}));
|
|
209
|
+
const secrets = response.json["secrets"];
|
|
210
|
+
if (!Array.isArray(secrets)) {
|
|
211
|
+
throw this.unexpectedResponse("list_secrets");
|
|
212
|
+
}
|
|
213
|
+
return secrets.map((item) => this.parseSecretInfo(item));
|
|
58
214
|
}
|
|
59
215
|
async deleteSecret({ secretId, delegatedTo, }) {
|
|
60
|
-
const
|
|
216
|
+
const response = await this.invoke("delete_secret", {
|
|
61
217
|
id: secretId,
|
|
62
218
|
delegated_to: delegatedTo ?? null,
|
|
63
|
-
};
|
|
64
|
-
const response = await this.client.invoke({ toolkit: "secrets", tool: "delete_secret", input: req });
|
|
219
|
+
});
|
|
65
220
|
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
66
221
|
return;
|
|
67
222
|
}
|
|
68
223
|
throw this.unexpectedResponse("delete_secret");
|
|
69
224
|
}
|
|
70
225
|
async deleteRequestedSecret({ url, type, delegatedTo, }) {
|
|
71
|
-
const
|
|
226
|
+
const response = await this.invoke("delete_requested_secret", {
|
|
72
227
|
url,
|
|
73
228
|
type,
|
|
74
229
|
delegated_to: delegatedTo ?? null,
|
|
75
|
-
};
|
|
76
|
-
const response = await this.client.invoke({ toolkit: "secrets", tool: "delete_requested_secret", input: req });
|
|
230
|
+
});
|
|
77
231
|
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
78
232
|
return;
|
|
79
233
|
}
|
|
80
234
|
throw this.unexpectedResponse("delete_requested_secret");
|
|
81
235
|
}
|
|
236
|
+
async requestSecret({ fromParticipantId, url, type, timeout = 60 * 5, delegateTo, }) {
|
|
237
|
+
const response = await this.invoke("request_secret", {
|
|
238
|
+
url,
|
|
239
|
+
type,
|
|
240
|
+
participant_id: fromParticipantId,
|
|
241
|
+
timeout,
|
|
242
|
+
delegate_to: delegateTo ?? null,
|
|
243
|
+
});
|
|
244
|
+
if (response instanceof FileContent) {
|
|
245
|
+
return response.data;
|
|
246
|
+
}
|
|
247
|
+
throw this.unexpectedResponse("request_secret");
|
|
248
|
+
}
|
|
249
|
+
async setSecret({ secretId, type, mimeType, name, delegatedTo, forIdentity, data, }) {
|
|
250
|
+
const response = await this.invoke("set_secret", new BinaryContent({
|
|
251
|
+
data,
|
|
252
|
+
headers: {
|
|
253
|
+
secret_id: secretId ?? null,
|
|
254
|
+
type: type ?? mimeType ?? null,
|
|
255
|
+
name: name ?? null,
|
|
256
|
+
delegated_to: delegatedTo ?? null,
|
|
257
|
+
for_identity: forIdentity ?? null,
|
|
258
|
+
has_data: true,
|
|
259
|
+
},
|
|
260
|
+
}));
|
|
261
|
+
if (response instanceof EmptyContent || response instanceof JsonContent) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
throw this.unexpectedResponse("set_secret");
|
|
265
|
+
}
|
|
266
|
+
async getSecret({ secretId, type, name, delegatedTo, }) {
|
|
267
|
+
const response = await this.invoke("get_secret", {
|
|
268
|
+
secret_id: secretId ?? null,
|
|
269
|
+
type: type ?? null,
|
|
270
|
+
name: name ?? null,
|
|
271
|
+
delegated_to: delegatedTo ?? null,
|
|
272
|
+
});
|
|
273
|
+
if (response instanceof EmptyContent) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
if (response instanceof FileContent) {
|
|
277
|
+
return response;
|
|
278
|
+
}
|
|
279
|
+
throw this.unexpectedResponse("get_secret");
|
|
280
|
+
}
|
|
82
281
|
}
|
|
@@ -1,37 +1,50 @@
|
|
|
1
1
|
import { RoomClient } from "./room-client";
|
|
2
|
+
import { Protocol } from "./protocol";
|
|
2
3
|
import { RoomEvent } from "./room-event";
|
|
3
4
|
import { BinaryContent, FileContent } from "./response";
|
|
4
5
|
import { EventEmitter } from "./event-emitter";
|
|
6
|
+
type StorageClientRoom = Pick<RoomClient, "invoke" | "invokeStream" | "emit"> & {
|
|
7
|
+
protocol: Pick<Protocol, "addHandler">;
|
|
8
|
+
};
|
|
5
9
|
export declare class FileHandle {
|
|
6
10
|
id: number;
|
|
7
11
|
constructor({ id }: {
|
|
8
12
|
id: number;
|
|
9
13
|
});
|
|
10
14
|
}
|
|
11
|
-
declare class StorageEntry {
|
|
15
|
+
export declare class StorageEntry {
|
|
12
16
|
name: string;
|
|
13
17
|
isFolder: boolean;
|
|
14
18
|
size: number | null;
|
|
15
|
-
|
|
19
|
+
createdAt: Date | null;
|
|
20
|
+
updatedAt: Date | null;
|
|
21
|
+
constructor({ name, isFolder, size, createdAt, updatedAt }: {
|
|
16
22
|
name: string;
|
|
17
23
|
isFolder: boolean;
|
|
18
24
|
size?: number | null;
|
|
25
|
+
createdAt?: Date | null;
|
|
26
|
+
updatedAt?: Date | null;
|
|
19
27
|
});
|
|
20
28
|
nameWithoutExtension(): string;
|
|
21
29
|
}
|
|
22
30
|
export declare class StorageClient extends EventEmitter<RoomEvent> {
|
|
23
31
|
private client;
|
|
24
32
|
constructor({ room }: {
|
|
25
|
-
room:
|
|
33
|
+
room: StorageClientRoom;
|
|
26
34
|
});
|
|
27
35
|
private _handleFileUpdated;
|
|
28
36
|
private _handleFileDeleted;
|
|
29
37
|
private _unexpectedResponseError;
|
|
38
|
+
private _storageEntry;
|
|
30
39
|
private _invoke;
|
|
31
40
|
list(path: string): Promise<StorageEntry[]>;
|
|
32
|
-
|
|
41
|
+
stat(path: string): Promise<StorageEntry | null>;
|
|
42
|
+
delete(path: string, { recursive, }?: {
|
|
43
|
+
recursive?: boolean | null;
|
|
44
|
+
}): Promise<void>;
|
|
33
45
|
exists(path: string): Promise<boolean>;
|
|
34
46
|
private _defaultUploadName;
|
|
47
|
+
private _defaultUploadMimeType;
|
|
35
48
|
upload(path: string, bytes: Uint8Array, { overwrite, name, mimeType, }?: {
|
|
36
49
|
overwrite?: boolean;
|
|
37
50
|
name?: string | null;
|
|
@@ -3,16 +3,68 @@ import { BinaryContent, ControlContent, ErrorContent, JsonContent, FileContent }
|
|
|
3
3
|
import { unpackMessage } from "./utils";
|
|
4
4
|
import { EventEmitter } from "./event-emitter";
|
|
5
5
|
import { RoomServerException } from "./room-server-client";
|
|
6
|
+
const _DEFAULT_UPLOAD_MIME_TYPE = "application/octet-stream";
|
|
7
|
+
const _UPLOAD_MIME_TYPES_BY_SUFFIX = new Map([
|
|
8
|
+
[".tar.gz", "application/x-tar"],
|
|
9
|
+
[".tgz", "application/x-tar"],
|
|
10
|
+
]);
|
|
11
|
+
const _UPLOAD_MIME_TYPES_BY_EXTENSION = new Map([
|
|
12
|
+
[".bin", "application/octet-stream"],
|
|
13
|
+
[".css", "text/css"],
|
|
14
|
+
[".csv", "text/csv"],
|
|
15
|
+
[".gif", "image/gif"],
|
|
16
|
+
[".gz", "application/gzip"],
|
|
17
|
+
[".htm", "text/html"],
|
|
18
|
+
[".html", "text/html"],
|
|
19
|
+
[".jpeg", "image/jpeg"],
|
|
20
|
+
[".jpg", "image/jpeg"],
|
|
21
|
+
[".js", "text/javascript"],
|
|
22
|
+
[".json", "application/json"],
|
|
23
|
+
[".md", "text/markdown"],
|
|
24
|
+
[".mp3", "audio/mpeg"],
|
|
25
|
+
[".mp4", "video/mp4"],
|
|
26
|
+
[".pdf", "application/pdf"],
|
|
27
|
+
[".png", "image/png"],
|
|
28
|
+
[".svg", "image/svg+xml"],
|
|
29
|
+
[".tar", "application/x-tar"],
|
|
30
|
+
[".txt", "text/plain"],
|
|
31
|
+
[".ts", "text/typescript"],
|
|
32
|
+
[".tsx", "text/tsx"],
|
|
33
|
+
[".wasm", "application/wasm"],
|
|
34
|
+
[".webp", "image/webp"],
|
|
35
|
+
[".xml", "application/xml"],
|
|
36
|
+
[".yaml", "application/yaml"],
|
|
37
|
+
[".yml", "application/yaml"],
|
|
38
|
+
[".zip", "application/zip"],
|
|
39
|
+
]);
|
|
40
|
+
function _unexpectedStorageResponseError(operation) {
|
|
41
|
+
return new RoomServerException(`unexpected return type from storage.${operation}`);
|
|
42
|
+
}
|
|
43
|
+
function _parseStorageTimestamp(value, operation) {
|
|
44
|
+
if (value == null) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value !== "string") {
|
|
48
|
+
throw _unexpectedStorageResponseError(operation);
|
|
49
|
+
}
|
|
50
|
+
const timestamp = new Date(value);
|
|
51
|
+
if (Number.isNaN(timestamp.getTime())) {
|
|
52
|
+
throw _unexpectedStorageResponseError(operation);
|
|
53
|
+
}
|
|
54
|
+
return timestamp;
|
|
55
|
+
}
|
|
6
56
|
export class FileHandle {
|
|
7
57
|
constructor({ id }) {
|
|
8
58
|
this.id = id;
|
|
9
59
|
}
|
|
10
60
|
}
|
|
11
|
-
class StorageEntry {
|
|
12
|
-
constructor({ name, isFolder, size = null }) {
|
|
61
|
+
export class StorageEntry {
|
|
62
|
+
constructor({ name, isFolder, size = null, createdAt = null, updatedAt = null }) {
|
|
13
63
|
this.name = name;
|
|
14
64
|
this.isFolder = isFolder;
|
|
15
65
|
this.size = size;
|
|
66
|
+
this.createdAt = createdAt;
|
|
67
|
+
this.updatedAt = updatedAt;
|
|
16
68
|
}
|
|
17
69
|
nameWithoutExtension() {
|
|
18
70
|
const segments = this.name
|
|
@@ -32,18 +84,32 @@ export class StorageClient extends EventEmitter {
|
|
|
32
84
|
}
|
|
33
85
|
async _handleFileUpdated(protocol, messageId, type, bytes) {
|
|
34
86
|
const [data, _] = unpackMessage(bytes || new Uint8Array());
|
|
35
|
-
const event = new FileUpdatedEvent({ path: data["path"] });
|
|
87
|
+
const event = new FileUpdatedEvent({ path: data["path"], participantId: data["participant_id"] });
|
|
36
88
|
this.client.emit(event);
|
|
37
89
|
this.emit('file.updated', event);
|
|
38
90
|
}
|
|
39
91
|
async _handleFileDeleted(protocol, messageId, type, bytes) {
|
|
40
92
|
const [data, _] = unpackMessage(bytes || new Uint8Array());
|
|
41
|
-
const event = new FileDeletedEvent({ path: data["path"] });
|
|
93
|
+
const event = new FileDeletedEvent({ path: data["path"], participantId: data["participant_id"] });
|
|
42
94
|
this.client.emit(event);
|
|
43
95
|
this.emit('file.deleted', event);
|
|
44
96
|
}
|
|
45
97
|
_unexpectedResponseError(operation) {
|
|
46
|
-
return
|
|
98
|
+
return _unexpectedStorageResponseError(operation);
|
|
99
|
+
}
|
|
100
|
+
_storageEntry(operation, value) {
|
|
101
|
+
if (typeof value["name"] !== "string" ||
|
|
102
|
+
typeof value["is_folder"] !== "boolean" ||
|
|
103
|
+
(value["size"] != null && typeof value["size"] !== "number")) {
|
|
104
|
+
throw this._unexpectedResponseError(operation);
|
|
105
|
+
}
|
|
106
|
+
return new StorageEntry({
|
|
107
|
+
name: value["name"],
|
|
108
|
+
isFolder: value["is_folder"],
|
|
109
|
+
size: typeof value["size"] === "number" ? value["size"] : null,
|
|
110
|
+
createdAt: _parseStorageTimestamp(value["created_at"], operation),
|
|
111
|
+
updatedAt: _parseStorageTimestamp(value["updated_at"], operation),
|
|
112
|
+
});
|
|
47
113
|
}
|
|
48
114
|
async _invoke(operation, input, callerContext) {
|
|
49
115
|
return await this.client.invoke({
|
|
@@ -55,22 +121,26 @@ export class StorageClient extends EventEmitter {
|
|
|
55
121
|
}
|
|
56
122
|
async list(path) {
|
|
57
123
|
const response = await this._invoke("list", { path });
|
|
58
|
-
if (!(response instanceof JsonContent)) {
|
|
124
|
+
if (!(response instanceof JsonContent) || !Array.isArray(response.json["files"])) {
|
|
59
125
|
throw this._unexpectedResponseError("list");
|
|
60
126
|
}
|
|
61
127
|
const files = response.json["files"];
|
|
62
|
-
const entries = files.map((f) =>
|
|
63
|
-
return new StorageEntry({
|
|
64
|
-
name: f["name"],
|
|
65
|
-
isFolder: f["is_folder"],
|
|
66
|
-
size: typeof f["size"] === "number" ? f["size"] : null,
|
|
67
|
-
});
|
|
68
|
-
});
|
|
128
|
+
const entries = files.map((f) => this._storageEntry("list", f));
|
|
69
129
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
70
130
|
return entries;
|
|
71
131
|
}
|
|
72
|
-
async
|
|
73
|
-
await this._invoke("
|
|
132
|
+
async stat(path) {
|
|
133
|
+
const response = await this._invoke("stat", { path });
|
|
134
|
+
if (!(response instanceof JsonContent) || typeof response.json["exists"] !== "boolean") {
|
|
135
|
+
throw this._unexpectedResponseError("stat");
|
|
136
|
+
}
|
|
137
|
+
if (!response.json["exists"]) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return this._storageEntry("stat", response.json);
|
|
141
|
+
}
|
|
142
|
+
async delete(path, { recursive = null, } = {}) {
|
|
143
|
+
await this._invoke("delete", { path, recursive });
|
|
74
144
|
}
|
|
75
145
|
async exists(path) {
|
|
76
146
|
const result = await this._invoke("exists", { path });
|
|
@@ -87,6 +157,22 @@ export class StorageClient extends EventEmitter {
|
|
|
87
157
|
const lastSegment = segments.length > 0 ? segments[segments.length - 1] : undefined;
|
|
88
158
|
return lastSegment ?? path;
|
|
89
159
|
}
|
|
160
|
+
_defaultUploadMimeType(name, mimeType) {
|
|
161
|
+
if (typeof mimeType === "string" && mimeType.length > 0) {
|
|
162
|
+
return mimeType;
|
|
163
|
+
}
|
|
164
|
+
const lowerName = name.toLowerCase();
|
|
165
|
+
for (const [suffix, contentType] of _UPLOAD_MIME_TYPES_BY_SUFFIX.entries()) {
|
|
166
|
+
if (lowerName.endsWith(suffix)) {
|
|
167
|
+
return contentType;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const lastDot = lowerName.lastIndexOf(".");
|
|
171
|
+
if (lastDot >= 0) {
|
|
172
|
+
return _UPLOAD_MIME_TYPES_BY_EXTENSION.get(lowerName.slice(lastDot)) ?? _DEFAULT_UPLOAD_MIME_TYPE;
|
|
173
|
+
}
|
|
174
|
+
return _DEFAULT_UPLOAD_MIME_TYPE;
|
|
175
|
+
}
|
|
90
176
|
async upload(path, bytes, { overwrite = false, name, mimeType = null, } = {}) {
|
|
91
177
|
async function* singleChunk() {
|
|
92
178
|
yield bytes;
|
|
@@ -100,6 +186,7 @@ export class StorageClient extends EventEmitter {
|
|
|
100
186
|
}
|
|
101
187
|
async uploadStream(path, chunks, { overwrite = false, chunkSize = 64 * 1024, size = null, name, mimeType = null, } = {}) {
|
|
102
188
|
const resolvedName = this._defaultUploadName(path, name);
|
|
189
|
+
const resolvedMimeType = this._defaultUploadMimeType(resolvedName, mimeType);
|
|
103
190
|
const input = new _StorageUploadInputStream({
|
|
104
191
|
path,
|
|
105
192
|
overwrite,
|
|
@@ -107,7 +194,7 @@ export class StorageClient extends EventEmitter {
|
|
|
107
194
|
chunkSize,
|
|
108
195
|
size,
|
|
109
196
|
name: resolvedName,
|
|
110
|
-
mimeType,
|
|
197
|
+
mimeType: resolvedMimeType,
|
|
111
198
|
});
|
|
112
199
|
const response = await this.client.invokeStream({
|
|
113
200
|
toolkit: "storage",
|
|
@@ -131,7 +218,10 @@ export class StorageClient extends EventEmitter {
|
|
|
131
218
|
if (chunk.headers["kind"] !== "pull") {
|
|
132
219
|
throw this._unexpectedResponseError("upload");
|
|
133
220
|
}
|
|
134
|
-
|
|
221
|
+
const rawChunkSize = chunk.headers["chunk_size"];
|
|
222
|
+
input.requestNext(typeof rawChunkSize === "number" && rawChunkSize > 0
|
|
223
|
+
? rawChunkSize
|
|
224
|
+
: null);
|
|
135
225
|
}
|
|
136
226
|
}
|
|
137
227
|
finally {
|
|
@@ -321,7 +411,7 @@ class _StorageDownloadInputStream {
|
|
|
321
411
|
class _StorageUploadInputStream {
|
|
322
412
|
constructor({ path, overwrite, chunks, chunkSize, size, name, mimeType, }) {
|
|
323
413
|
this.closed = false;
|
|
324
|
-
this.pendingPulls =
|
|
414
|
+
this.pendingPulls = [];
|
|
325
415
|
this.waitingResolver = null;
|
|
326
416
|
this.pendingChunk = new Uint8Array(0);
|
|
327
417
|
this.pendingOffset = 0;
|
|
@@ -334,11 +424,11 @@ class _StorageUploadInputStream {
|
|
|
334
424
|
this.name = name;
|
|
335
425
|
this.mimeType = mimeType;
|
|
336
426
|
}
|
|
337
|
-
requestNext() {
|
|
427
|
+
requestNext(chunkSize = null) {
|
|
338
428
|
if (this.closed) {
|
|
339
429
|
return;
|
|
340
430
|
}
|
|
341
|
-
this.pendingPulls
|
|
431
|
+
this.pendingPulls.push(chunkSize);
|
|
342
432
|
if (this.waitingResolver) {
|
|
343
433
|
const resolver = this.waitingResolver;
|
|
344
434
|
this.waitingResolver = null;
|
|
@@ -356,21 +446,26 @@ class _StorageUploadInputStream {
|
|
|
356
446
|
resolver();
|
|
357
447
|
}
|
|
358
448
|
}
|
|
359
|
-
async nextChunk() {
|
|
360
|
-
|
|
449
|
+
async nextChunk(requestedChunkSize) {
|
|
450
|
+
const parts = [];
|
|
451
|
+
let totalLength = 0;
|
|
452
|
+
while (totalLength < requestedChunkSize) {
|
|
361
453
|
if (this.pendingOffset < this.pendingChunk.length) {
|
|
362
454
|
const start = this.pendingOffset;
|
|
363
|
-
const end = Math.min(start +
|
|
455
|
+
const end = Math.min(start + (requestedChunkSize - totalLength), this.pendingChunk.length);
|
|
456
|
+
const part = this.pendingChunk.slice(start, end);
|
|
364
457
|
this.pendingOffset = end;
|
|
365
|
-
|
|
458
|
+
parts.push(part);
|
|
459
|
+
totalLength += part.length;
|
|
460
|
+
continue;
|
|
366
461
|
}
|
|
367
462
|
if (this.sourceExhausted) {
|
|
368
|
-
|
|
463
|
+
break;
|
|
369
464
|
}
|
|
370
465
|
const next = await this.source.next();
|
|
371
466
|
if (next.done) {
|
|
372
467
|
this.sourceExhausted = true;
|
|
373
|
-
|
|
468
|
+
break;
|
|
374
469
|
}
|
|
375
470
|
if (next.value.length === 0) {
|
|
376
471
|
continue;
|
|
@@ -378,6 +473,19 @@ class _StorageUploadInputStream {
|
|
|
378
473
|
this.pendingChunk = next.value;
|
|
379
474
|
this.pendingOffset = 0;
|
|
380
475
|
}
|
|
476
|
+
if (totalLength === 0) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (parts.length === 1) {
|
|
480
|
+
return parts[0];
|
|
481
|
+
}
|
|
482
|
+
const combined = new Uint8Array(totalLength);
|
|
483
|
+
let offset = 0;
|
|
484
|
+
for (const part of parts) {
|
|
485
|
+
combined.set(part, offset);
|
|
486
|
+
offset += part.length;
|
|
487
|
+
}
|
|
488
|
+
return combined;
|
|
381
489
|
}
|
|
382
490
|
async *stream() {
|
|
383
491
|
yield new BinaryContent({
|
|
@@ -392,7 +500,7 @@ class _StorageUploadInputStream {
|
|
|
392
500
|
},
|
|
393
501
|
});
|
|
394
502
|
while (!this.closed) {
|
|
395
|
-
if (this.pendingPulls === 0) {
|
|
503
|
+
if (this.pendingPulls.length === 0) {
|
|
396
504
|
await new Promise((resolve) => {
|
|
397
505
|
this.waitingResolver = resolve;
|
|
398
506
|
});
|
|
@@ -400,11 +508,13 @@ class _StorageUploadInputStream {
|
|
|
400
508
|
if (this.closed) {
|
|
401
509
|
return;
|
|
402
510
|
}
|
|
403
|
-
if (this.pendingPulls === 0) {
|
|
511
|
+
if (this.pendingPulls.length === 0) {
|
|
404
512
|
continue;
|
|
405
513
|
}
|
|
406
|
-
this.pendingPulls
|
|
407
|
-
const chunk = await this.nextChunk(
|
|
514
|
+
const requestedChunkSize = this.pendingPulls.shift();
|
|
515
|
+
const chunk = await this.nextChunk(typeof requestedChunkSize === "number" && requestedChunkSize > 0
|
|
516
|
+
? requestedChunkSize
|
|
517
|
+
: this.chunkSize);
|
|
408
518
|
if (chunk == null) {
|
|
409
519
|
return;
|
|
410
520
|
}
|