Skip to content

Commit 491b05c

Browse files
committed
fix: buffer types for TextDecoder
1 parent 42be4b0 commit 491b05c

File tree

4 files changed

+474
-238
lines changed

4 files changed

+474
-238
lines changed

packages/cloudflare_worker_host/src/index.ts

Lines changed: 127 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
1-
import { WASI } from '@cloudflare/workers-wasi';
1+
import { WASI } from "@cloudflare/workers-wasi";
22

3-
import { App, HandleMap, UnexpectedError } from './common/index.js';
3+
import { App, HandleMap, UnexpectedError } from "./common/index.js";
44
// @ts-ignore
5-
import coreModule from '../assets/core-async.wasm';
6-
import type { FileSystem, Network, SecurityValuesMap, TextCoder, Timers, WasiContext } from './common/index.js';
7-
import { ErrorCode, HostError, Persistence, WasiErrno, WasiError } from './common/index.js';
8-
9-
const pkg = require('../package.json');
10-
11-
export { PerformError, UnexpectedError } from './common/error.js';
5+
import coreModule from "../assets/core-async.wasm";
6+
import type {
7+
FileSystem,
8+
Network,
9+
SecurityValuesMap,
10+
TextCoder,
11+
Timers,
12+
WasiContext,
13+
} from "./common/index.js";
14+
import {
15+
ErrorCode,
16+
HostError,
17+
Persistence,
18+
WasiErrno,
19+
WasiError,
20+
} from "./common/index.js";
21+
22+
const pkg = require("../package.json");
23+
24+
export { PerformError, UnexpectedError } from "./common/error.js";
1225

