@swiftbrowser/native 0.18.3 → 0.21.0
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 +53 -11
- package/bin/swiftbrowser-native.mjs +47 -0
- package/dist/blob.d.ts +85 -11
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +184 -34
- package/dist/blob.js.map +1 -1
- package/dist/bridge.d.ts +66 -8
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js.map +1 -1
- package/dist/encoding.d.ts +13 -0
- package/dist/encoding.d.ts.map +1 -1
- package/dist/encoding.js +24 -0
- package/dist/encoding.js.map +1 -1
- package/dist/fetch.d.ts +27 -13
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +117 -45
- package/dist/fetch.js.map +1 -1
- package/dist/hostRequests.d.ts +24 -6
- package/dist/hostRequests.d.ts.map +1 -1
- package/dist/hostRequests.js +108 -15
- package/dist/hostRequests.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/ios.d.ts +79 -0
- package/dist/ios.d.ts.map +1 -0
- package/dist/ios.js +181 -0
- package/dist/ios.js.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +16 -7
- package/dist/runtime.js.map +1 -1
- package/dist/subtle.d.ts +1 -1
- package/dist/subtle.d.ts.map +1 -1
- package/dist/subtle.js +9 -5
- package/dist/subtle.js.map +1 -1
- package/dist/webSocket.d.ts +12 -5
- package/dist/webSocket.d.ts.map +1 -1
- package/dist/webSocket.js +37 -11
- package/dist/webSocket.js.map +1 -1
- package/package.json +7 -3
- package/src/blob.ts +215 -34
- package/src/bridge.ts +56 -8
- package/src/encoding.ts +22 -0
- package/src/fetch.ts +111 -48
- package/src/hostRequests.ts +129 -22
- package/src/index.ts +1 -2
- package/src/ios.ts +253 -0
- package/src/runtime.ts +23 -8
- package/src/subtle.ts +10 -6
- package/src/webSocket.ts +37 -13
- package/swift/Packages/SwiftBrowserDSP/Package.swift +31 -0
- package/swift/Packages/SwiftBrowserDSP/README.md +42 -0
- package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ProgramNode.swift +306 -0
- package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ScopeView.swift +79 -0
- package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Machine.swift +1561 -0
- package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Program.swift +483 -0
- package/swift/Packages/SwiftBrowserLocalModel/Package.swift +35 -0
- package/swift/Packages/SwiftBrowserLocalModel/README.md +66 -0
- package/swift/Packages/SwiftBrowserLocalModel/Sources/SwiftBrowserLocalModel/LlamaEngine.swift +223 -0
- package/swift/Packages/SwiftBrowserLocalModel/Sources/SwiftBrowserLocalModel/LocalLanguageModel.swift +409 -0
- package/swift/Packages/SwiftBrowserShader/Package.swift +31 -0
- package/swift/Packages/SwiftBrowserShader/README.md +45 -0
- package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderRenderer.swift +103 -0
- package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderView.swift +168 -0
- package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShaderCore/ShaderProps.swift +120 -0
- package/swift/Sources/SwiftBrowserNative/Host/AudioEngineBridge.swift +921 -0
- package/swift/Sources/SwiftBrowserNative/Host/AudioEngineNodes.swift +70 -0
- package/swift/Sources/SwiftBrowserNative/Host/BodyStream.swift +166 -0
- package/swift/Sources/SwiftBrowserNative/Host/FilesBridge.swift +144 -10
- package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +151 -27
- package/swift/Sources/SwiftBrowserNative/Host/HostRequestBridge.swift +139 -40
- package/swift/Sources/SwiftBrowserNative/Host/JSHost.swift +116 -0
- package/swift/Sources/SwiftBrowserNative/Host/JavaScriptCoreHost.swift +141 -2
- package/swift/Sources/SwiftBrowserNative/Host/LanguageModelBridge.swift +511 -0
- package/swift/Sources/SwiftBrowserNative/Host/LanguageModelProvider.swift +195 -0
- package/swift/Sources/SwiftBrowserNative/Host/WebSocketBridge.swift +24 -11
- package/swift/Sources/SwiftBrowserNative/Host/WorkerHost.swift +3 -2
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeFunctions.swift +1 -1
- package/swift/Sources/SwiftBrowserNative/Runtime/ViewRegistry.swift +57 -0
- package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +12 -7
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +18 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Drawing.swift +378 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Lists.swift +41 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/More.swift +88 -17
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +87 -21
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/PinchTracker.swift +200 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/PopTap.swift +103 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +3 -2
- package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +146 -0
- package/swift/Sources/SwiftBrowserProtocol/Drawing.swift +306 -0
- package/swift/Sources/SwiftBrowserProtocol/Props.swift +31 -2
- package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +24 -3
- package/swift/Sources/SwiftBrowserProtocol/Style.swift +87 -0
- package/swift/Sources/SwiftBrowserTree/OpTree.swift +25 -0
- package/wasm/SwiftBrowserNative.wasm +2 -2
- package/dist/native.d.ts +0 -19
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js +0 -17
- package/dist/native.js.map +0 -1
- package/src/native.ts +0 -35
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeRegistry.swift +0 -53
package/dist/subtle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subtle.js","sourceRoot":"","sources":["../src/subtle.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAexD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAEpE,SAAS,aAAa,CAAC,SAAoB;IACzC,OAAO,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AACpF,CAAC;AAED,SAAS,QAAQ,CAAC,SAAoB;IACpC,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC,WAAW,EAAE,CAAC;IAClE,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAC5B,OAAO,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"subtle.js","sourceRoot":"","sources":["../src/subtle.ts"],"names":[],"mappings":"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"}
|
package/dist/webSocket.d.ts
CHANGED
|
@@ -26,7 +26,12 @@ export interface NativeWebSocketEvent {
|
|
|
26
26
|
protocol?: string;
|
|
27
27
|
text?: string;
|
|
28
28
|
/** Base64. */
|
|
29
|
-
|
|
29
|
+
/** Base64, or `{ $bytes: 0 }` naming the `Uint8Array` after the event's JSON. */
|
|
30
|
+
binary?: string | {
|
|
31
|
+
$bytes: number;
|
|
32
|
+
};
|
|
33
|
+
/** The views that came after the JSON. */
|
|
34
|
+
bytes?: ArrayBufferView[];
|
|
30
35
|
message?: string;
|
|
31
36
|
code?: number;
|
|
32
37
|
reason?: string;
|
|
@@ -64,10 +69,12 @@ export declare class NativeWebSocket {
|
|
|
64
69
|
dispatchEvent(event: WebSocketEventLike): boolean;
|
|
65
70
|
/** The bridge's event for this socket. */
|
|
66
71
|
receive(event: NativeWebSocketEvent): void;
|
|
67
|
-
/** The bridge's `__sbWebSocketEvent(id, json)
|
|
68
|
-
static receive(id: number, json: string | NativeWebSocketEvent): void;
|
|
69
|
-
/** Points the sockets at the bridge (the runtime, once per install). */
|
|
70
|
-
static install(native: WebSocketBridge
|
|
72
|
+
/** The bridge's `__sbWebSocketEvent(id, json, ...bytes)`: a binary message's bytes after the JSON that names them. */
|
|
73
|
+
static receive(id: number, json: string | NativeWebSocketEvent, ...bytes: ArrayBufferView[]): void;
|
|
74
|
+
/** Points the sockets at the bridge (the runtime, once per install); `bytes`: the host carries typed arrays. */
|
|
75
|
+
static install(native: WebSocketBridge, options?: {
|
|
76
|
+
bytes?: boolean;
|
|
77
|
+
}): void;
|
|
71
78
|
}
|
|
72
79
|
export {};
|
|
73
80
|
//# sourceMappingURL=webSocket.d.ts.map
|
package/dist/webSocket.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webSocket.d.ts","sourceRoot":"","sources":["../src/webSocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"webSocket.d.ts","sourceRoot":"","sources":["../src/webSocket.ts"],"names":[],"mappings":"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"}
|
package/dist/webSocket.js
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* `onmessage`, `onerror`, `onclose`, `addEventListener`, `readyState`,
|
|
9
9
|
* `binaryType` (`blob` by default, or `arraybuffer`).
|
|
10
10
|
*/
|
|
11
|
-
import { SimpleBlob } from './blob';
|
|
12
|
-
import {
|
|
11
|
+
import { blobBytes, LazyFile, SimpleBlob } from './blob';
|
|
12
|
+
import { base64Encode, bridgedBytes } from './encoding';
|
|
13
13
|
const sockets = new Map();
|
|
14
14
|
let nextId = 1;
|
|
15
15
|
let bridge = null;
|
|
16
|
+
/** Whether binary crosses as views beside the JSON (a host that carries bytes) rather than base64. */
|
|
17
|
+
let carriesBytes = false;
|
|
16
18
|
export class NativeWebSocket {
|
|
17
19
|
static CONNECTING = 0;
|
|
18
20
|
static OPEN = 1;
|
|
@@ -43,7 +45,11 @@ export class NativeWebSocket {
|
|
|
43
45
|
this.url = href;
|
|
44
46
|
this.id = nextId++;
|
|
45
47
|
sockets.set(this.id, this);
|
|
46
|
-
|
|
48
|
+
const list = typeof protocols === 'string' ? [protocols] : [...protocols];
|
|
49
|
+
if (carriesBytes)
|
|
50
|
+
bridge.wsOpen(this.id, href, list, true);
|
|
51
|
+
else
|
|
52
|
+
bridge.wsOpen(this.id, href, list);
|
|
47
53
|
}
|
|
48
54
|
send(data) {
|
|
49
55
|
if (this.readyState === NativeWebSocket.CONNECTING)
|
|
@@ -54,8 +60,27 @@ export class NativeWebSocket {
|
|
|
54
60
|
bridge?.wsSend(this.id, JSON.stringify({ text: data }));
|
|
55
61
|
return;
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
if (data instanceof LazyFile) {
|
|
64
|
+
// A picked file on disk: read it, then send (the frame goes out when the read lands; order among lazy sends is the reads').
|
|
65
|
+
void blobBytes(data).then((bytes) => {
|
|
66
|
+
if (this.readyState !== NativeWebSocket.OPEN)
|
|
67
|
+
return;
|
|
68
|
+
if (carriesBytes)
|
|
69
|
+
bridge?.wsSend(this.id, JSON.stringify({ binary: { $bytes: 0 } }), bytes);
|
|
70
|
+
else
|
|
71
|
+
bridge?.wsSend(this.id, JSON.stringify({ binary: base64Encode(bytes) }));
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const bytes = data instanceof SimpleBlob
|
|
76
|
+
? data.bytes()
|
|
77
|
+
: data instanceof ArrayBuffer
|
|
78
|
+
? new Uint8Array(data)
|
|
79
|
+
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
80
|
+
if (carriesBytes)
|
|
81
|
+
bridge?.wsSend(this.id, JSON.stringify({ binary: { $bytes: 0 } }), bytes);
|
|
82
|
+
else
|
|
83
|
+
bridge?.wsSend(this.id, JSON.stringify({ binary: base64Encode(bytes) }));
|
|
59
84
|
}
|
|
60
85
|
close(code, reason) {
|
|
61
86
|
if (code !== undefined && code !== 1000 && !(code >= 3000 && code <= 4999))
|
|
@@ -98,7 +123,7 @@ export class NativeWebSocket {
|
|
|
98
123
|
return;
|
|
99
124
|
let data = event.text ?? '';
|
|
100
125
|
if (event.binary !== undefined) {
|
|
101
|
-
const bytes =
|
|
126
|
+
const bytes = bridgedBytes(typeof event.binary === 'string' ? event.binary : event.bytes?.[event.binary.$bytes]);
|
|
102
127
|
data = this.binaryType === 'arraybuffer' ? bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) : new SimpleBlob([bytes]);
|
|
103
128
|
}
|
|
104
129
|
this.dispatchEvent({ type: 'message', target: this, data });
|
|
@@ -116,8 +141,8 @@ export class NativeWebSocket {
|
|
|
116
141
|
return;
|
|
117
142
|
}
|
|
118
143
|
}
|
|
119
|
-
/** The bridge's `__sbWebSocketEvent(id, json)
|
|
120
|
-
static receive(id, json) {
|
|
144
|
+
/** The bridge's `__sbWebSocketEvent(id, json, ...bytes)`: a binary message's bytes after the JSON that names them. */
|
|
145
|
+
static receive(id, json, ...bytes) {
|
|
121
146
|
const socket = sockets.get(id);
|
|
122
147
|
if (!socket)
|
|
123
148
|
return;
|
|
@@ -128,11 +153,12 @@ export class NativeWebSocket {
|
|
|
128
153
|
catch {
|
|
129
154
|
return;
|
|
130
155
|
}
|
|
131
|
-
socket.receive(event);
|
|
156
|
+
socket.receive(bytes.length ? { ...event, bytes } : event);
|
|
132
157
|
}
|
|
133
|
-
/** Points the sockets at the bridge (the runtime, once per install). */
|
|
134
|
-
static install(native) {
|
|
158
|
+
/** Points the sockets at the bridge (the runtime, once per install); `bytes`: the host carries typed arrays. */
|
|
159
|
+
static install(native, options = {}) {
|
|
135
160
|
bridge = native;
|
|
161
|
+
carriesBytes = options.bytes === true;
|
|
136
162
|
}
|
|
137
163
|
}
|
|
138
164
|
/** A `DOMException` stand-in: an error with the browser's name. */
|
package/dist/webSocket.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webSocket.js","sourceRoot":"","sources":["../src/webSocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"webSocket.js","sourceRoot":"","sources":["../src/webSocket.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swiftbrowser/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run a @swiftbrowser/react app natively: the runtime a JavaScriptCore-hosted app needs (timers, console, fetch and the other web globals over the SwiftBrowser Native bridge), the `run` entry that renders into the native SwiftUI renderer, and the bundler that builds app.js for the Xcode target.",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"./wasm-host": {
|
|
31
31
|
"types": "./dist/wasmHost.d.ts",
|
|
32
32
|
"default": "./dist/wasmHost.js"
|
|
33
|
+
},
|
|
34
|
+
"./ios": {
|
|
35
|
+
"types": "./dist/ios.d.ts",
|
|
36
|
+
"default": "./dist/ios.js"
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"bin": {
|
|
@@ -59,8 +63,8 @@
|
|
|
59
63
|
},
|
|
60
64
|
"dependencies": {
|
|
61
65
|
"@babel/core": "^7.29.7",
|
|
62
|
-
"@swiftbrowser/react": "0.
|
|
63
|
-
"@swiftbrowser/web": "0.
|
|
66
|
+
"@swiftbrowser/react": "0.21.0",
|
|
67
|
+
"@swiftbrowser/web": "0.21.0",
|
|
64
68
|
"esbuild": "^0.28.2",
|
|
65
69
|
"react-refresh": "^0.17.0"
|
|
66
70
|
},
|
package/src/blob.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Blob`, `File` and `FormData` for the native app: bytes with a type, a
|
|
3
|
-
* named one, and a form whose entries `fetch` sends as multipart. A
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
3
|
+
* named one, and a form whose entries `fetch` sends as multipart. A blob is
|
|
4
|
+
* a list of parts, each bytes in memory or a range of a file on disk (a
|
|
5
|
+
* `LazyFile`, as the pickers and drops answer), so a `Blob` built from
|
|
6
|
+
* picked files' slices holds nothing in memory and `fetch` streams it from
|
|
7
|
+
* disk (fetch.ts). `URL.createObjectURL` gives a `data:` URL, which the
|
|
8
|
+
* host's image loader and `URLSession` both read, and `revokeObjectURL` has
|
|
9
|
+
* nothing to free.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
import { base64Encode, utf8Decode, utf8Encode } from './encoding';
|
|
10
13
|
|
|
11
14
|
export type BlobPartLike = string | ArrayBuffer | ArrayBufferView | SimpleBlob;
|
|
12
15
|
|
|
16
|
+
/** What a blob, and so a request body, is made of: bytes here, or a range of a file on disk. */
|
|
17
|
+
export type BodyPart = { bytes: Uint8Array } | { file: LazyFile };
|
|
18
|
+
|
|
13
19
|
export interface BlobOptions {
|
|
14
20
|
type?: string;
|
|
15
21
|
}
|
|
@@ -18,12 +24,25 @@ export interface FileOptions extends BlobOptions {
|
|
|
18
24
|
lastModified?: number;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (part
|
|
24
|
-
if (part instanceof
|
|
25
|
-
if (ArrayBuffer
|
|
26
|
-
return
|
|
27
|
+
/** A constructor part as body parts: a blob's own parts are taken as they are (a file on disk stays there). */
|
|
28
|
+
function toParts(part: BlobPartLike): BodyPart[] {
|
|
29
|
+
if (typeof part === 'string') return [{ bytes: utf8Encode(part) }];
|
|
30
|
+
if (part instanceof SimpleBlob) return part.parts();
|
|
31
|
+
if (part instanceof ArrayBuffer) return [{ bytes: new Uint8Array(part.slice(0)) }];
|
|
32
|
+
if (ArrayBuffer.isView(part)) return [{ bytes: new Uint8Array(part.buffer.slice(part.byteOffset, part.byteOffset + part.byteLength)) }];
|
|
33
|
+
return [{ bytes: utf8Encode(String(part)) }];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const partSize = (part: BodyPart): number => ('bytes' in part ? part.bytes.byteLength : part.file.size);
|
|
37
|
+
|
|
38
|
+
/** Whether any part is a file on disk, which only an asynchronous read reaches. */
|
|
39
|
+
export const partsAreLazy = (parts: readonly BodyPart[]): boolean => parts.some((part) => 'file' in part);
|
|
40
|
+
|
|
41
|
+
/** The parts' bytes, whole: those on disk read through the files bridge. */
|
|
42
|
+
export async function partsBytes(parts: readonly BodyPart[]): Promise<Uint8Array> {
|
|
43
|
+
const read: Uint8Array[] = [];
|
|
44
|
+
for (const part of parts) read.push('bytes' in part ? part.bytes : await part.file.bytesAsync());
|
|
45
|
+
return concat(read);
|
|
27
46
|
}
|
|
28
47
|
|
|
29
48
|
function concat(parts: Uint8Array[]): Uint8Array {
|
|
@@ -37,39 +56,78 @@ function concat(parts: Uint8Array[]): Uint8Array {
|
|
|
37
56
|
return out;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
|
-
/** The browser's `Blob`: bytes and a MIME type. */
|
|
59
|
+
/** The browser's `Blob`: parts (bytes, or files on disk) and a MIME type. */
|
|
41
60
|
export class SimpleBlob {
|
|
42
|
-
private readonly
|
|
61
|
+
private readonly parts_: BodyPart[];
|
|
62
|
+
/** The parts joined, once asked for (a blob with nothing on disk). */
|
|
63
|
+
private joined: Uint8Array | null = null;
|
|
43
64
|
readonly type: string;
|
|
44
65
|
|
|
45
66
|
constructor(parts: Iterable<BlobPartLike> = [], options: BlobOptions = {}) {
|
|
46
|
-
this.
|
|
67
|
+
this.parts_ = [...parts].flatMap(toParts);
|
|
47
68
|
// As the browser does: lowercased, and empty when it is not printable ASCII.
|
|
48
69
|
const type = options.type ?? '';
|
|
49
70
|
this.type = /^[\x20-\x7e]*$/.test(type) ? type.toLowerCase() : '';
|
|
50
71
|
}
|
|
51
72
|
|
|
73
|
+
/** A blob straight from parts (a slice). */
|
|
74
|
+
protected static fromParts(parts: BodyPart[], type: string): SimpleBlob {
|
|
75
|
+
const blob = new SimpleBlob([], { type });
|
|
76
|
+
blob.parts_.push(...parts);
|
|
77
|
+
return blob;
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
get size(): number {
|
|
53
|
-
return this.
|
|
81
|
+
return this.parts_.reduce((sum, part) => sum + partSize(part), 0);
|
|
54
82
|
}
|
|
55
83
|
|
|
56
|
-
/** The
|
|
84
|
+
/** The parts, in order: what a request body sends, each from memory or from disk. */
|
|
85
|
+
parts(): BodyPart[] {
|
|
86
|
+
return [...this.parts_];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Whether any part is still on disk, so `bytes()` cannot answer and `bytesAsync()` reads it. */
|
|
90
|
+
get isLazy(): boolean {
|
|
91
|
+
return partsAreLazy(this.parts_);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The bytes (a view; callers must not write into it). Throws for a blob holding a file on disk: read it with `bytesAsync`. */
|
|
57
95
|
bytes(): Uint8Array {
|
|
58
|
-
|
|
96
|
+
if (this.isLazy) throw new Error('unsupported: the blob holds a picked file, which is read asynchronously (arrayBuffer); it is not in memory');
|
|
97
|
+
if (!this.joined) this.joined = this.parts_.length === 1 && 'bytes' in this.parts_[0]! ? this.parts_[0].bytes : concat(this.parts_.map((part) => ('bytes' in part ? part.bytes : new Uint8Array(0))));
|
|
98
|
+
return this.joined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The bytes, the parts on disk read now. */
|
|
102
|
+
async bytesAsync(): Promise<Uint8Array> {
|
|
103
|
+
return this.isLazy ? partsBytes(this.parts_) : this.bytes();
|
|
59
104
|
}
|
|
60
105
|
|
|
61
106
|
slice(start = 0, end = this.size, contentType = ''): SimpleBlob {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
107
|
+
const size = this.size;
|
|
108
|
+
const from = start < 0 ? Math.max(size + start, 0) : Math.min(start, size);
|
|
109
|
+
const to = Math.max(from, end < 0 ? Math.max(size + end, 0) : Math.min(end, size));
|
|
110
|
+
const out: BodyPart[] = [];
|
|
111
|
+
let at = 0;
|
|
112
|
+
for (const part of this.parts_) {
|
|
113
|
+
const length = partSize(part);
|
|
114
|
+
const partStart = at;
|
|
115
|
+
at += length;
|
|
116
|
+
if (at <= from || partStart >= to) continue;
|
|
117
|
+
const a = Math.max(from, partStart) - partStart;
|
|
118
|
+
const b = Math.min(to, at) - partStart;
|
|
119
|
+
out.push('bytes' in part ? { bytes: part.bytes.subarray(a, b) } : { file: part.file.slice(a, b) });
|
|
120
|
+
}
|
|
121
|
+
return SimpleBlob.fromParts(out, contentType);
|
|
65
122
|
}
|
|
66
123
|
|
|
67
124
|
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
68
|
-
|
|
125
|
+
const bytes = await this.bytesAsync();
|
|
126
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
69
127
|
}
|
|
70
128
|
|
|
71
129
|
async text(): Promise<string> {
|
|
72
|
-
return utf8Decode(this.
|
|
130
|
+
return utf8Decode(await this.bytesAsync());
|
|
73
131
|
}
|
|
74
132
|
|
|
75
133
|
get [Symbol.toStringTag](): string {
|
|
@@ -93,6 +151,98 @@ export class SimpleFile extends SimpleBlob {
|
|
|
93
151
|
}
|
|
94
152
|
}
|
|
95
153
|
|
|
154
|
+
/**
|
|
155
|
+
* A file the viewer picked or dropped, still on disk: its name, type and size
|
|
156
|
+
* are known, its bytes are read through the host's files bridge only when
|
|
157
|
+
* asked (`arrayBuffer`, `bytesAsync`, or a `fetch` body), a range at a time
|
|
158
|
+
* for a `slice`, so a large file never sits in memory whole. `fileManager.write`
|
|
159
|
+
* copies one on disk instead of reading it. `bytes()` (synchronous) is not
|
|
160
|
+
* available: it throws, as the bytes are not here.
|
|
161
|
+
*/
|
|
162
|
+
export class LazyFile extends SimpleFile {
|
|
163
|
+
/** The `file:` URL the host reads it at. */
|
|
164
|
+
readonly url: string;
|
|
165
|
+
/** The range of the file on disk this stands for (`slice` narrows it). */
|
|
166
|
+
readonly start: number;
|
|
167
|
+
readonly end: number;
|
|
168
|
+
private readonly reader: (url: string, start: number, end: number) => Promise<Uint8Array>;
|
|
169
|
+
|
|
170
|
+
constructor(
|
|
171
|
+
url: string,
|
|
172
|
+
name: string,
|
|
173
|
+
size: number,
|
|
174
|
+
reader: (url: string, start: number, end: number) => Promise<Uint8Array>,
|
|
175
|
+
options: FileOptions = {},
|
|
176
|
+
range: { start: number; end: number } = { start: 0, end: size },
|
|
177
|
+
) {
|
|
178
|
+
super([], name, options);
|
|
179
|
+
this.url = url;
|
|
180
|
+
this.reader = reader;
|
|
181
|
+
this.start = range.start;
|
|
182
|
+
this.end = range.end;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Whether this is the whole file on disk, not a slice of it. */
|
|
186
|
+
get isWhole(): boolean {
|
|
187
|
+
return this.start === 0 && this.end === this.sizeOnDisk;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private sizeOnDisk = 0;
|
|
191
|
+
|
|
192
|
+
/** Sets the file's size on disk (the constructor's `size`); the range defaults to all of it. */
|
|
193
|
+
static make(url: string, name: string, size: number, reader: (url: string, start: number, end: number) => Promise<Uint8Array>, options: FileOptions = {}): LazyFile {
|
|
194
|
+
const file = new LazyFile(url, name, size, reader, options);
|
|
195
|
+
file.sizeOnDisk = size;
|
|
196
|
+
return file;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
override get size(): number {
|
|
200
|
+
return this.end - this.start;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
override parts(): BodyPart[] {
|
|
204
|
+
return [{ file: this }];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
override get isLazy(): boolean {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
override bytes(): Uint8Array {
|
|
212
|
+
throw new Error('unsupported: a picked file is read asynchronously (arrayBuffer); it is not in memory');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The bytes, read from disk now. */
|
|
216
|
+
override async bytesAsync(): Promise<Uint8Array> {
|
|
217
|
+
return this.reader(this.url, this.start, this.end);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
override slice(start = 0, end = this.size, contentType = ''): LazyFile {
|
|
221
|
+
const size = this.size;
|
|
222
|
+
const from = start < 0 ? Math.max(size + start, 0) : Math.min(start, size);
|
|
223
|
+
const to = end < 0 ? Math.max(size + end, 0) : Math.min(end, size);
|
|
224
|
+
const sliced = new LazyFile(this.url, this.name, this.sizeOnDisk, this.reader, { type: contentType, lastModified: this.lastModified }, {
|
|
225
|
+
start: this.start + from,
|
|
226
|
+
end: this.start + Math.max(from, to),
|
|
227
|
+
});
|
|
228
|
+
sliced.sizeOnDisk = this.sizeOnDisk;
|
|
229
|
+
return sliced;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The same file under another name (a form's `filename`), still on disk. */
|
|
233
|
+
renamed(name: string): LazyFile {
|
|
234
|
+
const copy = new LazyFile(this.url, name, this.sizeOnDisk, this.reader, { type: this.type, lastModified: this.lastModified }, { start: this.start, end: this.end });
|
|
235
|
+
copy.sizeOnDisk = this.sizeOnDisk;
|
|
236
|
+
return copy;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** A blob's bytes, read from disk where it holds a `LazyFile`. */
|
|
242
|
+
export async function blobBytes(blob: SimpleBlob): Promise<Uint8Array> {
|
|
243
|
+
return blob.bytesAsync();
|
|
244
|
+
}
|
|
245
|
+
|
|
96
246
|
export type FormDataEntryValueLike = string | SimpleFile;
|
|
97
247
|
|
|
98
248
|
/** The browser's `FormData`, as far as a request body needs it. */
|
|
@@ -102,11 +252,17 @@ export class SimpleFormData {
|
|
|
102
252
|
private static value(value: unknown, filename?: string): FormDataEntryValueLike {
|
|
103
253
|
if (value instanceof SimpleBlob) {
|
|
104
254
|
const name = filename ?? (value instanceof SimpleFile ? value.name : 'blob');
|
|
255
|
+
if (value instanceof LazyFile) return filename === undefined ? value : value.renamed(filename);
|
|
105
256
|
return value instanceof SimpleFile && filename === undefined ? value : new SimpleFile([value], name, { type: value.type });
|
|
106
257
|
}
|
|
107
258
|
return String(value);
|
|
108
259
|
}
|
|
109
260
|
|
|
261
|
+
/** Whether an entry is a picked file still on disk (or a blob holding one), which the form's body streams from there. */
|
|
262
|
+
get hasLazyFiles(): boolean {
|
|
263
|
+
return this.entries_.some(([, value]) => typeof value !== 'string' && value.isLazy);
|
|
264
|
+
}
|
|
265
|
+
|
|
110
266
|
append(name: string, value: unknown, filename?: string): void {
|
|
111
267
|
this.entries_.push([String(name), SimpleFormData.value(value, filename)]);
|
|
112
268
|
}
|
|
@@ -164,23 +320,48 @@ export class SimpleFormData {
|
|
|
164
320
|
}
|
|
165
321
|
}
|
|
166
322
|
|
|
167
|
-
/**
|
|
168
|
-
export function
|
|
169
|
-
|
|
323
|
+
/** The boundary of a multipart body, unique by `random`'s bytes. */
|
|
324
|
+
export function multipartBoundary(random: (count: number) => Uint8Array): string {
|
|
325
|
+
return `----SwiftBrowserFormBoundary${Array.from(random(16), (byte) => byte.toString(16).padStart(2, '0')).join('')}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function multipartHead(boundary: string, name: string, value: FormDataEntryValueLike): Uint8Array {
|
|
170
329
|
const escape = (text: string): string => text.replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/"/g, '%22');
|
|
171
|
-
const
|
|
330
|
+
const head = [`--${boundary}`, `Content-Disposition: form-data; name="${escape(name)}"` + (typeof value === 'string' ? '' : `; filename="${escape(value.name)}"`)];
|
|
331
|
+
if (typeof value !== 'string') head.push(`Content-Type: ${value.type || 'application/octet-stream'}`);
|
|
332
|
+
return utf8Encode(`${head.join('\r\n')}\r\n\r\n`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* A multipart/form-data body for the form, with the boundary `random` makes
|
|
337
|
+
* unique, as parts: the heads and the fields in memory, a file still on disk
|
|
338
|
+
* as the range of it (`fetch` streams those from disk, or reads them first
|
|
339
|
+
* for a host that cannot).
|
|
340
|
+
*/
|
|
341
|
+
export function encodeMultipart(form: SimpleFormData, random: (count: number) => Uint8Array, boundary = multipartBoundary(random)): { parts: BodyPart[]; contentType: string } {
|
|
342
|
+
const parts: BodyPart[] = [];
|
|
172
343
|
for (const [name, value] of form.entries()) {
|
|
173
|
-
|
|
174
|
-
if (typeof value
|
|
175
|
-
parts.push(
|
|
176
|
-
parts.push(
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
parts
|
|
180
|
-
|
|
344
|
+
parts.push({ bytes: multipartHead(boundary, name, value) });
|
|
345
|
+
if (typeof value === 'string') parts.push({ bytes: utf8Encode(value) });
|
|
346
|
+
else parts.push(...value.parts());
|
|
347
|
+
parts.push({ bytes: utf8Encode('\r\n') });
|
|
348
|
+
}
|
|
349
|
+
parts.push({ bytes: utf8Encode(`--${boundary}--\r\n`) });
|
|
350
|
+
return { parts, contentType: `multipart/form-data; boundary=${boundary}` };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** `encodeMultipart` with the body whole: a form's files on disk are read first. */
|
|
354
|
+
export async function encodeMultipartAsync(
|
|
355
|
+
form: SimpleFormData,
|
|
356
|
+
random: (count: number) => Uint8Array,
|
|
357
|
+
boundary = multipartBoundary(random),
|
|
358
|
+
): Promise<{ bytes: Uint8Array; contentType: string }> {
|
|
359
|
+
const { parts, contentType } = encodeMultipart(form, random, boundary);
|
|
360
|
+
return { bytes: await partsBytes(parts), contentType };
|
|
181
361
|
}
|
|
182
362
|
|
|
183
|
-
/** `URL.createObjectURL`: a `data:` URL holding the blob. */
|
|
363
|
+
/** `URL.createObjectURL`: a `data:` URL holding the blob; a picked file still on disk is its own `file:` URL. */
|
|
184
364
|
export function objectURL(blob: SimpleBlob): string {
|
|
365
|
+
if (blob instanceof LazyFile && blob.isWhole) return blob.url;
|
|
185
366
|
return `data:${blob.type || 'application/octet-stream'};base64,${base64Encode(blob.bytes())}`;
|
|
186
367
|
}
|