@adobe/aio-lib-sandbox 0.1.0-alpha.10
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/.eslintignore +2 -0
- package/.eslintrc.json +21 -0
- package/.github/CONTRIBUTING.md +44 -0
- package/.github/workflows/daily.yml +11 -0
- package/.github/workflows/node.js.yml +15 -0
- package/.github/workflows/on-push-publish-to-npm.yml +38 -0
- package/CODE_OF_CONDUCT.md +79 -0
- package/COPYRIGHT +5 -0
- package/LICENSE +201 -0
- package/README.md +244 -0
- package/RELEASING.md +36 -0
- package/jest.config.js +19 -0
- package/package.json +40 -0
- package/src/Sandbox.js +599 -0
- package/src/constants.js +22 -0
- package/src/errors.js +47 -0
- package/src/index.js +44 -0
- package/src/utils.js +170 -0
- package/src/ws.js +609 -0
- package/test/Sandbox.test.js +1464 -0
package/src/constants.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
governing permissions and limitations under the License.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const SANDBOX_SIZES = Object.freeze({
|
|
13
|
+
SMALL: { cpu: '500m', memory: '512Mi', gpu: 0 },
|
|
14
|
+
MEDIUM: { cpu: '2000m', memory: '4Gi', gpu: 0 },
|
|
15
|
+
LARGE: { cpu: '4000m', memory: '16Gi', gpu: 0 },
|
|
16
|
+
XLARGE: { cpu: '8000m', memory: '32Gi', gpu: 1 }
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const PROTOCOL_VERSION = '1'
|
|
20
|
+
const API_PREFIX = `/api/v${PROTOCOL_VERSION}`
|
|
21
|
+
|
|
22
|
+
module.exports = { SANDBOX_SIZES, PROTOCOL_VERSION, API_PREFIX }
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
governing permissions and limitations under the License.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
class SandboxSDKError extends Error {
|
|
13
|
+
constructor (message) {
|
|
14
|
+
super(message)
|
|
15
|
+
this.name = this.constructor.name
|
|
16
|
+
if (Error.captureStackTrace) {
|
|
17
|
+
Error.captureStackTrace(this, this.constructor)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class SandboxInitializationError extends SandboxSDKError {}
|
|
23
|
+
class SandboxClientError extends SandboxSDKError {}
|
|
24
|
+
class SandboxNotFoundError extends SandboxSDKError {}
|
|
25
|
+
class SandboxUnauthorizedError extends SandboxSDKError {}
|
|
26
|
+
class SandboxTimeoutError extends SandboxSDKError {}
|
|
27
|
+
class SandboxWebSocketError extends SandboxSDKError {}
|
|
28
|
+
class SandboxCommandNotFoundError extends SandboxSDKError {}
|
|
29
|
+
class SandboxPortNotProvisionedError extends SandboxSDKError {}
|
|
30
|
+
class SandboxInvalidPortError extends SandboxClientError {}
|
|
31
|
+
class ProtocolVersionMismatchError extends SandboxClientError {}
|
|
32
|
+
class SandboxMalformedFrameError extends SandboxClientError {}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
SandboxSDKError,
|
|
36
|
+
SandboxInitializationError,
|
|
37
|
+
SandboxClientError,
|
|
38
|
+
SandboxNotFoundError,
|
|
39
|
+
SandboxUnauthorizedError,
|
|
40
|
+
SandboxTimeoutError,
|
|
41
|
+
SandboxWebSocketError,
|
|
42
|
+
SandboxCommandNotFoundError,
|
|
43
|
+
SandboxPortNotProvisionedError,
|
|
44
|
+
SandboxInvalidPortError,
|
|
45
|
+
ProtocolVersionMismatchError,
|
|
46
|
+
SandboxMalformedFrameError
|
|
47
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
governing permissions and limitations under the License.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const Sandbox = require('./Sandbox')
|
|
13
|
+
const { PROTOCOL_VERSION } = require('./constants')
|
|
14
|
+
const {
|
|
15
|
+
SandboxSDKError,
|
|
16
|
+
SandboxInitializationError,
|
|
17
|
+
SandboxClientError,
|
|
18
|
+
SandboxNotFoundError,
|
|
19
|
+
SandboxUnauthorizedError,
|
|
20
|
+
SandboxTimeoutError,
|
|
21
|
+
SandboxWebSocketError,
|
|
22
|
+
SandboxCommandNotFoundError,
|
|
23
|
+
SandboxPortNotProvisionedError,
|
|
24
|
+
SandboxInvalidPortError,
|
|
25
|
+
ProtocolVersionMismatchError,
|
|
26
|
+
SandboxMalformedFrameError
|
|
27
|
+
} = require('./errors')
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
Sandbox,
|
|
31
|
+
SANDBOX_PROTOCOL_VERSION: PROTOCOL_VERSION,
|
|
32
|
+
SandboxSDKError,
|
|
33
|
+
SandboxInitializationError,
|
|
34
|
+
SandboxClientError,
|
|
35
|
+
SandboxNotFoundError,
|
|
36
|
+
SandboxUnauthorizedError,
|
|
37
|
+
SandboxTimeoutError,
|
|
38
|
+
SandboxWebSocketError,
|
|
39
|
+
SandboxCommandNotFoundError,
|
|
40
|
+
SandboxPortNotProvisionedError,
|
|
41
|
+
SandboxInvalidPortError,
|
|
42
|
+
ProtocolVersionMismatchError,
|
|
43
|
+
SandboxMalformedFrameError
|
|
44
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
governing permissions and limitations under the License.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
SandboxClientError,
|
|
14
|
+
SandboxInitializationError,
|
|
15
|
+
SandboxNotFoundError,
|
|
16
|
+
SandboxUnauthorizedError,
|
|
17
|
+
SandboxTimeoutError
|
|
18
|
+
} = require('./errors')
|
|
19
|
+
const { SANDBOX_SIZES, API_PREFIX } = require('./constants')
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds a Basic authorization header from a Runtime API key.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} apiKey Runtime API key
|
|
25
|
+
* @returns {string} Basic authorization header value
|
|
26
|
+
*/
|
|
27
|
+
function buildAuthorizationHeader (apiKey) {
|
|
28
|
+
return `Basic ${Buffer.from(apiKey).toString('base64')}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Maps sandbox management API status codes to SDK error classes.
|
|
33
|
+
*
|
|
34
|
+
* @param {number} status HTTP response status
|
|
35
|
+
* @param {string} message error message
|
|
36
|
+
* @returns {SandboxClientError} SDK error matching the response status
|
|
37
|
+
*/
|
|
38
|
+
function createSandboxHttpError (status, message) {
|
|
39
|
+
if (status === 401 || status === 403) {
|
|
40
|
+
return new SandboxUnauthorizedError(message)
|
|
41
|
+
}
|
|
42
|
+
if (status === 404) {
|
|
43
|
+
return new SandboxNotFoundError(message)
|
|
44
|
+
}
|
|
45
|
+
if (status === 504) {
|
|
46
|
+
return new SandboxTimeoutError(message)
|
|
47
|
+
}
|
|
48
|
+
return new SandboxClientError(message)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Ensures the Runtime API host has a URL scheme.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} host Runtime API host
|
|
55
|
+
* @returns {string} API host with a URL scheme
|
|
56
|
+
*/
|
|
57
|
+
function normalizeApiHost (host) {
|
|
58
|
+
if (!host.match(/^https?:\/\//)) {
|
|
59
|
+
return `https://${host}`
|
|
60
|
+
}
|
|
61
|
+
return host
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Builds the sandbox execution WebSocket endpoint from Runtime API details.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} apiHost Runtime API host
|
|
68
|
+
* @param {string} namespace Runtime namespace
|
|
69
|
+
* @param {string} sandboxId sandbox id
|
|
70
|
+
* @returns {string} sandbox WebSocket endpoint
|
|
71
|
+
*/
|
|
72
|
+
function buildWebSocketEndpoint (apiHost, namespace, sandboxId) {
|
|
73
|
+
const url = new URL(apiHost)
|
|
74
|
+
url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:'
|
|
75
|
+
url.pathname = `${API_PREFIX}/namespaces/${namespace}/sandboxes/${sandboxId}/exec`
|
|
76
|
+
url.search = ''
|
|
77
|
+
return url.toString()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Reads Runtime credentials from environment variables, merged with any
|
|
82
|
+
* explicit overrides. Throws `SandboxInitializationError` for missing values.
|
|
83
|
+
*
|
|
84
|
+
* @param {object} overrides explicit credential overrides
|
|
85
|
+
* @returns {{ apiHost: string, namespace: string, apiKey: string }} resolved Runtime credentials
|
|
86
|
+
*/
|
|
87
|
+
function resolveCredentials (overrides = {}) {
|
|
88
|
+
const apiHost = overrides.apiHost || process.env.__OW_API_HOST
|
|
89
|
+
const namespace = overrides.namespace || process.env.__OW_NAMESPACE
|
|
90
|
+
const apiKey = overrides.auth || process.env.__OW_API_KEY
|
|
91
|
+
|
|
92
|
+
const missing = []
|
|
93
|
+
if (!apiHost) missing.push('apiHost')
|
|
94
|
+
if (!namespace) missing.push('namespace')
|
|
95
|
+
if (!apiKey) missing.push('auth')
|
|
96
|
+
|
|
97
|
+
if (missing.length > 0) {
|
|
98
|
+
throw new SandboxInitializationError(
|
|
99
|
+
`Missing required credentials: ${missing.join(', ')}. ` +
|
|
100
|
+
'Pass them explicitly or set __OW_API_HOST, __OW_NAMESPACE, __OW_API_KEY in the environment.'
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { apiHost: normalizeApiHost(apiHost), namespace, apiKey }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {string|object|undefined} size size name or spec object
|
|
109
|
+
* @returns {string} normalised size name
|
|
110
|
+
*/
|
|
111
|
+
function normalizeSize (size) {
|
|
112
|
+
if (!size) return 'MEDIUM'
|
|
113
|
+
|
|
114
|
+
if (typeof size === 'string' && SANDBOX_SIZES[size]) return size
|
|
115
|
+
|
|
116
|
+
if (typeof size === 'object') {
|
|
117
|
+
const entry = Object.entries(SANDBOX_SIZES).find(
|
|
118
|
+
([, v]) => v.cpu === size.cpu && v.memory === size.memory && v.gpu === size.gpu
|
|
119
|
+
)
|
|
120
|
+
if (entry) return entry[0]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new SandboxClientError('Invalid sandbox size provided')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Thin fetch wrapper around the management REST API.
|
|
128
|
+
*
|
|
129
|
+
* Uses the Node.js global `fetch` (available since Node 18).
|
|
130
|
+
*
|
|
131
|
+
* @param {string} method HTTP method
|
|
132
|
+
* @param {string} url full request URL
|
|
133
|
+
* @param {string} apiKey API key for Basic auth
|
|
134
|
+
* @param {object|undefined} body request body (JSON-encoded when present)
|
|
135
|
+
* @returns {Promise<object>} parsed response JSON
|
|
136
|
+
*/
|
|
137
|
+
async function apiRequest (method, url, apiKey, body) {
|
|
138
|
+
const headers = { Authorization: buildAuthorizationHeader(apiKey) }
|
|
139
|
+
const init = { method, headers }
|
|
140
|
+
|
|
141
|
+
if (body !== undefined) {
|
|
142
|
+
headers['Content-Type'] = 'application/json'
|
|
143
|
+
init.body = JSON.stringify(body)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let response
|
|
147
|
+
try {
|
|
148
|
+
response = await fetch(url, init)
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new SandboxClientError(`Sandbox API request failed: ${error.message}`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const text = await response.text()
|
|
155
|
+
const detail = `${response.status}${text ? ` ${text}` : ''}`
|
|
156
|
+
throw createSandboxHttpError(response.status, detail)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return response.json()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
buildAuthorizationHeader,
|
|
164
|
+
createSandboxHttpError,
|
|
165
|
+
normalizeApiHost,
|
|
166
|
+
buildWebSocketEndpoint,
|
|
167
|
+
resolveCredentials,
|
|
168
|
+
normalizeSize,
|
|
169
|
+
apiRequest
|
|
170
|
+
}
|