@phpsandbox/sdk 0.1.9 → 0.1.11
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 +42 -50
- package/dist/beacon/index.d.ts +1 -7
- package/dist/beacon/index.js +14 -13
- package/dist/beacon/navigator.d.ts +8 -36
- package/dist/beacon/navigator.js +1 -62
- package/dist/browser/phpsandbox-sdk.esm.js +552 -692
- package/dist/browser/phpsandbox-sdk.esm.min.js +24 -24
- package/dist/browser/phpsandbox-sdk.iife.js +552 -692
- package/dist/browser/phpsandbox-sdk.iife.min.js +35 -35
- package/dist/composer.d.ts +10 -8
- package/dist/composer.js +18 -18
- package/dist/errors/index.d.ts +43 -8
- package/dist/errors/index.js +120 -14
- package/dist/feedback.js +1 -2
- package/dist/filesystem.d.ts +0 -17
- package/dist/filesystem.js +2 -21
- package/dist/git.d.ts +1 -5
- package/dist/git.js +6 -8
- package/dist/http.d.ts +6 -0
- package/dist/http.js +11 -0
- package/dist/index.d.ts +54 -144
- package/dist/index.js +170 -289
- package/dist/integrations.d.ts +0 -2
- package/dist/integrations.js +5 -11
- package/dist/log.d.ts +0 -2
- package/dist/lsp.js +1 -1
- package/dist/metrics.d.ts +3 -3
- package/dist/ports.d.ts +1 -1
- package/dist/publications.d.ts +4 -54
- package/dist/publications.js +25 -103
- package/dist/runtime/rest.js +35 -18
- package/dist/runtime.d.ts +68 -0
- package/dist/runtime.js +46 -0
- package/dist/sandbox-integrations.d.ts +1 -2
- package/dist/sandbox-integrations.js +11 -14
- package/dist/schemas/publications.d.ts +0 -6
- package/dist/schemas/publications.js +0 -1
- package/dist/servers.d.ts +4 -2
- package/dist/servers.js +5 -8
- package/dist/shell.d.ts +2 -14
- package/dist/shell.js +1 -21
- package/dist/socket/index.d.ts +6 -7
- package/dist/socket/index.js +39 -25
- package/dist/types.d.ts +0 -12
- package/dist/types.js +1 -15
- package/docs/api-summary.md +28 -28
- package/docs/cdn-usage.md +43 -55
- package/docs/getting-started.md +43 -26
- package/examples/README.md +5 -35
- package/examples/basic.ts +9 -18
- package/examples/get.ts +2 -2
- package/examples/git-merge-smoke.ts +2 -3
- package/examples/laravel-load-test.ts +31 -36
- package/examples/notebook-readonly-smoke.ts +12 -26
- package/examples/publication-provider-label.ts +1 -1
- package/examples/publish-cloudflare-container.ts +2 -2
- package/examples/publish-laravel-cloud.ts +2 -2
- package/examples/publish-ssh-agent.ts +2 -2
- package/package.json +4 -3
- package/dist/container.d.ts +0 -65
- package/dist/container.js +0 -30
|
@@ -54,11 +54,6 @@ export declare const PublicationJurisdictionSchema: z.ZodEnum<{
|
|
|
54
54
|
eu: "eu";
|
|
55
55
|
fedramp: "fedramp";
|
|
56
56
|
}>;
|
|
57
|
-
export declare const PublicationDomainDnsRecordTypeSchema: z.ZodEnum<{
|
|
58
|
-
CNAME: "CNAME";
|
|
59
|
-
ALIAS: "ALIAS";
|
|
60
|
-
ANAME: "ANAME";
|
|
61
|
-
}>;
|
|
62
57
|
export declare const PublicationProtectionModeSchema: z.ZodEnum<{
|
|
63
58
|
none: "none";
|
|
64
59
|
password: "password";
|
|
@@ -415,7 +410,6 @@ export type PublicationReleaseStatus = z.infer<typeof PublicationReleaseStatusSc
|
|
|
415
410
|
export type PublicationSize = z.infer<typeof PublicationSizeSchema>;
|
|
416
411
|
export type PublicationRegion = z.infer<typeof PublicationRegionSchema>;
|
|
417
412
|
export type PublicationJurisdiction = z.infer<typeof PublicationJurisdictionSchema>;
|
|
418
|
-
export type PublicationDomainDnsRecordType = z.infer<typeof PublicationDomainDnsRecordTypeSchema>;
|
|
419
413
|
export type PublicationProtectionMode = z.infer<typeof PublicationProtectionModeSchema>;
|
|
420
414
|
export type PublicationLogStream = z.infer<typeof PublicationLogStreamSchema>;
|
|
421
415
|
export type PublicationEventData = z.infer<typeof PublicationEventDataSchema>;
|
|
@@ -47,7 +47,6 @@ export const PublicationRegionSchema = z.enum([
|
|
|
47
47
|
'AFR',
|
|
48
48
|
]);
|
|
49
49
|
export const PublicationJurisdictionSchema = z.enum(['eu', 'fedramp']);
|
|
50
|
-
export const PublicationDomainDnsRecordTypeSchema = z.enum(['CNAME', 'ALIAS', 'ANAME']);
|
|
51
50
|
export const PublicationProtectionModeSchema = z.enum(['none', 'password']);
|
|
52
51
|
export const PublicationLogStreamSchema = z.enum(['build', 'runtime']);
|
|
53
52
|
export const PublishStreamLogStreamSchema = z.enum(['build', 'publish', 'runtime']);
|
package/dist/servers.d.ts
CHANGED
|
@@ -21,13 +21,15 @@ export interface CreateServerInput {
|
|
|
21
21
|
host: string;
|
|
22
22
|
ssh?: ServerSshOptions;
|
|
23
23
|
}
|
|
24
|
+
export interface ServerListOptions {
|
|
25
|
+
page?: number;
|
|
26
|
+
}
|
|
24
27
|
export declare class ServerApi {
|
|
25
28
|
private readonly client;
|
|
26
29
|
constructor(client: Client);
|
|
27
|
-
list(): Promise<PaginatedApiResponse<ServerData>>;
|
|
30
|
+
list(options?: ServerListOptions): Promise<PaginatedApiResponse<ServerData>>;
|
|
28
31
|
create(input: CreateServerInput): Promise<ServerInstance>;
|
|
29
32
|
get(id: string): Promise<ServerInstance>;
|
|
30
|
-
delete(id: string): Promise<void>;
|
|
31
33
|
}
|
|
32
34
|
export declare class ServerInstance {
|
|
33
35
|
readonly data: ServerData;
|
package/dist/servers.js
CHANGED
|
@@ -2,9 +2,9 @@ export class ServerApi {
|
|
|
2
2
|
constructor(client) {
|
|
3
3
|
this.client = client;
|
|
4
4
|
}
|
|
5
|
-
async list() {
|
|
6
|
-
const
|
|
7
|
-
return
|
|
5
|
+
async list(options = {}) {
|
|
6
|
+
const query = options.page === undefined ? '' : `?${new URLSearchParams({ page: String(options.page) }).toString()}`;
|
|
7
|
+
return this.client.get(`/servers${query}`);
|
|
8
8
|
}
|
|
9
9
|
async create(input) {
|
|
10
10
|
const response = await this.client.post('/servers', input);
|
|
@@ -14,9 +14,6 @@ export class ServerApi {
|
|
|
14
14
|
const response = await this.client.get(`/servers/${id}`);
|
|
15
15
|
return new ServerInstance(response.data, this.client);
|
|
16
16
|
}
|
|
17
|
-
async delete(id) {
|
|
18
|
-
await this.client.delete(`/servers/${id}`);
|
|
19
|
-
}
|
|
20
17
|
}
|
|
21
18
|
export class ServerInstance {
|
|
22
19
|
constructor(data, client) {
|
|
@@ -26,8 +23,8 @@ export class ServerInstance {
|
|
|
26
23
|
refresh() {
|
|
27
24
|
return this.client.servers.get(this.data.id);
|
|
28
25
|
}
|
|
29
|
-
delete() {
|
|
30
|
-
|
|
26
|
+
async delete() {
|
|
27
|
+
await this.client.delete(`/servers/${this.data.id}`);
|
|
31
28
|
}
|
|
32
29
|
async waitReady(options) {
|
|
33
30
|
const timeout = options?.timeout ?? 300000; // 5 minutes default
|
package/dist/shell.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface ShellExecInput {
|
|
|
22
22
|
opts?: Omit<SpawnOptions, 'abortSignal'>;
|
|
23
23
|
}
|
|
24
24
|
export interface ShellActions {
|
|
25
|
-
'shell.exec': Action<ShellExecInput,
|
|
25
|
+
'shell.exec': Action<ShellExecInput, ProcessResult>;
|
|
26
26
|
'shell.input': Action<{
|
|
27
27
|
id: string;
|
|
28
28
|
input: string;
|
|
@@ -31,24 +31,12 @@ export interface ShellActions {
|
|
|
31
31
|
id: string;
|
|
32
32
|
}, boolean>;
|
|
33
33
|
}
|
|
34
|
-
export interface
|
|
34
|
+
export interface ProcessResult {
|
|
35
35
|
output: string;
|
|
36
36
|
stdout: string;
|
|
37
37
|
stderr: string;
|
|
38
38
|
exitCode: number;
|
|
39
39
|
}
|
|
40
|
-
export declare class CommandError extends Error {
|
|
41
|
-
readonly result: ProcessResult;
|
|
42
|
-
constructor(result: ProcessResult);
|
|
43
|
-
}
|
|
44
|
-
export declare class ProcessResult implements ProcessResultData {
|
|
45
|
-
readonly output: string;
|
|
46
|
-
readonly stdout: string;
|
|
47
|
-
readonly stderr: string;
|
|
48
|
-
readonly exitCode: number;
|
|
49
|
-
constructor(output: string, stdout: string, stderr: string, exitCode: number);
|
|
50
|
-
throw(): this;
|
|
51
|
-
}
|
|
52
40
|
export declare class ProcessReadable<T> {
|
|
53
41
|
readonly stream: ReadableStream<T>;
|
|
54
42
|
private controller;
|
package/dist/shell.js
CHANGED
|
@@ -7,26 +7,6 @@ function resolveRunInput(command, argsOrOpts = [], opts) {
|
|
|
7
7
|
opts: { tty: false, ...resolvedOpts },
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
export class CommandError extends Error {
|
|
11
|
-
constructor(result) {
|
|
12
|
-
super(result.output);
|
|
13
|
-
this.result = result;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export class ProcessResult {
|
|
17
|
-
constructor(output, stdout, stderr, exitCode) {
|
|
18
|
-
this.output = output;
|
|
19
|
-
this.stdout = stdout;
|
|
20
|
-
this.stderr = stderr;
|
|
21
|
-
this.exitCode = exitCode;
|
|
22
|
-
}
|
|
23
|
-
throw() {
|
|
24
|
-
if (this.exitCode !== 0) {
|
|
25
|
-
throw new CommandError(this);
|
|
26
|
-
}
|
|
27
|
-
return this;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
10
|
export class ProcessReadable {
|
|
31
11
|
constructor() {
|
|
32
12
|
this.controller = null;
|
|
@@ -111,7 +91,7 @@ export class ShellProcess {
|
|
|
111
91
|
const { abortSignal, ...payloadOpts } = opts;
|
|
112
92
|
this.completion = this.okra.invoke('shell.exec', { command, opts: { ...payloadOpts, id: this.id } }, { abortSignal }).then((result) => {
|
|
113
93
|
finish();
|
|
114
|
-
return
|
|
94
|
+
return result;
|
|
115
95
|
}).catch((error) => {
|
|
116
96
|
fail(error);
|
|
117
97
|
throw error;
|
package/dist/socket/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { RemoteError, TransportError } from '../errors/index.js';
|
|
1
2
|
import type { CloseEvent as ReconnectingWebSocketCloseEvent, ErrorEvent as ReconnectingWebSocketErrorEvent } from 'reconnecting-websocket';
|
|
2
3
|
import { EventDispatcher } from '../events/index.js';
|
|
3
|
-
import { NotebookUnavailableError } from '../errors/index.js';
|
|
4
4
|
type TransportUrlProvider = string | (() => string) | (() => Promise<string>);
|
|
5
5
|
interface WsOptions {
|
|
6
6
|
debug?: boolean;
|
|
7
|
-
startClosed?: boolean;
|
|
8
7
|
pingInterval?: number;
|
|
9
8
|
connectionTimeout?: number;
|
|
10
9
|
connectWaitTimeout?: number;
|
|
@@ -28,14 +27,14 @@ export declare enum SocketEvent {
|
|
|
28
27
|
Error = "error",
|
|
29
28
|
ClientId = "App.Actions.GetClientId"
|
|
30
29
|
}
|
|
31
|
-
export declare class ConnectionTimeoutError extends
|
|
30
|
+
export declare class ConnectionTimeoutError extends TransportError<'ConnectionTimeout'> {
|
|
32
31
|
constructor(message?: string);
|
|
33
32
|
}
|
|
34
|
-
export declare class ConnectionFailedError extends
|
|
33
|
+
export declare class ConnectionFailedError extends TransportError<'ConnectionFailed'> {
|
|
35
34
|
readonly originalError?: unknown;
|
|
36
35
|
constructor(message: string, originalError?: unknown);
|
|
37
36
|
}
|
|
38
|
-
export declare class InvalidMessageError extends
|
|
37
|
+
export declare class InvalidMessageError extends TransportError<'InvalidResponse'> {
|
|
39
38
|
readonly data?: unknown;
|
|
40
39
|
constructor(message: string, data?: unknown);
|
|
41
40
|
}
|
|
@@ -44,7 +43,7 @@ export declare class InvalidConfigurationError extends Error {
|
|
|
44
43
|
}
|
|
45
44
|
export interface TransportEvents {
|
|
46
45
|
'transport.boot_error': {
|
|
47
|
-
error:
|
|
46
|
+
error: RemoteError<'NotebookUnavailable'>;
|
|
48
47
|
timestamp: number;
|
|
49
48
|
};
|
|
50
49
|
'transport.error': {
|
|
@@ -94,7 +93,7 @@ export declare class Transport {
|
|
|
94
93
|
private handleRawMessage;
|
|
95
94
|
private handleMessage;
|
|
96
95
|
listen(event: string, listener: (data: unknown) => void, _context?: unknown): import("../types.js").Disposable;
|
|
97
|
-
onDidBootError(listener: (error:
|
|
96
|
+
onDidBootError(listener: (error: RemoteError<'NotebookUnavailable'>) => void): {
|
|
98
97
|
dispose: () => void;
|
|
99
98
|
};
|
|
100
99
|
removeListener(event: string, listener?: (data: unknown) => void): void;
|
package/dist/socket/index.js
CHANGED
|
@@ -6,14 +6,17 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
6
6
|
var _Transport_instances, _Transport_connect, _Transport_startPeriodicPing;
|
|
7
7
|
import { nanoid } from 'nanoid';
|
|
8
8
|
import { encode, decode } from '@msgpack/msgpack';
|
|
9
|
-
import {
|
|
9
|
+
import { PromiseTimeoutError, RemoteError, SendTimeoutError, TransportError, remoteError, } from '../errors/index.js';
|
|
10
10
|
import { retry } from '../utils/retry.js';
|
|
11
11
|
import * as ReconnectingWebSocketModule from 'reconnecting-websocket';
|
|
12
12
|
import { timeout } from '../utils/promise.js';
|
|
13
13
|
import { describeWebSocketEvent } from '../utils/websocket.js';
|
|
14
14
|
import WebSocket from 'isomorphic-ws';
|
|
15
15
|
import { NamedDisposable } from '../utils/disposable.js';
|
|
16
|
-
|
|
16
|
+
const SAFE_DEBUG_METADATA_KEYS = new Set([
|
|
17
|
+
'url', 'action', 'event', 'responseEvent', 'errorEvent', 'hasData',
|
|
18
|
+
'code', 'attempt', 'maxRetries', 'timeout', 'errorCount', 'byteLength',
|
|
19
|
+
]);
|
|
17
20
|
const ReconnectingWebSocket = (ReconnectingWebSocketModule.default
|
|
18
21
|
?? ReconnectingWebSocketModule);
|
|
19
22
|
export var SocketEvent;
|
|
@@ -24,24 +27,21 @@ export var SocketEvent;
|
|
|
24
27
|
SocketEvent["ClientId"] = "App.Actions.GetClientId";
|
|
25
28
|
})(SocketEvent || (SocketEvent = {}));
|
|
26
29
|
// Add specific error types for better error handling
|
|
27
|
-
export class ConnectionTimeoutError extends
|
|
30
|
+
export class ConnectionTimeoutError extends TransportError {
|
|
28
31
|
constructor(message = 'WebSocket connection timeout') {
|
|
29
|
-
super(message);
|
|
30
|
-
this.name = 'ConnectionTimeoutError';
|
|
32
|
+
super(message, 'ConnectionTimeout');
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
|
-
export class ConnectionFailedError extends
|
|
35
|
+
export class ConnectionFailedError extends TransportError {
|
|
34
36
|
constructor(message, originalError) {
|
|
35
|
-
super(message);
|
|
37
|
+
super(message, 'ConnectionFailed', originalError);
|
|
36
38
|
this.originalError = originalError;
|
|
37
|
-
this.name = 'ConnectionFailedError';
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
export class InvalidMessageError extends
|
|
41
|
+
export class InvalidMessageError extends TransportError {
|
|
41
42
|
constructor(message, data) {
|
|
42
|
-
super(message);
|
|
43
|
+
super(message, 'InvalidResponse', data);
|
|
43
44
|
this.data = data;
|
|
44
|
-
this.name = 'InvalidMessageError';
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
// Add configuration validation
|
|
@@ -72,8 +72,6 @@ export class Transport {
|
|
|
72
72
|
// Use configurable ping interval
|
|
73
73
|
this.PING_INTERVAL = options.pingInterval ?? 30000;
|
|
74
74
|
this.CONNECT_WAIT_TIMEOUT = options.connectWaitTimeout ?? 30000;
|
|
75
|
-
// Always start closed by default (lazy initialization)
|
|
76
|
-
const startClosed = options.startClosed !== false;
|
|
77
75
|
this.rws = new ReconnectingWebSocket(urlProvider, [], {
|
|
78
76
|
WebSocket: options.webSocket ?? globalThis.WebSocket ?? WebSocket,
|
|
79
77
|
connectionTimeout: options.connectionTimeout,
|
|
@@ -81,7 +79,7 @@ export class Transport {
|
|
|
81
79
|
minReconnectionDelay: 200,
|
|
82
80
|
maxEnqueuedMessages: 0,
|
|
83
81
|
maxRetries: options.maxRetries ?? 50,
|
|
84
|
-
startClosed,
|
|
82
|
+
startClosed: true,
|
|
85
83
|
});
|
|
86
84
|
this.log('debug', 'Transport initialized', {
|
|
87
85
|
url: this.url === null ? 'dynamic' : transportLogUrl(this.url),
|
|
@@ -112,8 +110,9 @@ export class Transport {
|
|
|
112
110
|
log(level, message, data) {
|
|
113
111
|
if (this.options.debug) {
|
|
114
112
|
const timestamp = new Date().toISOString();
|
|
115
|
-
const
|
|
116
|
-
|
|
113
|
+
const metadata = safeDebugMetadata(data);
|
|
114
|
+
const logData = metadata ? `\n${JSON.stringify(metadata, null, 2)}` : '';
|
|
115
|
+
console[level](`[Transport ${timestamp}] ${message}${logData}`);
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
/**
|
|
@@ -243,7 +242,12 @@ export class Transport {
|
|
|
243
242
|
}
|
|
244
243
|
if (event === SocketEvent.BootError) {
|
|
245
244
|
const errorData = data;
|
|
246
|
-
const error = new
|
|
245
|
+
const error = new RemoteError(errorData?.message || 'Notebook is no longer available', {
|
|
246
|
+
source: 'runtime',
|
|
247
|
+
status: 503,
|
|
248
|
+
code: 'NotebookUnavailable',
|
|
249
|
+
details: errorData || {},
|
|
250
|
+
});
|
|
247
251
|
this.log('error', 'Boot error received', { data });
|
|
248
252
|
this.terminate(error, 4000, error.message);
|
|
249
253
|
return;
|
|
@@ -392,12 +396,16 @@ export class Transport {
|
|
|
392
396
|
});
|
|
393
397
|
this.listenOnce(errorEvent, (e) => {
|
|
394
398
|
this.log('error', 'Message error received', { action, error: e });
|
|
395
|
-
|
|
396
|
-
reject(new SDKErrorEvent(error.code, error.message, error));
|
|
399
|
+
reject(remoteError('runtime', e));
|
|
397
400
|
});
|
|
398
401
|
closeHandler = (_ev) => {
|
|
399
402
|
if (_ev.code === 1008 && (_ev.reason || '').includes('rate limit')) {
|
|
400
|
-
reject(new
|
|
403
|
+
reject(new RemoteError(_ev.reason || 'Rate limit exceeded', {
|
|
404
|
+
source: 'runtime',
|
|
405
|
+
status: 429,
|
|
406
|
+
code: 'RateLimited',
|
|
407
|
+
cause: _ev,
|
|
408
|
+
}));
|
|
401
409
|
return;
|
|
402
410
|
}
|
|
403
411
|
const detail = describeWebSocketEvent(_ev);
|
|
@@ -411,7 +419,7 @@ export class Transport {
|
|
|
411
419
|
}
|
|
412
420
|
catch (error) {
|
|
413
421
|
this.log('error', 'Failed to send message', { action, error });
|
|
414
|
-
reject(error);
|
|
422
|
+
reject(new ConnectionFailedError(`Failed to send runtime action ${action}.`, error));
|
|
415
423
|
}
|
|
416
424
|
};
|
|
417
425
|
const send = async () => {
|
|
@@ -470,11 +478,9 @@ export class Transport {
|
|
|
470
478
|
}
|
|
471
479
|
catch (e) {
|
|
472
480
|
// Don't retry these errors
|
|
473
|
-
if (e instanceof
|
|
474
|
-
e instanceof RateLimitError ||
|
|
481
|
+
if (e instanceof RemoteError ||
|
|
475
482
|
e instanceof InvalidConfigurationError ||
|
|
476
483
|
e instanceof InvalidMessageError ||
|
|
477
|
-
e instanceof NotebookUnavailableError ||
|
|
478
484
|
e instanceof DOMException) {
|
|
479
485
|
this.log('debug', 'Non-retryable error, bailing', {
|
|
480
486
|
error: e.message,
|
|
@@ -550,7 +556,7 @@ export class Transport {
|
|
|
550
556
|
}
|
|
551
557
|
terminate(error, code = 4000, reason = error.message) {
|
|
552
558
|
this.terminalError = error;
|
|
553
|
-
if (error
|
|
559
|
+
if (RemoteError.is(error, 'NotebookUnavailable')) {
|
|
554
560
|
this.eventEmitter.emit('transport.boot_error', {
|
|
555
561
|
error,
|
|
556
562
|
timestamp: Date.now(),
|
|
@@ -702,6 +708,14 @@ _Transport_instances = new WeakSet(), _Transport_connect = function _Transport_c
|
|
|
702
708
|
};
|
|
703
709
|
});
|
|
704
710
|
};
|
|
711
|
+
function safeDebugMetadata(data) {
|
|
712
|
+
if (data === null || typeof data !== 'object') {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
// Message bodies and errors can contain tokens, source code, or process output.
|
|
716
|
+
const metadata = Object.entries(data).filter(([key, value]) => SAFE_DEBUG_METADATA_KEYS.has(key) && ['string', 'number', 'boolean'].includes(typeof value));
|
|
717
|
+
return metadata.length > 0 ? Object.fromEntries(metadata) : null;
|
|
718
|
+
}
|
|
705
719
|
function transportLogUrl(url) {
|
|
706
720
|
const safeUrl = new URL(url);
|
|
707
721
|
safeUrl.search = '';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
export type ErrorCode = 400 | 401 | 403 | 404 | 405 | 409 | 413 | 422 | 429 | 500 | 503 | 5000;
|
|
2
|
-
export declare class ErrorEvent extends Error {
|
|
3
|
-
readonly code: ErrorCode;
|
|
4
|
-
readonly message: string;
|
|
5
|
-
readonly raw: object;
|
|
6
|
-
constructor(code: ErrorCode, message: string, raw?: object);
|
|
7
|
-
}
|
|
8
|
-
export declare class RateLimitError extends ErrorEvent {
|
|
9
|
-
constructor(message: string, raw?: object);
|
|
10
|
-
}
|
|
11
|
-
export declare class PHPSandboxError extends ErrorEvent {
|
|
12
|
-
}
|
|
13
1
|
export interface Disposable {
|
|
14
2
|
dispose: () => void;
|
|
15
3
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,15 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
constructor(code, message, raw = {}) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.code = code;
|
|
5
|
-
this.message = message;
|
|
6
|
-
this.raw = raw;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
export class RateLimitError extends ErrorEvent {
|
|
10
|
-
constructor(message, raw = {}) {
|
|
11
|
-
super(429, message, raw);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export class PHPSandboxError extends ErrorEvent {
|
|
15
|
-
}
|
|
1
|
+
export {};
|
package/docs/api-summary.md
CHANGED
|
@@ -9,7 +9,6 @@ import { PHPSandbox } from '@phpsandbox/sdk';
|
|
|
9
9
|
|
|
10
10
|
const client = PHPSandbox.realtime(token, 'https://api.phpsandbox.io/v1', {
|
|
11
11
|
debug: false,
|
|
12
|
-
startClosed: true,
|
|
13
12
|
});
|
|
14
13
|
|
|
15
14
|
const restClient = PHPSandbox.rest(token, 'https://api.phpsandbox.io/v1', {
|
|
@@ -21,17 +20,14 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
|
|
|
21
20
|
|
|
22
21
|
### `NotebookApi`
|
|
23
22
|
|
|
24
|
-
- `create(template, input
|
|
23
|
+
- `create(template, input?)`
|
|
25
24
|
- `get(id)`
|
|
26
|
-
- `
|
|
27
|
-
- `destroy(id)`
|
|
28
|
-
- `openFromData(data)`
|
|
29
|
-
- `mail.status(id)`, `mail.enable(id)`, `mail.disable(id)`, `mail.list(id, options?)`, `mail.get(id, hash)`, `mail.delete(id, hash)`
|
|
25
|
+
- `open(data)`
|
|
30
26
|
|
|
31
27
|
## `NotebookInstance`
|
|
32
28
|
|
|
33
|
-
- Lifecycle: `ready()`, `connected()`, `
|
|
34
|
-
- Notebook actions: `fork(input?)`, `destroy()`, `stop()`, `restart()
|
|
29
|
+
- Lifecycle: `ready()`, `connected()`, `reconnect()`, `dispose()`
|
|
30
|
+
- Notebook actions: `fork(input?)`, `destroy()`, `stop()`, `restart()`
|
|
35
31
|
- Processes: `exec(command, args?, options?)`, or `run(command, args?, options?)` returning native `stdin`, `stdout`, `stderr`, ordered `output`, `wait()`, and `kill()`
|
|
36
32
|
- Events: `listen(event, handler)`, `onDidConnect(handler)`, `onDidDisconnect(handler)`, `onDidInitialize(handler)`
|
|
37
33
|
- Raw call: `invoke(action, data?, options?)`
|
|
@@ -54,18 +50,21 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
|
|
|
54
50
|
- `attach(id, options?)` returns a terminal handle
|
|
55
51
|
- Handles expose `input`, `output`, `wait()`, `resize()`, and `kill()`
|
|
56
52
|
|
|
57
|
-
###
|
|
53
|
+
### Notebook lifecycle
|
|
58
54
|
|
|
59
|
-
-
|
|
60
|
-
|
|
55
|
+
- `notebook.stop()`, `notebook.restart()`
|
|
56
|
+
|
|
57
|
+
### Runtime (`notebook.runtime`)
|
|
58
|
+
|
|
59
|
+
- Runtime configuration: `setPhpVersion(version)`
|
|
61
60
|
|
|
62
61
|
### Runtime observability
|
|
63
62
|
|
|
64
|
-
- `notebook.logs.follow({ signal? })` returns `ReadableStream<RuntimeLogEntry>`
|
|
65
|
-
- `notebook.ports.list()` returns the current preview ports
|
|
66
|
-
- `notebook.ports.watch({ signal? })` returns `ReadableStream<PortInfo[]>`
|
|
67
|
-
- `notebook.metrics.current()` returns the current CPU, memory, and disk sample
|
|
68
|
-
- `notebook.metrics.watch({ signal? })` returns `ReadableStream<
|
|
63
|
+
- `notebook.runtime.logs.follow({ signal? })` returns `ReadableStream<RuntimeLogEntry>`
|
|
64
|
+
- `notebook.runtime.ports.list()` returns the current preview ports
|
|
65
|
+
- `notebook.runtime.ports.watch({ signal? })` returns `ReadableStream<PortInfo[]>`
|
|
66
|
+
- `notebook.runtime.metrics.current()` returns the current CPU, memory, and disk sample
|
|
67
|
+
- `notebook.runtime.metrics.watch({ signal? })` returns `ReadableStream<RuntimeStats>`
|
|
69
68
|
|
|
70
69
|
### Processes
|
|
71
70
|
|
|
@@ -84,8 +83,8 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
|
|
|
84
83
|
|
|
85
84
|
### Composer (`notebook.composer`)
|
|
86
85
|
|
|
87
|
-
-
|
|
88
|
-
-
|
|
86
|
+
- `run(command, args?, options?)`
|
|
87
|
+
- `packages()`
|
|
89
88
|
- Command handles expose `output`, `stdout`, `stderr`, `wait()`, and `kill()`
|
|
90
89
|
|
|
91
90
|
### Git (`notebook.git`)
|
|
@@ -93,8 +92,8 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
|
|
|
93
92
|
- `checkpoint(author, message, branch = 'main')`
|
|
94
93
|
- `targets.create({ provider, repository, author, branch?, setDefault? })`
|
|
95
94
|
- `targets.list()`, `targets.get(target)`, and `target.sync({ direction?, author? })`
|
|
96
|
-
- `diff({ url?, token?, ref?, base?, target? })`
|
|
97
|
-
- `merge({ url?, token?, ref?, target?, expectedBaseRef?, author?, message? })`
|
|
95
|
+
- `diff({ notebookId?, url?, token?, ref?, base?, target?, patch? })`
|
|
96
|
+
- `merge({ notebookId?, url?, token?, ref?, target?, expectedBaseRef?, author?, message? })`
|
|
98
97
|
- `conflicts()`
|
|
99
98
|
- `abortMerge()`
|
|
100
99
|
- `continueMerge(author, message?)`
|
|
@@ -153,7 +152,8 @@ Feedback ingestion is always available through the authenticated API. Configurat
|
|
|
153
152
|
|
|
154
153
|
- `notebook.publish({ slug, provider, protection?, metadata? })`
|
|
155
154
|
- `notebook.publication()` returns the current publication or `null`
|
|
156
|
-
- `client.publications.get(id)
|
|
155
|
+
- `client.publications.get(id)` returns a publication reference.
|
|
156
|
+
- Publication references expose `refresh()`, `destroy()`, `events()`, `logs()`, `buildLogs()`, and protection operations.
|
|
157
157
|
- A publish run exposes `events()` as an `AsyncIterable`, `result()` with the final status and URL, and `publication()` when refreshed deployment state is needed.
|
|
158
158
|
- Built-in provider names are `cloudflare-containers`, `ssh-server`, and `laravel-cloud`. Provider maps support TypeScript declaration merging.
|
|
159
159
|
- Cloudflare Containers input uses `{ name: 'cloudflare-containers', accountId, size?, sleepAfter?, placement?, instances? }` and resolves the sandbox's attached Cloudflare integration when each operation runs.
|
|
@@ -162,19 +162,19 @@ Feedback ingestion is always available through the authenticated API. Configurat
|
|
|
162
162
|
## Account integrations
|
|
163
163
|
|
|
164
164
|
- `client.integrations.link({ provider, label?, authorization })`
|
|
165
|
-
- `client.integrations.list()
|
|
165
|
+
- `client.integrations.list()` and `get(integration)`
|
|
166
166
|
- Resource instances expose `update(input)` and `unlink()`.
|
|
167
167
|
- Built-in providers are `cloudflare`, `github`, and `laravel-cloud`; current public authorization uses opaque caller-managed tokens.
|
|
168
168
|
|
|
169
169
|
## Common Errors
|
|
170
170
|
|
|
171
|
-
- `
|
|
172
|
-
- `
|
|
173
|
-
- `
|
|
174
|
-
- `
|
|
171
|
+
- `RemoteError` when Core or the notebook runtime returns a structured failure
|
|
172
|
+
- `RemoteError.is(error, code)` for exact conditions such as `FileNotFound` and `RateLimited`
|
|
173
|
+
- `RemoteError.isValidation(error)` for typed validation details
|
|
174
|
+
- `TransportError` when no valid remote response is received
|
|
175
175
|
|
|
176
176
|
## Notes for Consumers
|
|
177
177
|
|
|
178
|
-
-
|
|
179
|
-
- `
|
|
178
|
+
- Runtime actions wait for readiness before their handlers run.
|
|
179
|
+
- Call `ready()` explicitly when you need provisioning metadata or initialization errors before another action.
|
|
180
180
|
- Always call `notebook.dispose()` when finished.
|
package/docs/cdn-usage.md
CHANGED
|
@@ -1,80 +1,68 @@
|
|
|
1
|
-
# CDN
|
|
1
|
+
# Browser/CDN usage
|
|
2
2
|
|
|
3
|
-
The SDK
|
|
3
|
+
The SDK includes ESM and script-tag browser bundles. Browser support is a packaging capability, not permission to expose an account API key.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Choose a trust boundary
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
For public applications, keep the SDK on your backend. Authenticate your application users and authorize the sandbox and operation before making SDK calls. Do not expose a generic proxy for arbitrary management or runtime operations.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
There is no public SDK API for minting short-lived account keys. Never embed a `psb_` API key in a page, frontend environment variable, local storage, or CDN example.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
A trusted workspace can receive a notebook-specific runtime URL from its backend. That URL contains a bearer ticket and grants access to the sandbox runtime. Only give it to a user you trust to access that sandbox; it is not a preview link. Use preview sessions or handoffs for application review.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
<script type="module">
|
|
15
|
-
import { PHPSandbox } from 'https://unpkg.com/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.esm.min.js';
|
|
16
|
-
|
|
17
|
-
const client = new PHPSandbox('your-short-lived-token');
|
|
18
|
-
const notebook = await client.notebook.get('your-notebook-id');
|
|
19
|
-
await notebook.ready();
|
|
13
|
+
## Trusted workspace example (ESM)
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
result.throw();
|
|
23
|
-
console.log(result.output);
|
|
24
|
-
</script>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### jsDelivr
|
|
15
|
+
The `/api/sandbox-session` endpoint below belongs to **your application** and must be implemented on your backend. It must authenticate the user, authorize access to the selected notebook, retrieve fresh notebook data using your server-side API key, and return `id`, `gitUrl`, and `runtimeUrl` with `Cache-Control: no-store`. Never return the account API key. Do not accept arbitrary notebook IDs without authorization.
|
|
28
16
|
|
|
29
17
|
```html
|
|
30
18
|
<script type="module">
|
|
31
|
-
import { PHPSandbox } from 'https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.
|
|
19
|
+
import { PHPSandbox } from 'https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.1.9/dist/browser/phpsandbox-sdk.esm.min.js';
|
|
20
|
+
|
|
21
|
+
async function getSession() {
|
|
22
|
+
const response = await fetch('/api/sandbox-session', {
|
|
23
|
+
credentials: 'same-origin',
|
|
24
|
+
cache: 'no-store',
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) throw new Error(`Sandbox session failed: ${response.status}`);
|
|
27
|
+
return response.json();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// No account token in this client. Management calls stay on your backend.
|
|
31
|
+
const client = PHPSandbox.realtime('', undefined, {
|
|
32
|
+
runtimeUrlProvider: async () => (await getSession()).runtimeUrl,
|
|
33
|
+
});
|
|
34
|
+
const notebook = client.notebook.open(await getSession());
|
|
32
35
|
|
|
33
|
-
const
|
|
36
|
+
const result = await notebook.exec('php', ['-v']);
|
|
37
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || result.output);
|
|
38
|
+
document.querySelector('#output').textContent = result.stdout;
|
|
39
|
+
notebook.dispose();
|
|
34
40
|
</script>
|
|
41
|
+
<pre id="output"></pre>
|
|
35
42
|
```
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
`open()` creates a runtime handle without requesting notebook data from Core. Do not call `client.notebook.create()`, `get()`, or other management methods with this tokenless client. Refresh the same authorized notebook's ticket through your backend when reconnecting. Never log session responses or runtime URLs.
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
## Script tag (IIFE)
|
|
40
47
|
|
|
41
48
|
```html
|
|
42
|
-
<script src="https://unpkg.com/@phpsandbox/sdk@0.
|
|
49
|
+
<script src="https://unpkg.com/@phpsandbox/sdk@0.1.9/dist/browser/phpsandbox-sdk.iife.min.js"></script>
|
|
43
50
|
<script>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await notebook.ready();
|
|
48
|
-
const result = await notebook.exec('php -v', { tty: false });
|
|
49
|
-
console.log(result.output);
|
|
50
|
-
notebook.dispose();
|
|
51
|
-
});
|
|
51
|
+
// The IIFE namespace contains the exported PHPSandbox class.
|
|
52
|
+
const client = PHPSandbox.PHPSandbox.realtime('');
|
|
53
|
+
// Use the same authorized backend session and open flow shown above.
|
|
52
54
|
</script>
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```html
|
|
58
|
-
<script src="https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.iife.min.js"></script>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Version Pinning
|
|
62
|
-
|
|
63
|
-
Always pin a version in production URLs:
|
|
64
|
-
|
|
65
|
-
```html
|
|
66
|
-
https://unpkg.com/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.esm.min.js
|
|
67
|
-
https://cdn.jsdelivr.net/npm/@phpsandbox/sdk@0.0.51/dist/browser/phpsandbox-sdk.iife.min.js
|
|
68
|
-
```
|
|
57
|
+
## Version pinning
|
|
69
58
|
|
|
70
|
-
|
|
59
|
+
These URLs pin `0.1.9`. Use the version you have tested when upgrading; avoid unversioned or `latest` URLs. Both CDNs serve the same package files:
|
|
71
60
|
|
|
72
|
-
- `phpsandbox-sdk.esm.js`
|
|
73
|
-
- `phpsandbox-sdk.esm.min.js`
|
|
74
|
-
- `phpsandbox-sdk.iife.js`
|
|
75
|
-
- `phpsandbox-sdk.iife.min.js`
|
|
61
|
+
- `dist/browser/phpsandbox-sdk.esm.js`
|
|
62
|
+
- `dist/browser/phpsandbox-sdk.esm.min.js`
|
|
63
|
+
- `dist/browser/phpsandbox-sdk.iife.js`
|
|
64
|
+
- `dist/browser/phpsandbox-sdk.iife.min.js`
|
|
76
65
|
|
|
77
|
-
## Browser
|
|
66
|
+
## Browser requirements
|
|
78
67
|
|
|
79
|
-
|
|
80
|
-
- `fetch`, `WebSocket`, and `ReadableStream` support
|
|
68
|
+
Use a modern browser with `fetch`, `WebSocket`, `ReadableStream`, `WritableStream`, `TextEncoder`, `TextDecoder`, and `AbortController`. ES2020 syntax support alone does not supply these web APIs. Your application's CSP must permit the CDN script and the authorized runtime connection.
|