1326
class CfwTextCoder implements TextCoder {
1427
private encoder: TextEncoder = new TextEncoder();
1528
private decoder: TextDecoder = new TextDecoder();
1629

17-
decodeUtf8(buffer: ArrayBufferLike): string {
30+
decodeUtf8(buffer: ArrayBuffer): string {
1831
return this.decoder.decode(buffer);
1932
}
2033

21-
encodeUtf8(string: string): ArrayBuffer {
34+
encodeUtf8(string: string): Uint8Array<ArrayBufferLike> {
2235
return this.encoder.encode(string);
2336
}
2437
}
2538
class CfwFileSystem implements FileSystem {
2639
private readonly preopens: Record<string, Uint8Array>;
27-
private readonly files: HandleMap<{ data: Uint8Array, cursor: number }>;
40+
private readonly files: HandleMap<{ data: Uint8Array; cursor: number }>;
2841

2942
constructor(preopens: Record<string, Uint8Array>) {
3043
this.preopens = preopens;
@@ -35,7 +48,17 @@ class CfwFileSystem implements FileSystem {
3548
return this.preopens[path] !== undefined;
3649
}
3750

38-
async open(path: string, options: { createNew?: boolean, create?: boolean, truncate?: boolean, append?: boolean, write?: boolean, read?: boolean }): Promise<number> {
51+
async open(
52+
path: string,
53+
options: {
54+
createNew?: boolean;
55+
create?: boolean;
56+
truncate?: boolean;
57+
append?: boolean;
58+
write?: boolean;
59+
read?: boolean;
60+
}
61+
): Promise<number> {
3962
if (options.read !== true) {
4063
throw new WasiError(WasiErrno.EROFS);
4164
}
@@ -53,7 +76,10 @@ class CfwFileSystem implements FileSystem {
5376
throw new WasiError(WasiErrno.EBADF);
5477
}
5578

56-
const readCount = Math.min(out.byteLength, file.data.byteLength - file.cursor);
79+
const readCount = Math.min(
80+
out.byteLength,
81+
file.data.byteLength - file.cursor
82+
);
5783
const data = file.data.subarray(file.cursor, file.cursor + readCount);
5884
for (let i = 0; i < readCount; i += 1) {
5985
out[i] = data[i];
@@ -92,7 +118,7 @@ class CfwNetwork implements Network {
92118
try {
93119
response = await fetch(input, init);
94120
} catch (err: unknown) {
95-
if (typeof err === 'object' && err !== null && 'message' in err) {
121+
if (typeof err === "object" && err !== null && "message" in err) {
96122
// found a `Error: Network connection lost` caused by `kj/async-io-unix.c++:186: disconnected` in the wild
97123
throw new HostError(ErrorCode.NetworkError, `${err.message}`);
98124
}
@@ -102,18 +128,21 @@ class CfwNetwork implements Network {
102128
if (response.status === 530) {
103129
// TODO: DNS error is 530 with a human-readable HTML body describing the error
104130
// this is not trivial to parse and map to our error codes
105-
throw new HostError(ErrorCode.NetworkError, await response.text().catch(_ => 'Unknown Cloudflare 530 error'));
131+
throw new HostError(
132+
ErrorCode.NetworkError,
133+
await response.text().catch((_) => "Unknown Cloudflare 530 error")
134+
);
106135
}
107136

108137
return response;
109138
}
110139
}
111140
class CfwTextStreamDecoder {
112141
private decoder: TextDecoder = new TextDecoder();
113-
private decoderBuffer: string = '';
142+
private decoderBuffer: string = "";
114143

115144
/** Decodes streaming data and splits it by newline. */
116-
decodeUtf8Lines(buffer: ArrayBufferLike): string[] {
145+
decodeUtf8Lines(buffer: ArrayBuffer): string[] {
117146
this.decoderBuffer += this.decoder.decode(buffer, { stream: true });
118147

119148
return this.getLines();
@@ -126,7 +155,7 @@ class CfwTextStreamDecoder {
126155
}
127156

128157
private getLines() {
129-
const lines = this.decoderBuffer.split('\n');
158+
const lines = this.decoderBuffer.split("\n");
130159
if (lines.length > 1) {
131160
this.decoderBuffer = lines[lines.length - 1];
132161
return lines.slice(0, -1);
@@ -156,8 +185,8 @@ class CfwWasiCompat implements WasiContext {
156185
this.wasi.start({
157186
exports: {
158187
...instance.exports,
159-
_start() { }
160-
}
188+
_start() {},
189+
},
161190
});
162191

163192
this.memory = instance.exports.memory as WebAssembly.Memory;
@@ -169,18 +198,18 @@ class CfwWasiCompat implements WasiContext {
169198
iovs_ptr: number,
170199
iovs_len: number
171200
): Array<Uint8Array> {
172-
let result = Array<Uint8Array>(iovs_len)
201+
let result = Array<Uint8Array>(iovs_len);
173202

174203
for (let i = 0; i < iovs_len; i++) {
175-
const bufferPtr = view.getUint32(iovs_ptr, true)
176-
iovs_ptr += 4
204+
const bufferPtr = view.getUint32(iovs_ptr, true);
205+
iovs_ptr += 4;
177206

178-
const bufferLen = view.getUint32(iovs_ptr, true)
179-
iovs_ptr += 4
207+
const bufferLen = view.getUint32(iovs_ptr, true);
208+
iovs_ptr += 4;
180209

181-
result[i] = new Uint8Array(view.buffer, bufferPtr, bufferLen)
210+
result[i] = new Uint8Array(view.buffer, bufferPtr, bufferLen);
182211
}
183-
return result
212+
return result;
184213
}
185214

186215
private fd_write(
@@ -208,9 +237,13 @@ class CfwWasiCompat implements WasiContext {
208237
view.setUint32(retptr0, writeCount, true);
209238

210239
if (fd === 1) {
211-
this.stdoutDecoder.decodeUtf8Lines(buffer).forEach(line => console.log(line));
240+
this.stdoutDecoder
241+
.decodeUtf8Lines(buffer.buffer)
242+
.forEach((line) => console.log(line));
212243
} else {
213-
this.stderrDecoder.decodeUtf8Lines(buffer).forEach(line => console.error(line));
244+
this.stderrDecoder
245+
.decodeUtf8Lines(buffer.buffer)
246+
.forEach((line) => console.error(line));
214247
}
215248

216249
return 0; // SUCCESS
@@ -231,7 +264,7 @@ class CfwPersistence implements Persistence {
231264
if (superfaceApiUrl !== undefined) {
232265
this.insightsUrl = `${superfaceApiUrl}/insights/sdk_event`;
233266
} else {
234-
this.insightsUrl = 'https://superface.ai/insights/sdk_event';
267+
this.insightsUrl = "https://superface.ai/insights/sdk_event";
235268
}
236269
}
237270

@@ -240,23 +273,20 @@ class CfwPersistence implements Persistence {
240273
// 2. Logpush https://developers.cloudflare.com/workers/platform/logpush
241274
async persistMetrics(events: string[]): Promise<void> {
242275
const headers: Record<string, string> = {
243-
'content-type': 'application/json'
276+
"content-type": "application/json",
244277
};
245278
if (this.token !== undefined) {
246-
headers['authorization'] = `SUPERFACE-SDK-TOKEN ${this.token}`;
279+
headers["authorization"] = `SUPERFACE-SDK-TOKEN ${this.token}`;
247280
}
248281
if (this.userAgent !== undefined) {
249-
headers['user-agent'] = this.userAgent;
282+
headers["user-agent"] = this.userAgent;
250283
}
251284

252-
await fetch(
253-
`${this.insightsUrl}/batch`,
254-
{
255-
method: 'POST',
256-
body: '[' + events.join(',') + ']',
257-
headers
258-
}
259-
);
285+
await fetch(`${this.insightsUrl}/batch`, {
286+
method: "POST",
287+
body: "[" + events.join(",") + "]",
288+
headers,
289+
});
260290
}
261291

262292
async persistDeveloperDump(events: string[]): Promise<void> {
@@ -287,13 +317,20 @@ class InternalClient {
287317

288318
constructor(readonly options: ClientOptions = {}) {
289319
this.fileSystem = new CfwFileSystem(options.preopens ?? {});
290-
this.app = new App({
291-
fileSystem: this.fileSystem,
292-
textCoder: new CfwTextCoder(),
293-
timers: new CfwTimers(),
294-
network: new CfwNetwork(),
295-
persistence: new CfwPersistence(options.token, options.superfaceApiUrl, this.userAgent)
296-
}, { metricsTimeout: 0 });
320+
this.app = new App(
321+
{
322+
fileSystem: this.fileSystem,
323+
textCoder: new CfwTextCoder(),
324+
timers: new CfwTimers(),
325+
network: new CfwNetwork(),
326+
persistence: new CfwPersistence(
327+
options.token,
328+
options.superfaceApiUrl,
329+
this.userAgent
330+
),
331+
},
332+
{ metricsTimeout: 0 }
333+
);
297334
}
298335

299336
public async init() {
@@ -302,14 +339,14 @@ class InternalClient {
302339
return;
303340
}
304341

305-
console.log('INIT useragent', this.userAgent);
342+
console.log("INIT useragent", this.userAgent);
306343

307344
await this.app.loadCoreModule(coreModule);
308345
const wasi = new WASI({
309346
env: {
310347
ONESDK_DEFAULT_USERAGENT: this.userAgent,
311-
...this.options.env
312-
}
348+
...this.options.env,
349+
},
313350
});
314351
await this.app.init(new CfwWasiCompat(wasi));
315352

@@ -331,15 +368,15 @@ class InternalClient {
331368
): Promise<any> {
332369
await this.init();
333370

334-
const resolvedProfile = profile.replace(/\//g, '.'); // TODO: be smarter about this
335-
const assetsPath = this.options.assetsPath ?? 'superface'; // TODO: path join? - not sure if we are going to stick with this VFS
371+
const resolvedProfile = profile.replace(/\//g, "."); // TODO: be smarter about this
372+
const assetsPath = this.options.assetsPath ?? "superface"; // TODO: path join? - not sure if we are going to stick with this VFS
336373

337374
let profilePath = `${assetsPath}/${resolvedProfile}.profile.ts`;
338375
// migration from Comlink to TypeScript profiles
339376
const profilePathComlink = `${assetsPath}/${resolvedProfile}.profile`;
340377
if (
341-
!(await this.fileSystem.exists(profilePath))
342-
&& (await this.fileSystem.exists(profilePathComlink))
378+
!(await this.fileSystem.exists(profilePath)) &&
379+
(await this.fileSystem.exists(profilePathComlink))
343380
) {
344381
profilePath = profilePathComlink;
345382
}
@@ -356,7 +393,10 @@ class InternalClient {
356393
security
357394
);
358395
} catch (err: unknown) {
359-
if (err instanceof UnexpectedError && (err.name === 'WebAssemblyRuntimeError')) {
396+
if (
397+
err instanceof UnexpectedError &&
398+
err.name === "WebAssemblyRuntimeError"
399+
) {
360400
await this.destroy();
361401
}
362402

@@ -393,20 +433,26 @@ export class OneClient {
393433
}
394434

395435
/** Send metrics to Superface.
396-
*
436+
*
397437
* If `token` was passed in `ClientOptions` then the metrics will be associated with that account and project.
398-
*/
438+
*/
399439
public async sendMetricsToSuperface(): Promise<void> {
400440
return this.internal.sendMetrics();
401441
}
402442
}
403443

404444
export class Profile {
405-
private constructor(private readonly internal: InternalClient, public readonly name: string, public readonly url: string) {
406-
}
445+
private constructor(
446+
private readonly internal: InternalClient,
447+
public readonly name: string,
448+
public readonly url: string
449+
) {}
407450

408-
public static async loadLocal(internal: InternalClient, name: string): Promise<Profile> {
409-
return new Profile(internal, name, ''); // TODO: why do we need the url here?
451+
public static async loadLocal(
452+
internal: InternalClient,
453+
name: string
454+
): Promise<Profile> {
455+
return new Profile(internal, name, ""); // TODO: why do we need the url here?
410456
}
411457

412458
public getUseCase(usecaseName: string): UseCase {
@@ -415,10 +461,23 @@ export class Profile {
415461
}
416462

417463
export class UseCase {
418-
constructor(private readonly internal: InternalClient, private readonly profile: Profile, public readonly name: string) {
419-
}
420-
421-
public async perform<TInput = unknown, TResult = unknown>(input: TInput | undefined, options: ClientPerformOptions): Promise<TResult> {
422-
return await this.internal.perform(this.profile.name, options.provider, this.name, input, options?.parameters, options?.security) as TResult;
464+
constructor(
465+
private readonly internal: InternalClient,
466+
private readonly profile: Profile,
467+
public readonly name: string
468+
) {}
469+
470+
public async perform<TInput = unknown, TResult = unknown>(
471+
input: TInput | undefined,
472+
options: ClientPerformOptions
473+
): Promise<TResult> {
474+
return (await this.internal.perform(
475+
this.profile.name,
476+
options.provider,
477+
this.name,
478+
input,
479+
options?.parameters,
480+
options?.security
481+
)) as TResult;
423482
}
424483
}

0 commit comments

Comments
 (0)