@slates/provider 1.0.0-rc.20 → 1.0.0-rc.21

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/dist/index.cjs CHANGED
@@ -1210,8 +1210,38 @@ var createTextAttachment = (content, mimeType, attachmentHash) => ({
1210
1210
  });
1211
1211
 
1212
1212
  // src/action/directUpload.ts
1213
+ var bufferAttachmentBody = async (body, maxSizeBytes) => {
1214
+ if (body instanceof Uint8Array) {
1215
+ if (body.byteLength > maxSizeBytes) {
1216
+ throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
1217
+ }
1218
+ return body;
1219
+ }
1220
+ let reader = body.getReader();
1221
+ let chunks = [];
1222
+ let sizeBytes = 0;
1223
+ while (true) {
1224
+ let { done, value } = await reader.read();
1225
+ if (done) break;
1226
+ if (!value) continue;
1227
+ sizeBytes += value.byteLength;
1228
+ if (sizeBytes > maxSizeBytes) {
1229
+ await reader.cancel();
1230
+ throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
1231
+ }
1232
+ chunks.push(value);
1233
+ }
1234
+ let buffered = new Uint8Array(sizeBytes);
1235
+ let offset = 0;
1236
+ for (let chunk of chunks) {
1237
+ buffered.set(chunk, offset);
1238
+ offset += chunk.byteLength;
1239
+ }
1240
+ return buffered;
1241
+ };
1213
1242
  var uploadAttachmentDirect = async (d) => {
1214
1243
  let baseUrl = d.live.baseUrl.replace(/\/$/, "");
1244
+ let body = await bufferAttachmentBody(d.body, d.live.maxAttachmentSizeBytes);
1215
1245
  let registerRes = await fetch(`${baseUrl}/slates-hub/live-invocation/attachments`, {
1216
1246
  method: "POST",
1217
1247
  headers: {
@@ -1219,7 +1249,13 @@ var uploadAttachmentDirect = async (d) => {
1219
1249
  authorization: `Bearer ${d.live.token}`
1220
1250
  },
1221
1251
  body: JSON.stringify({
1222
- attachments: [{ mimeType: d.mimeType, filename: d.filename }]
1252
+ attachments: [
1253
+ {
1254
+ mimeType: d.mimeType,
1255
+ filename: d.filename,
1256
+ sizeBytes: body.byteLength || void 0
1257
+ }
1258
+ ]
1223
1259
  })
1224
1260
  });
1225
1261
  if (!registerRes.ok) {
@@ -1234,9 +1270,11 @@ var uploadAttachmentDirect = async (d) => {
1234
1270
  }
1235
1271
  let putRes = await fetch(registration.uploadUrl, {
1236
1272
  method: "PUT",
1237
- ...d.body instanceof Uint8Array ? {} : { duplex: "half" },
1238
- headers: d.mimeType ? { "content-type": d.mimeType } : void 0,
1239
- body: d.body
1273
+ headers: {
1274
+ ...d.mimeType ? { "content-type": d.mimeType } : {},
1275
+ "content-length": String(body.byteLength)
1276
+ },
1277
+ body
1240
1278
  });
1241
1279
  if (!putRes.ok) {
1242
1280
  throw new Error(`Failed to upload attachment content (status ${putRes.status})`);
@@ -1101,8 +1101,38 @@ var createTextAttachment = (content, mimeType, attachmentHash) => ({
1101
1101
  });
1102
1102
 
1103
1103
  // src/action/directUpload.ts
1104
+ var bufferAttachmentBody = async (body, maxSizeBytes) => {
1105
+ if (body instanceof Uint8Array) {
1106
+ if (body.byteLength > maxSizeBytes) {
1107
+ throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
1108
+ }
1109
+ return body;
1110
+ }
1111
+ let reader = body.getReader();
1112
+ let chunks = [];
1113
+ let sizeBytes = 0;
1114
+ while (true) {
1115
+ let { done, value } = await reader.read();
1116
+ if (done) break;
1117
+ if (!value) continue;
1118
+ sizeBytes += value.byteLength;
1119
+ if (sizeBytes > maxSizeBytes) {
1120
+ await reader.cancel();
1121
+ throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
1122
+ }
1123
+ chunks.push(value);
1124
+ }
1125
+ let buffered = new Uint8Array(sizeBytes);
1126
+ let offset = 0;
1127
+ for (let chunk of chunks) {
1128
+ buffered.set(chunk, offset);
1129
+ offset += chunk.byteLength;
1130
+ }
1131
+ return buffered;
1132
+ };
1104
1133
  var uploadAttachmentDirect = async (d) => {
1105
1134
  let baseUrl = d.live.baseUrl.replace(/\/$/, "");
1135
+ let body = await bufferAttachmentBody(d.body, d.live.maxAttachmentSizeBytes);
1106
1136
  let registerRes = await fetch(`${baseUrl}/slates-hub/live-invocation/attachments`, {
1107
1137
  method: "POST",
1108
1138
  headers: {
@@ -1110,7 +1140,13 @@ var uploadAttachmentDirect = async (d) => {
1110
1140
  authorization: `Bearer ${d.live.token}`
1111
1141
  },
1112
1142
  body: JSON.stringify({
1113
- attachments: [{ mimeType: d.mimeType, filename: d.filename }]
1143
+ attachments: [
1144
+ {
1145
+ mimeType: d.mimeType,
1146
+ filename: d.filename,
1147
+ sizeBytes: body.byteLength || void 0
1148
+ }
1149
+ ]
1114
1150
  })
1115
1151
  });
1116
1152
  if (!registerRes.ok) {
@@ -1125,9 +1161,11 @@ var uploadAttachmentDirect = async (d) => {
1125
1161
  }
1126
1162
  let putRes = await fetch(registration.uploadUrl, {
1127
1163
  method: "PUT",
1128
- ...d.body instanceof Uint8Array ? {} : { duplex: "half" },
1129
- headers: d.mimeType ? { "content-type": d.mimeType } : void 0,
1130
- body: d.body
1164
+ headers: {
1165
+ ...d.mimeType ? { "content-type": d.mimeType } : {},
1166
+ "content-length": String(body.byteLength)
1167
+ },
1168
+ body
1131
1169
  });
1132
1170
  if (!putRes.ok) {
1133
1171
  throw new Error(`Failed to upload attachment content (status ${putRes.status})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slates/provider",
3
- "version": "1.0.0-rc.20",
3
+ "version": "1.0.0-rc.21",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -0,0 +1,70 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { uploadAttachmentDirect } from './directUpload';
3
+
4
+ let live = {
5
+ token: 'live-token',
6
+ baseUrl: 'https://hub.example/',
7
+ maxAttachmentSizeBytes: 10
8
+ };
9
+
10
+ describe('uploadAttachmentDirect', () => {
11
+ afterEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+
15
+ it('buffers streams and uploads them with their exact content length', async () => {
16
+ let fetchSpy = vi
17
+ .spyOn(globalThis, 'fetch')
18
+ .mockResolvedValueOnce(
19
+ Response.json({
20
+ attachments: [
21
+ { referenceId: 'attachment-reference', uploadUrl: 'https://s3.example/upload' }
22
+ ]
23
+ })
24
+ )
25
+ .mockResolvedValueOnce(new Response(null, { status: 200 }));
26
+ let body = new ReadableStream<Uint8Array>({
27
+ start(controller) {
28
+ controller.enqueue(new Uint8Array([1, 2]));
29
+ controller.enqueue(new Uint8Array([3, 4, 5]));
30
+ controller.close();
31
+ }
32
+ });
33
+
34
+ await expect(
35
+ uploadAttachmentDirect({ live, body, mimeType: 'application/octet-stream' })
36
+ ).resolves.toEqual({
37
+ mimeType: 'application/octet-stream',
38
+ content: { type: 'upload_reference', referenceId: 'attachment-reference' }
39
+ });
40
+
41
+ expect(JSON.parse(fetchSpy.mock.calls[0]![1]!.body as string)).toEqual({
42
+ attachments: [{ mimeType: 'application/octet-stream', sizeBytes: 5 }]
43
+ });
44
+ let upload = fetchSpy.mock.calls[1]!;
45
+ expect(upload[0]).toBe('https://s3.example/upload');
46
+ expect(upload[1]).toMatchObject({
47
+ method: 'PUT',
48
+ headers: {
49
+ 'content-type': 'application/octet-stream',
50
+ 'content-length': '5'
51
+ }
52
+ });
53
+ expect(upload[1]!.body).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
54
+ expect(upload[1]).not.toHaveProperty('duplex');
55
+ });
56
+
57
+ it('rejects an oversized stream before registering an upload', async () => {
58
+ let fetchSpy = vi.spyOn(globalThis, 'fetch');
59
+ let body = new ReadableStream<Uint8Array>({
60
+ start(controller) {
61
+ controller.enqueue(new Uint8Array(11));
62
+ }
63
+ });
64
+
65
+ await expect(uploadAttachmentDirect({ live, body })).rejects.toThrow(
66
+ 'Attachment exceeds the maximum size of 10 bytes'
67
+ );
68
+ expect(fetchSpy).not.toHaveBeenCalled();
69
+ });
70
+ });
@@ -13,10 +13,48 @@ export interface SlateDirectUploadInput {
13
13
  body: ReadableStream<Uint8Array> | Uint8Array;
14
14
  }
15
15
 
16
+ let bufferAttachmentBody = async (
17
+ body: SlateDirectUploadInput['body'],
18
+ maxSizeBytes: number
19
+ ) => {
20
+ if (body instanceof Uint8Array) {
21
+ if (body.byteLength > maxSizeBytes) {
22
+ throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
23
+ }
24
+ return body;
25
+ }
26
+
27
+ let reader = body.getReader();
28
+ let chunks: Uint8Array[] = [];
29
+ let sizeBytes = 0;
30
+
31
+ while (true) {
32
+ let { done, value } = await reader.read();
33
+ if (done) break;
34
+ if (!value) continue;
35
+
36
+ sizeBytes += value.byteLength;
37
+ if (sizeBytes > maxSizeBytes) {
38
+ await reader.cancel();
39
+ throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
40
+ }
41
+ chunks.push(value);
42
+ }
43
+
44
+ let buffered = new Uint8Array(sizeBytes);
45
+ let offset = 0;
46
+ for (let chunk of chunks) {
47
+ buffered.set(chunk, offset);
48
+ offset += chunk.byteLength;
49
+ }
50
+ return buffered;
51
+ };
52
+
16
53
  export let uploadAttachmentDirect = async (
17
54
  d: SlateDirectUploadInput
18
55
  ): Promise<SlateAttachment> => {
19
56
  let baseUrl = d.live.baseUrl.replace(/\/$/, '');
57
+ let body = await bufferAttachmentBody(d.body, d.live.maxAttachmentSizeBytes);
20
58
 
21
59
  let registerRes = await fetch(`${baseUrl}/slates-hub/live-invocation/attachments`, {
22
60
  method: 'POST',
@@ -25,7 +63,13 @@ export let uploadAttachmentDirect = async (
25
63
  authorization: `Bearer ${d.live.token}`
26
64
  },
27
65
  body: JSON.stringify({
28
- attachments: [{ mimeType: d.mimeType, filename: d.filename }]
66
+ attachments: [
67
+ {
68
+ mimeType: d.mimeType,
69
+ filename: d.filename,
70
+ sizeBytes: body.byteLength || undefined
71
+ }
72
+ ]
29
73
  })
30
74
  });
31
75
  if (!registerRes.ok) {
@@ -44,9 +88,11 @@ export let uploadAttachmentDirect = async (
44
88
 
45
89
  let putRes = await fetch(registration.uploadUrl, {
46
90
  method: 'PUT',
47
- ...(d.body instanceof Uint8Array ? {} : ({ duplex: 'half' } as any)),
48
- headers: d.mimeType ? { 'content-type': d.mimeType } : undefined,
49
- body: d.body
91
+ headers: {
92
+ ...(d.mimeType ? { 'content-type': d.mimeType } : {}),
93
+ 'content-length': String(body.byteLength)
94
+ },
95
+ body
50
96
  });
51
97
  if (!putRes.ok) {
52
98
  throw new Error(`Failed to upload attachment content (status ${putRes.status})`);