@honeybbq/teamspeak 1.0.0-beta.28 → 1.0.0-beta.30
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 +1 -1
- package/dist/client/api/files.d.ts +4 -0
- package/dist/client/api/files.d.ts.map +1 -1
- package/dist/client/api/files.js +4 -0
- package/dist/client/api/files.js.map +1 -1
- package/dist/client/api/s3-avatars.d.ts +67 -0
- package/dist/client/api/s3-avatars.d.ts.map +1 -0
- package/dist/client/api/s3-avatars.js +95 -0
- package/dist/client/api/s3-avatars.js.map +1 -0
- package/dist/client/api/s3-list.d.ts +12 -0
- package/dist/client/api/s3-list.d.ts.map +1 -0
- package/dist/client/api/s3-list.js +43 -0
- package/dist/client/api/s3-list.js.map +1 -0
- package/dist/client/api/s3.d.ts +284 -0
- package/dist/client/api/s3.d.ts.map +1 -0
- package/dist/client/api/s3.js +561 -0
- package/dist/client/api/s3.js.map +1 -0
- package/dist/client/capabilities.d.ts +6 -4
- package/dist/client/capabilities.d.ts.map +1 -1
- package/dist/client/capabilities.js.map +1 -1
- package/dist/client/commands.d.ts.map +1 -1
- package/dist/client/commands.js +3 -2
- package/dist/client/commands.js.map +1 -1
- package/dist/client/errors.d.ts +46 -0
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +73 -0
- package/dist/client/errors.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/transfer.d.ts +13 -1
- package/dist/client/transfer.d.ts.map +1 -1
- package/dist/client/transfer.js +2 -2
- package/dist/client/transfer.js.map +1 -1
- package/dist/core/command/parser.d.ts +6 -4
- package/dist/core/command/parser.d.ts.map +1 -1
- package/dist/core/command/parser.js +10 -8
- package/dist/core/command/parser.js.map +1 -1
- package/dist/core/command/responses.d.ts +6 -0
- package/dist/core/command/responses.d.ts.map +1 -1
- package/dist/core/command/responses.js +24 -0
- package/dist/core/command/responses.js.map +1 -1
- package/dist/core/command-name.d.ts +14 -0
- package/dist/core/command-name.d.ts.map +1 -1
- package/dist/core/command-name.js +14 -0
- package/dist/core/command-name.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/notification-name.d.ts +10 -0
- package/dist/core/notification-name.d.ts.map +1 -1
- package/dist/core/notification-name.js +10 -0
- package/dist/core/notification-name.js.map +1 -1
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +17 -0
- package/dist/core/types.js.map +1 -1
- package/dist/stream/streams.d.ts.map +1 -1
- package/dist/stream/streams.js +4 -4
- package/dist/stream/streams.js.map +1 -1
- package/dist/stream/wire.d.ts +0 -5
- package/dist/stream/wire.d.ts.map +1 -1
- package/dist/stream/wire.js +0 -5
- package/dist/stream/wire.js.map +1 -1
- package/package.json +1 -1
- package/src/client/api/files.ts +4 -0
- package/src/client/api/s3-avatars.ts +137 -0
- package/src/client/api/s3-list.ts +49 -0
- package/src/client/api/s3.ts +833 -0
- package/src/client/capabilities.ts +6 -4
- package/src/client/commands.ts +3 -1
- package/src/client/errors.ts +90 -0
- package/src/client/index.ts +4 -0
- package/src/client/transfer.ts +2 -2
- package/src/core/command/parser.ts +10 -8
- package/src/core/command/responses.ts +31 -0
- package/src/core/command-name.ts +14 -0
- package/src/core/index.ts +1 -0
- package/src/core/notification-name.ts +10 -0
- package/src/core/types.ts +23 -0
- package/src/stream/streams.ts +3 -3
- package/src/stream/wire.ts +0 -6
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
- **ServerQuery.** The same typed commands run over raw TCP, SSH (with host-key verification) or WebQuery, so an administration script and a voice bot share one vocabulary.
|
|
35
35
|
- **TeamSpeak 6 screen share.** Find, publish and watch the streams of a TeamSpeak 6 server, interoperable with the TeamSpeak 6 client. SDP and ICE are plain strings for any WebRTC stack: a browser, Electron, or werift in Node.
|
|
36
36
|
- **Typed commands and events.** Every command the voice protocol answers is a typed function, and the server's notifications arrive as typed events with pluggable middleware.
|
|
37
|
-
- **File transfers.** Upload, download and delete files on the server, with avatars and icons covered.
|
|
37
|
+
- **File transfers.** Upload, download and delete files on the server, with avatars and icons covered, and the S3 file transfer of a TeamSpeak 6 server that keeps its files in an S3 bucket.
|
|
38
38
|
- **Built for long-running bots.** Opt-in reconnection tuned for TeamSpeak's antiflood ban, and a line middleware seam for pacing commands, with recipes for a token bucket, resending a refused command and keeping a query session open.
|
|
39
39
|
- **Identities and server facts.** Generate, import, export and upgrade identities; read the server version, its uid hash (SHA-1 on TS3, SHA-256 on TS6) and its name, icon and banner.
|
|
40
40
|
- **TeamSpeak 3 and 6, ESM with types.** Pure TypeScript, zero native dependencies, full declarations, and a custom-transport hook for proxies and tests.
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* beta13.1 (SSH, voice), the key a query connection negotiates opens the same
|
|
9
9
|
* file transfer port a voice client's does. WebQuery refuses every file command
|
|
10
10
|
* (error 5120), on both servers.
|
|
11
|
+
*
|
|
12
|
+
* A TeamSpeak 6 server whose extensions name `s3` keeps a separate store in an
|
|
13
|
+
* S3 bucket, which these do not reach: see `uploadS3File` and the rest of the
|
|
14
|
+
* S3 functions.
|
|
11
15
|
*/
|
|
12
16
|
import { Readable, Writable } from "node:stream";
|
|
13
17
|
import type { ChannelID, CommandExecutor, FileDownloadOptions, FileUploadOptions } from "@honeybbq/teamspeak";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/client/api/files.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/client/api/files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAa,QAAQ,EAAY,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EAEf,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB,EAAE,mBAAmB;IAC/E;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,mBAAmB,EAAE,mBAAmB;IACnF,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAqHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,GAAG,QAAQ,EAC3B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GACzD,OAAO,CAAC,IAAI,CAAC,CAAC;AACjB,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAAC"}
|
package/dist/client/api/files.js
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* beta13.1 (SSH, voice), the key a query connection negotiates opens the same
|
|
9
9
|
* file transfer port a voice client's does. WebQuery refuses every file command
|
|
10
10
|
* (error 5120), on both servers.
|
|
11
|
+
*
|
|
12
|
+
* A TeamSpeak 6 server whose extensions name `s3` keeps a separate store in an
|
|
13
|
+
* S3 bucket, which these do not reach: see `uploadS3File` and the rest of the
|
|
14
|
+
* S3 functions.
|
|
11
15
|
*/
|
|
12
16
|
import { Readable, Transform, Writable, pipeline } from "node:stream";
|
|
13
17
|
import { initFileDownload, initFileUpload, stopFileTransfer } from "@honeybbq/teamspeak";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/client/api/files.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/client/api/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatars and icons on a TeamSpeak 6 server with S3 file transfer: the S3
|
|
3
|
+
* transfers behind them, in channel 0's S3 file area.
|
|
4
|
+
*
|
|
5
|
+
* Measured on TeamSpeak 6 6.0.0-beta13.1. In channel 0 the server signs an
|
|
6
|
+
* upload only for a name that starts with `avatar_` or `icon_`, and keeps the
|
|
7
|
+
* file under the name it is given: unlike the classic upload, which the server
|
|
8
|
+
* stores under the uploader's {@link avatarFileName} whatever it was asked,
|
|
9
|
+
* an avatar here has to be uploaded under that name for others to find it.
|
|
10
|
+
* The classic avatars and icons are a separate store: one uploaded one way is
|
|
11
|
+
* not found the other.
|
|
12
|
+
*/
|
|
13
|
+
import type { CommandOptions, IconID, UID } from "@honeybbq/teamspeak";
|
|
14
|
+
import type { Client } from "../client.js";
|
|
15
|
+
import type { S3TransferOptions } from "./s3.js";
|
|
16
|
+
/** Options for the S3 avatar and icon transfers: the commands' and the requests'. */
|
|
17
|
+
export interface S3AvatarOptions extends CommandOptions, S3TransferOptions {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Upload `data` as this client's avatar over S3 and set `client_flag_avatar`
|
|
21
|
+
* to its MD5, so that other clients see the change in a `clientUpdated` event.
|
|
22
|
+
* Returns the MD5, lower-case hex.
|
|
23
|
+
*
|
|
24
|
+
* On the wire: an upload signed for `/<avatarFileName(uid)>` in channel 0, the
|
|
25
|
+
* bytes to the storage, the server told the upload is done, then
|
|
26
|
+
* `clientupdate client_flag_avatar=<md5>`. An avatar already there is replaced.
|
|
27
|
+
*
|
|
28
|
+
* The size limit is the permission `i_client_max_avatar_filesize`, 200000
|
|
29
|
+
* bytes by default; over it, the signing is refused with 2049 (invalid file
|
|
30
|
+
* permissions) and nothing is sent or changed. A file of exactly the limit
|
|
31
|
+
* passes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function uploadS3Avatar(client: Client, data: Uint8Array, options?: S3AvatarOptions): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* The bytes of the S3 avatar of the client with this unique id, or undefined
|
|
36
|
+
* when the storage has none for it (a download of `/<avatarFileName(uid)>` in
|
|
37
|
+
* channel 0, answered 404).
|
|
38
|
+
*/
|
|
39
|
+
export declare function downloadS3Avatar(client: Client, uid: UID | string, options?: S3AvatarOptions): Promise<Uint8Array | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Remove this client's own S3 avatar: delete `/<avatarFileName(uid)>` in
|
|
42
|
+
* channel 0, then empty `client_flag_avatar`, which the delete leaves as it
|
|
43
|
+
* was. A client may delete its own avatar only: another's is refused with 2568.
|
|
44
|
+
* Resolves whether or not there was an avatar, as the storage's delete does.
|
|
45
|
+
*/
|
|
46
|
+
export declare function removeS3Avatar(client: Client, options?: CommandOptions): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Upload `data` as an icon over S3 and return its id, the CRC-32 of the bytes
|
|
49
|
+
* ({@link iconIDOf}): an upload signed for `/icon_<id>` in channel 0, the
|
|
50
|
+
* bytes to the storage, and the server told the upload is done. Pass the id to
|
|
51
|
+
* `setChannelIcon`, `setServerGroupIcon`, `setChannelGroupIcon`,
|
|
52
|
+
* `setClientIcon` or `setServerIcon`.
|
|
53
|
+
*
|
|
54
|
+
* Needs `b_icon_manage`, and the size limit is `i_max_icon_filesize`, 8192
|
|
55
|
+
* bytes for a Server Admin: without the one, or over the other, the signing is
|
|
56
|
+
* refused with 2049 (invalid file permissions).
|
|
57
|
+
*/
|
|
58
|
+
export declare function uploadS3Icon(client: Client, data: Uint8Array, options?: S3AvatarOptions): Promise<IconID>;
|
|
59
|
+
/** The bytes of an S3 icon, or undefined when the storage has no file for the id. */
|
|
60
|
+
export declare function downloadS3Icon(client: Client, id: IconID, options?: S3AvatarOptions): Promise<Uint8Array | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* Delete an S3 icon's file (`/icon_<id>` in channel 0). Needs `b_icon_manage`
|
|
63
|
+
* (2568 without it). A channel, group or client that still names the icon
|
|
64
|
+
* keeps the id; deleting a file that is not there succeeds.
|
|
65
|
+
*/
|
|
66
|
+
export declare function deleteS3Icon(client: Client, id: IconID, options?: CommandOptions): Promise<void>;
|
|
67
|
+
//# sourceMappingURL=s3-avatars.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-avatars.d.ts","sourceRoot":"","sources":["../../../src/client/api/s3-avatars.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,qFAAqF;AACrF,MAAM,WAAW,eAAgB,SAAQ,cAAc,EAAE,iBAAiB;CAAG;AAkB7E;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,GAAG,MAAM,EACjB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAEjC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhG;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAEjC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatars and icons on a TeamSpeak 6 server with S3 file transfer: the S3
|
|
3
|
+
* transfers behind them, in channel 0's S3 file area.
|
|
4
|
+
*
|
|
5
|
+
* Measured on TeamSpeak 6 6.0.0-beta13.1. In channel 0 the server signs an
|
|
6
|
+
* upload only for a name that starts with `avatar_` or `icon_`, and keeps the
|
|
7
|
+
* file under the name it is given: unlike the classic upload, which the server
|
|
8
|
+
* stores under the uploader's {@link avatarFileName} whatever it was asked,
|
|
9
|
+
* an avatar here has to be uploaded under that name for others to find it.
|
|
10
|
+
* The classic avatars and icons are a separate store: one uploaded one way is
|
|
11
|
+
* not found the other.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { asChannelID, avatarFileName, iconFileName, iconIDOf, updateClient, } from "@honeybbq/teamspeak";
|
|
15
|
+
import { S3TransferError } from "../errors.js";
|
|
16
|
+
import { deleteS3File, downloadS3File, uploadS3File } from "./s3.js";
|
|
17
|
+
const CHANNEL_ZERO = asChannelID(0n);
|
|
18
|
+
/** The bytes at `path` in channel 0, or undefined when the storage has no file there. */
|
|
19
|
+
async function download(client, path, options) {
|
|
20
|
+
try {
|
|
21
|
+
return await downloadS3File(client, CHANNEL_ZERO, path, options);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof S3TransferError && error.status === 404)
|
|
25
|
+
return undefined;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Upload `data` as this client's avatar over S3 and set `client_flag_avatar`
|
|
31
|
+
* to its MD5, so that other clients see the change in a `clientUpdated` event.
|
|
32
|
+
* Returns the MD5, lower-case hex.
|
|
33
|
+
*
|
|
34
|
+
* On the wire: an upload signed for `/<avatarFileName(uid)>` in channel 0, the
|
|
35
|
+
* bytes to the storage, the server told the upload is done, then
|
|
36
|
+
* `clientupdate client_flag_avatar=<md5>`. An avatar already there is replaced.
|
|
37
|
+
*
|
|
38
|
+
* The size limit is the permission `i_client_max_avatar_filesize`, 200000
|
|
39
|
+
* bytes by default; over it, the signing is refused with 2049 (invalid file
|
|
40
|
+
* permissions) and nothing is sent or changed. A file of exactly the limit
|
|
41
|
+
* passes.
|
|
42
|
+
*/
|
|
43
|
+
export async function uploadS3Avatar(client, data, options = {}) {
|
|
44
|
+
const md5 = createHash("md5").update(data).digest("hex");
|
|
45
|
+
await uploadS3File(client, CHANNEL_ZERO, `/${avatarFileName(client.uid)}`, data, options);
|
|
46
|
+
await updateClient(client, { avatar: md5 }, options);
|
|
47
|
+
return md5;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The bytes of the S3 avatar of the client with this unique id, or undefined
|
|
51
|
+
* when the storage has none for it (a download of `/<avatarFileName(uid)>` in
|
|
52
|
+
* channel 0, answered 404).
|
|
53
|
+
*/
|
|
54
|
+
export function downloadS3Avatar(client, uid, options = {}) {
|
|
55
|
+
return download(client, `/${avatarFileName(uid)}`, options);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Remove this client's own S3 avatar: delete `/<avatarFileName(uid)>` in
|
|
59
|
+
* channel 0, then empty `client_flag_avatar`, which the delete leaves as it
|
|
60
|
+
* was. A client may delete its own avatar only: another's is refused with 2568.
|
|
61
|
+
* Resolves whether or not there was an avatar, as the storage's delete does.
|
|
62
|
+
*/
|
|
63
|
+
export async function removeS3Avatar(client, options = {}) {
|
|
64
|
+
await deleteS3File(client, CHANNEL_ZERO, `/${avatarFileName(client.uid)}`, options);
|
|
65
|
+
await updateClient(client, { avatar: "" }, options);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Upload `data` as an icon over S3 and return its id, the CRC-32 of the bytes
|
|
69
|
+
* ({@link iconIDOf}): an upload signed for `/icon_<id>` in channel 0, the
|
|
70
|
+
* bytes to the storage, and the server told the upload is done. Pass the id to
|
|
71
|
+
* `setChannelIcon`, `setServerGroupIcon`, `setChannelGroupIcon`,
|
|
72
|
+
* `setClientIcon` or `setServerIcon`.
|
|
73
|
+
*
|
|
74
|
+
* Needs `b_icon_manage`, and the size limit is `i_max_icon_filesize`, 8192
|
|
75
|
+
* bytes for a Server Admin: without the one, or over the other, the signing is
|
|
76
|
+
* refused with 2049 (invalid file permissions).
|
|
77
|
+
*/
|
|
78
|
+
export async function uploadS3Icon(client, data, options = {}) {
|
|
79
|
+
const id = iconIDOf(data);
|
|
80
|
+
await uploadS3File(client, CHANNEL_ZERO, iconFileName(id), data, options);
|
|
81
|
+
return id;
|
|
82
|
+
}
|
|
83
|
+
/** The bytes of an S3 icon, or undefined when the storage has no file for the id. */
|
|
84
|
+
export function downloadS3Icon(client, id, options = {}) {
|
|
85
|
+
return download(client, iconFileName(id), options);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Delete an S3 icon's file (`/icon_<id>` in channel 0). Needs `b_icon_manage`
|
|
89
|
+
* (2568 without it). A channel, group or client that still names the icon
|
|
90
|
+
* keeps the id; deleting a file that is not there succeeds.
|
|
91
|
+
*/
|
|
92
|
+
export async function deleteS3Icon(client, id, options = {}) {
|
|
93
|
+
await deleteS3File(client, CHANNEL_ZERO, iconFileName(id), options);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=s3-avatars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-avatars.js","sourceRoot":"","sources":["../../../src/client/api/s3-avatars.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the file list a TeamSpeak 6 server sends for `s3ft_list_files`.
|
|
3
|
+
*/
|
|
4
|
+
import type { S3FileEntry } from "./s3.js";
|
|
5
|
+
/**
|
|
6
|
+
* The file list the server sends: base64 of the JSON
|
|
7
|
+
* `{"contents":[{"key":"dir/a.txt","last_modified":"…","size":2}]}`, each key
|
|
8
|
+
* from the channel's root without a leading slash, and an empty field for no
|
|
9
|
+
* files.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseFileList(field: string): S3FileEntry[];
|
|
12
|
+
//# sourceMappingURL=s3-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-list.d.ts","sourceRoot":"","sources":["../../../src/client/api/s3-list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAmC1D"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the file list a TeamSpeak 6 server sends for `s3ft_list_files`.
|
|
3
|
+
*/
|
|
4
|
+
import { NoDataError } from "@honeybbq/teamspeak";
|
|
5
|
+
/**
|
|
6
|
+
* The file list the server sends: base64 of the JSON
|
|
7
|
+
* `{"contents":[{"key":"dir/a.txt","last_modified":"…","size":2}]}`, each key
|
|
8
|
+
* from the channel's root without a leading slash, and an empty field for no
|
|
9
|
+
* files.
|
|
10
|
+
*/
|
|
11
|
+
export function parseFileList(field) {
|
|
12
|
+
if (field === "")
|
|
13
|
+
return [];
|
|
14
|
+
const malformed = () => new NoDataError("s3ft_list_files", "the server answered s3ft_list_files with a malformed file list");
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(Buffer.from(field, "base64").toString("utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw malformed();
|
|
21
|
+
}
|
|
22
|
+
const contents = parsed?.contents;
|
|
23
|
+
if (!Array.isArray(contents))
|
|
24
|
+
throw malformed();
|
|
25
|
+
return contents.map((item) => {
|
|
26
|
+
const entry = item;
|
|
27
|
+
const key = entry?.key;
|
|
28
|
+
const size = entry?.size;
|
|
29
|
+
if (typeof key !== "string" ||
|
|
30
|
+
typeof size !== "number" ||
|
|
31
|
+
!Number.isSafeInteger(size) ||
|
|
32
|
+
size < 0) {
|
|
33
|
+
throw malformed();
|
|
34
|
+
}
|
|
35
|
+
const time = typeof entry?.last_modified === "string" ? new Date(entry.last_modified) : undefined;
|
|
36
|
+
return {
|
|
37
|
+
path: key.startsWith("/") ? key : `/${key}`,
|
|
38
|
+
size: BigInt(size),
|
|
39
|
+
modifiedAt: time === undefined || Number.isNaN(time.getTime()) ? undefined : time,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=s3-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-list.js","sourceRoot":"","sources":["../../../src/client/api/s3-list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,GAAgB,EAAE,CAClC,IAAI,WAAW,CACb,iBAAiB,EACjB,gEAAgE,CACjE,CAAC;IACJ,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,EAAE,CAAC;IACpB,CAAC;IACD,MAAM,QAAQ,GAAI,MAAwC,EAAE,QAAQ,CAAC;IACrE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,MAAM,SAAS,EAAE,CAAC;IAChD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAa,EAAe,EAAE;QACjD,MAAM,KAAK,GAAG,IAAyE,CAAC;QACxF,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,CAAC;QACvB,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC;QACzB,IACE,OAAO,GAAG,KAAK,QAAQ;YACvB,OAAO,IAAI,KAAK,QAAQ;YACxB,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;YAC3B,IAAI,GAAG,CAAC,EACR,CAAC;YACD,MAAM,SAAS,EAAE,CAAC;QACpB,CAAC;QACD,MAAM,IAAI,GACR,OAAO,KAAK,EAAE,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE;YAC3C,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;YAClB,UAAU,EAAE,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;SAClF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamSpeak 6 S3 file transfer.
|
|
3
|
+
*
|
|
4
|
+
* A TeamSpeak 6 server started with S3 file transfer keeps channel files in an
|
|
5
|
+
* S3-compatible bucket, and says so: its `extensions`
|
|
6
|
+
* (`client.serverCapabilities.extensions`) name `"s3"`. A client asks the
|
|
7
|
+
* server for a presigned URL over the voice connection and moves the bytes
|
|
8
|
+
* with a plain HTTPS request to the storage; nothing goes through the server's
|
|
9
|
+
* file transfer port.
|
|
10
|
+
*
|
|
11
|
+
* TeamSpeak does not document these commands. Everything here was measured on
|
|
12
|
+
* TeamSpeak 6 6.0.0-beta13.1 with an S3-compatible storage behind TLS:
|
|
13
|
+
*
|
|
14
|
+
* - They run on the voice connection, which is why they take a `Client`:
|
|
15
|
+
* WebQuery refuses them as unknown commands.
|
|
16
|
+
* - The server keeps each channel's file list in memory, takes a file out of it
|
|
17
|
+
* when it deletes one, and reads it from the storage again when told an
|
|
18
|
+
* upload is done: a file stored without {@link completeS3FileUpload} is not
|
|
19
|
+
* listed.
|
|
20
|
+
* - Other clients are told nothing when a file is uploaded or deleted.
|
|
21
|
+
* - The classic file commands (`ftinitupload`, `ftgetfilelist`, …) still work
|
|
22
|
+
* on such a server, and reach a separate store: a file uploaded one way is
|
|
23
|
+
* not listed the other.
|
|
24
|
+
* - A rename is a copy the server makes in the storage, and its copy request
|
|
25
|
+
* leaves a header unsigned that a storage checking signatures refuses: see
|
|
26
|
+
* {@link renameS3File}.
|
|
27
|
+
*
|
|
28
|
+
* Every function takes the channel password as `password`, in cleartext, and
|
|
29
|
+
* sends it the way every file command does.
|
|
30
|
+
*/
|
|
31
|
+
import { Readable, Writable } from "node:stream";
|
|
32
|
+
import type { ChannelID, CommandOptions } from "@honeybbq/teamspeak";
|
|
33
|
+
import type { Client } from "../client.js";
|
|
34
|
+
/** The channel password an S3 file command needs, and the command's own options. */
|
|
35
|
+
export interface S3FileOptions extends CommandOptions {
|
|
36
|
+
/** The channel's password, in cleartext; "" by default (`chpw`). */
|
|
37
|
+
readonly password?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* An upload the server signed: where to send the file, and what to tell the
|
|
41
|
+
* server once it is stored.
|
|
42
|
+
*
|
|
43
|
+
* `url` is a credential: whoever holds it can write the file until it expires,
|
|
44
|
+
* after the server's `s3ft-presigned-url-expiration` (300 seconds by default).
|
|
45
|
+
* Keep it out of logs.
|
|
46
|
+
*/
|
|
47
|
+
export interface S3FileUploadInfo {
|
|
48
|
+
/** The channel whose file area the file goes to. */
|
|
49
|
+
readonly channelID: ChannelID;
|
|
50
|
+
/** The file's path from the channel's root, as the server confirmed it. */
|
|
51
|
+
readonly path: string;
|
|
52
|
+
/** The size in bytes the URL is signed for. The upload must send exactly this many. */
|
|
53
|
+
readonly size: bigint;
|
|
54
|
+
/** The presigned URL to `PUT` the file to. */
|
|
55
|
+
readonly url: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A download the server signed. `url` is a credential as an upload's is: whoever
|
|
59
|
+
* holds it can read the file until it expires.
|
|
60
|
+
*/
|
|
61
|
+
export interface S3FileDownloadInfo {
|
|
62
|
+
/** The channel whose file area the file is in. */
|
|
63
|
+
readonly channelID: ChannelID;
|
|
64
|
+
/** The file's path from the channel's root, as the server confirmed it. */
|
|
65
|
+
readonly path: string;
|
|
66
|
+
/** The presigned URL to `GET` the file from. */
|
|
67
|
+
readonly url: string;
|
|
68
|
+
}
|
|
69
|
+
/** One file in a channel's S3 file area. */
|
|
70
|
+
export interface S3FileEntry {
|
|
71
|
+
/** The file's path from the channel's root, with a leading slash: `/docs/a.txt`. */
|
|
72
|
+
readonly path: string;
|
|
73
|
+
/** The size in bytes. */
|
|
74
|
+
readonly size: bigint;
|
|
75
|
+
/** When the storage last wrote it; undefined when the server sent no time it could read. */
|
|
76
|
+
readonly modifiedAt: Date | undefined;
|
|
77
|
+
}
|
|
78
|
+
/** How an S3 transfer reaches the storage. */
|
|
79
|
+
export interface S3TransferOptions {
|
|
80
|
+
/** Abandons the request; it then rejects with the signal's reason. */
|
|
81
|
+
readonly signal?: AbortSignal | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* The `fetch` the request goes through; the global `fetch` by default. Pass
|
|
84
|
+
* one to route through a proxy, trust another certificate authority, time
|
|
85
|
+
* or watch the request, or to test.
|
|
86
|
+
*/
|
|
87
|
+
readonly fetch?: typeof globalThis.fetch | undefined;
|
|
88
|
+
}
|
|
89
|
+
/** How {@link downloadS3FileData} reads the file. */
|
|
90
|
+
export interface S3DownloadDataOptions extends S3TransferOptions {
|
|
91
|
+
/**
|
|
92
|
+
* The byte offset to start from, to resume; 0 by default. The bytes written
|
|
93
|
+
* are the file's from here to its end. The storage answers an offset at or
|
|
94
|
+
* past the end with 416, as an {@link S3TransferError}.
|
|
95
|
+
*/
|
|
96
|
+
readonly seekPosition?: bigint | undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ask the server to sign an upload of `size` bytes to `path` in a channel's S3
|
|
100
|
+
* file area. Pass the result to {@link uploadS3FileData} to send the file, then
|
|
101
|
+
* to {@link completeS3FileUpload}; {@link uploadS3File} does all three.
|
|
102
|
+
*
|
|
103
|
+
* The upload replaces a file already at the path: the storage keeps no other
|
|
104
|
+
* copy, and nothing asks first. A client without upload power is refused with
|
|
105
|
+
* 2049 (invalid file permissions). A path into a directory the channel's file
|
|
106
|
+
* list does not have yet needs directory create power as well, and is refused
|
|
107
|
+
* with 2049 without it; an area with no files at all takes any path. The
|
|
108
|
+
* directory is no object of its own: it is there while a file is in it.
|
|
109
|
+
*
|
|
110
|
+
* Rejects with an {@link S3UnsupportedError} unless the server names `"s3"`
|
|
111
|
+
* among its extensions, and with a `TypeError` for a path that cannot be sent
|
|
112
|
+
* (see {@link uploadS3File}).
|
|
113
|
+
*/
|
|
114
|
+
export declare function initS3FileUpload(client: Client, channelID: ChannelID, path: string, size: bigint, options?: S3FileOptions): Promise<S3FileUploadInfo>;
|
|
115
|
+
/**
|
|
116
|
+
* Ask the server to sign a download of `path` from a channel's S3 file area.
|
|
117
|
+
* Pass the result to {@link downloadS3FileData}; {@link downloadS3File} does
|
|
118
|
+
* both.
|
|
119
|
+
*
|
|
120
|
+
* The server signs a download whether or not the file is there: a missing one
|
|
121
|
+
* is the 404 of the download itself. A client without download power is
|
|
122
|
+
* refused with 2049 (invalid file permissions), as is a channel that does not
|
|
123
|
+
* exist.
|
|
124
|
+
*/
|
|
125
|
+
export declare function initS3FileDownload(client: Client, channelID: ChannelID, path: string, options?: S3FileOptions): Promise<S3FileDownloadInfo>;
|
|
126
|
+
/**
|
|
127
|
+
* Tell the server an upload is stored, after {@link uploadS3FileData}. The
|
|
128
|
+
* server then reads the channel's file list from the storage again, and lists
|
|
129
|
+
* the file; without this it does not.
|
|
130
|
+
*
|
|
131
|
+
* Rejects with an {@link S3TransferError} when the server reports that its own
|
|
132
|
+
* request to the storage failed.
|
|
133
|
+
*/
|
|
134
|
+
export declare function completeS3FileUpload(client: Client, info: S3FileUploadInfo, options?: S3FileOptions): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Every file in a channel's S3 file area, in the storage's order (by path), as
|
|
137
|
+
* the server last read them from the storage.
|
|
138
|
+
*
|
|
139
|
+
* The list is the whole area, files in directories included: there are no
|
|
140
|
+
* directories, only paths. A file uploaded without
|
|
141
|
+
* {@link completeS3FileUpload} is missing from it until the server reads the
|
|
142
|
+
* list again. A channel that does not exist has no files.
|
|
143
|
+
*/
|
|
144
|
+
export declare function listS3Files(client: Client, channelID: ChannelID, options?: S3FileOptions): Promise<S3FileEntry[]>;
|
|
145
|
+
/**
|
|
146
|
+
* Delete files from a channel's S3 file area: one path, or several in one
|
|
147
|
+
* command (`s3ft_deletefiles`, each file on a row of its own). The server
|
|
148
|
+
* deletes them from the storage and from the file list it keeps; an empty list
|
|
149
|
+
* sends nothing.
|
|
150
|
+
*
|
|
151
|
+
* Deleting a file that is not there succeeds, as the storage's delete does. A
|
|
152
|
+
* client without delete power is refused with 2568 (insufficient client
|
|
153
|
+
* permissions), and then nothing is deleted. Rejects with an
|
|
154
|
+
* {@link S3TransferError} when the server reports that its own request to the
|
|
155
|
+
* storage failed.
|
|
156
|
+
*/
|
|
157
|
+
export declare function deleteS3File(client: Client, channelID: ChannelID, paths: string | readonly string[], options?: S3FileOptions): Promise<void>;
|
|
158
|
+
/** One file of a {@link renameS3Files} batch: its path now, and the one it moves to. */
|
|
159
|
+
export interface S3FileRename {
|
|
160
|
+
/** The file's path now, from the channel's root. */
|
|
161
|
+
readonly oldPath: string;
|
|
162
|
+
/** The path it moves to, in the same channel. */
|
|
163
|
+
readonly newPath: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Rename or move one file within a channel's S3 file area (`s3ft_renamefile`).
|
|
167
|
+
* The server copies the file in the storage to the new path, deletes the old
|
|
168
|
+
* one, and updates the file list it keeps. A file already at the new path is
|
|
169
|
+
* replaced. There is no move to another channel.
|
|
170
|
+
*
|
|
171
|
+
* A client without rename power is refused with 2568 (insufficient client
|
|
172
|
+
* permissions). Rejects with an {@link S3TransferError} when the server
|
|
173
|
+
* reports that it did not rename the file: there is none at the old path, the
|
|
174
|
+
* new path is the old one, or the storage refused the server's copy.
|
|
175
|
+
*
|
|
176
|
+
* Measured on TeamSpeak 6 6.0.0-beta13.1: the server's copy carries an
|
|
177
|
+
* `x-amz-copy-source` header it does not sign, which a storage that checks
|
|
178
|
+
* every request's signature refuses (RustFS, versitygw, SeaweedFS and
|
|
179
|
+
* CloudServer did, as S3's signing rules have it), so the rename fails there
|
|
180
|
+
* and the file stays where it was.
|
|
181
|
+
*/
|
|
182
|
+
export declare function renameS3File(client: Client, channelID: ChannelID, oldPath: string, newPath: string, options?: S3FileOptions): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Rename several files within a channel's S3 file area in one command
|
|
185
|
+
* (`s3ft_renamefiles`, each file on a row of its own). Each is renamed as by
|
|
186
|
+
* {@link renameS3File}; an empty list sends nothing.
|
|
187
|
+
*
|
|
188
|
+
* The server tries every rename, and answers once: whether the batch's last
|
|
189
|
+
* rename was done, and nothing about the others. So this rejects with an
|
|
190
|
+
* {@link S3TransferError} when the last one failed, whatever became of the
|
|
191
|
+
* rest, and resolves when it succeeded even if an earlier one failed. List the
|
|
192
|
+
* files, or rename them one at a time, to know each outcome. After a batch
|
|
193
|
+
* whose last rename failed, the list the server keeps shows the others a
|
|
194
|
+
* moment later rather than at once.
|
|
195
|
+
*/
|
|
196
|
+
export declare function renameS3Files(client: Client, channelID: ChannelID, renames: readonly S3FileRename[], options?: S3FileOptions): Promise<void>;
|
|
197
|
+
/**
|
|
198
|
+
* Send a file to the storage, over an upload {@link initS3FileUpload} signed.
|
|
199
|
+
* `data` must be exactly `info.size` bytes: bytes or a `Blob` of another size
|
|
200
|
+
* are refused before anything is sent, and a stream that gives fewer or more
|
|
201
|
+
* fails the upload.
|
|
202
|
+
*
|
|
203
|
+
* Resolves once the storage has answered that it stored the file. Then call
|
|
204
|
+
* {@link completeS3FileUpload}, or the server does not list it. A failure is an
|
|
205
|
+
* {@link S3TransferError}: 403 for a URL that expired.
|
|
206
|
+
*/
|
|
207
|
+
export declare function uploadS3FileData(info: S3FileUploadInfo, data: Uint8Array | Blob | Readable, options?: S3TransferOptions): Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Read a file from the storage into `dest`, over a download
|
|
210
|
+
* {@link initS3FileDownload} signed. Resolves once the file is written; `dest`
|
|
211
|
+
* is not ended, so it may take another download.
|
|
212
|
+
*
|
|
213
|
+
* A file that is not there is an {@link S3TransferError} with status 404; a URL
|
|
214
|
+
* that expired, one with 403.
|
|
215
|
+
*/
|
|
216
|
+
export declare function downloadS3FileData(info: S3FileDownloadInfo, dest: Writable, options?: S3DownloadDataOptions): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* How {@link uploadS3File} signs and sends the file: the options of the
|
|
219
|
+
* commands and those of the request. `signal` applies to both.
|
|
220
|
+
*/
|
|
221
|
+
export interface UploadS3FileOptions extends S3FileOptions, S3TransferOptions {
|
|
222
|
+
/**
|
|
223
|
+
* The size in bytes of a stream passed as the data, which the server signs
|
|
224
|
+
* the upload for. Required for a `Readable`; for bytes or a `Blob` it is
|
|
225
|
+
* their size, and a different value here is refused.
|
|
226
|
+
*/
|
|
227
|
+
readonly size?: bigint | number | undefined;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Upload a file into a channel's S3 file area: sign the upload
|
|
231
|
+
* ({@link initS3FileUpload}), send the file to the storage
|
|
232
|
+
* ({@link uploadS3FileData}) and tell the server it is stored
|
|
233
|
+
* ({@link completeS3FileUpload}), in one call.
|
|
234
|
+
*
|
|
235
|
+
* `data` is the bytes, a `Blob` (such as `fs.openAsBlob(file)`), or a
|
|
236
|
+
* `Readable` with `options.size` the number of bytes it will give. Resolves
|
|
237
|
+
* once the storage has stored the file and the server lists it. The file
|
|
238
|
+
* replaces one already at the path.
|
|
239
|
+
*
|
|
240
|
+
* `path` runs from the channel's root; a leading slash is added when missing,
|
|
241
|
+
* and the directories in it need not exist. A path is refused with a
|
|
242
|
+
* `TypeError` when an HTTP client would not send the URL the server signs for
|
|
243
|
+
* it: one with an empty, `.` or `..` segment, a control character, or a slash
|
|
244
|
+
* at its end.
|
|
245
|
+
*
|
|
246
|
+
* A refusal by the server is the ServerError it sends (2049 for a client
|
|
247
|
+
* without upload power); a failure of the storage request, or a server that
|
|
248
|
+
* reports its own request to the storage failed, is an {@link S3TransferError}. A
|
|
249
|
+
* server that does not name `"s3"` among its extensions is refused with an
|
|
250
|
+
* {@link S3UnsupportedError} before anything is sent.
|
|
251
|
+
*/
|
|
252
|
+
export declare function uploadS3File(client: Client, channelID: ChannelID, path: string, data: Uint8Array | Blob | Readable, options?: UploadS3FileOptions): Promise<void>;
|
|
253
|
+
/**
|
|
254
|
+
* How {@link downloadS3File} signs and reads the file. With a `seekPosition`
|
|
255
|
+
* the bytes are the file's from there to its end.
|
|
256
|
+
*/
|
|
257
|
+
export interface DownloadS3FileOptions extends S3FileOptions, S3DownloadDataOptions {
|
|
258
|
+
/**
|
|
259
|
+
* Write the file into this stream instead of returning its bytes. The stream
|
|
260
|
+
* is neither ended nor destroyed, so it may take several downloads; a failed
|
|
261
|
+
* download still rejects the call.
|
|
262
|
+
*/
|
|
263
|
+
readonly dest?: Writable | undefined;
|
|
264
|
+
/**
|
|
265
|
+
* The most bytes to hold in memory when no `dest` is given; 268435456
|
|
266
|
+
* (256 MiB) by default. A larger download is refused with a
|
|
267
|
+
* {@link FileTransferError} before its body is read.
|
|
268
|
+
*/
|
|
269
|
+
readonly maxSize?: number | undefined;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Download a file from a channel's S3 file area into memory: sign the download
|
|
273
|
+
* ({@link initS3FileDownload}) and read the file ({@link downloadS3FileData}),
|
|
274
|
+
* in one call. With `options.dest` the bytes go into that stream instead and
|
|
275
|
+
* nothing is held, which is the way to fetch a large file.
|
|
276
|
+
*
|
|
277
|
+
* A file that is not there is an {@link S3TransferError} with status 404. Paths
|
|
278
|
+
* and refusals are as for {@link uploadS3File}.
|
|
279
|
+
*/
|
|
280
|
+
export declare function downloadS3File(client: Client, channelID: ChannelID, path: string, options: DownloadS3FileOptions & {
|
|
281
|
+
readonly dest: Writable;
|
|
282
|
+
}): Promise<void>;
|
|
283
|
+
export declare function downloadS3File(client: Client, channelID: ChannelID, path: string, options?: DownloadS3FileOptions): Promise<Uint8Array>;
|
|
284
|
+
//# sourceMappingURL=s3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../src/client/api/s3.ts"],"names":[],"mappings":"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"}
|