@meshagent/meshagent 0.35.6 → 0.35.8
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 +12 -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 +104 -16
- 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 +103 -16
- 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 +104 -16
- 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",
|