@ricsam/isolate-encoding 0.1.1 → 0.1.3
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 +50 -0
- package/dist/cjs/index.cjs +190 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/{src/index.ts → dist/mjs/index.mjs} +10 -26
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/isolate.d.ts +34 -0
- package/package.json +41 -12
- package/CHANGELOG.md +0 -9
- package/src/setup.test.ts +0 -134
- package/tsconfig.json +0 -8
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @ricsam/isolate-encoding
|
|
2
|
+
|
|
3
|
+
Base64 encoding and decoding via `atob` and `btoa`.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { setupEncoding } from "@ricsam/isolate-encoding";
|
|
7
|
+
|
|
8
|
+
const handle = await setupEncoding(context);
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Injected Globals:**
|
|
12
|
+
- `atob(encodedData)` - Decode a Base64-encoded string
|
|
13
|
+
- `btoa(stringToEncode)` - Encode a string to Base64
|
|
14
|
+
|
|
15
|
+
**Usage in Isolate:**
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// Encode string to Base64
|
|
19
|
+
const encoded = btoa("Hello, World!");
|
|
20
|
+
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
|
|
21
|
+
|
|
22
|
+
// Decode Base64 to string
|
|
23
|
+
const decoded = atob("SGVsbG8sIFdvcmxkIQ==");
|
|
24
|
+
console.log(decoded); // "Hello, World!"
|
|
25
|
+
|
|
26
|
+
// Common use case: encoding JSON for transport
|
|
27
|
+
const data = { user: "john", token: "abc123" };
|
|
28
|
+
const base64Data = btoa(JSON.stringify(data));
|
|
29
|
+
|
|
30
|
+
// Decode it back
|
|
31
|
+
const originalData = JSON.parse(atob(base64Data));
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Error Handling:**
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// btoa throws for characters outside Latin1 range (0-255)
|
|
38
|
+
try {
|
|
39
|
+
btoa("Hello 世界"); // Throws DOMException
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.error("Cannot encode non-Latin1 characters");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// atob throws for invalid Base64
|
|
45
|
+
try {
|
|
46
|
+
atob("not valid base64!!!");
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error("Invalid Base64 string");
|
|
49
|
+
}
|
|
50
|
+
```
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// @bun @bun-cjs
|
|
2
|
+
(function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// packages/encoding/src/index.ts
|
|
31
|
+
var exports_src = {};
|
|
32
|
+
__export(exports_src, {
|
|
33
|
+
setupEncoding: () => setupEncoding
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(exports_src);
|
|
36
|
+
var encodingCode = `
|
|
37
|
+
(function() {
|
|
38
|
+
// Define DOMException if not available
|
|
39
|
+
if (typeof DOMException === 'undefined') {
|
|
40
|
+
globalThis.DOMException = class DOMException extends Error {
|
|
41
|
+
constructor(message, name) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = name || 'DOMException';
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
49
|
+
|
|
50
|
+
// Build reverse lookup table
|
|
51
|
+
const base64Lookup = new Map();
|
|
52
|
+
for (let i = 0; i < base64Chars.length; i++) {
|
|
53
|
+
base64Lookup.set(base64Chars[i], i);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
globalThis.btoa = function btoa(str) {
|
|
57
|
+
if (str === undefined) {
|
|
58
|
+
throw new TypeError("1 argument required, but only 0 present.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
str = String(str);
|
|
62
|
+
|
|
63
|
+
// Check for characters outside Latin-1 range
|
|
64
|
+
for (let i = 0; i < str.length; i++) {
|
|
65
|
+
if (str.charCodeAt(i) > 255) {
|
|
66
|
+
throw new DOMException(
|
|
67
|
+
"The string to be encoded contains characters outside of the Latin1 range.",
|
|
68
|
+
"InvalidCharacterError"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (str.length === 0) {
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let result = '';
|
|
78
|
+
let i = 0;
|
|
79
|
+
|
|
80
|
+
while (i < str.length) {
|
|
81
|
+
const a = str.charCodeAt(i++);
|
|
82
|
+
const bExists = i < str.length;
|
|
83
|
+
const b = bExists ? str.charCodeAt(i++) : 0;
|
|
84
|
+
const cExists = i < str.length;
|
|
85
|
+
const c = cExists ? str.charCodeAt(i++) : 0;
|
|
86
|
+
|
|
87
|
+
const triplet = (a << 16) | (b << 8) | c;
|
|
88
|
+
|
|
89
|
+
result += base64Chars[(triplet >> 18) & 0x3F];
|
|
90
|
+
result += base64Chars[(triplet >> 12) & 0x3F];
|
|
91
|
+
result += bExists ? base64Chars[(triplet >> 6) & 0x3F] : '=';
|
|
92
|
+
result += cExists ? base64Chars[triplet & 0x3F] : '=';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
globalThis.atob = function atob(str) {
|
|
99
|
+
if (str === undefined) {
|
|
100
|
+
throw new TypeError("1 argument required, but only 0 present.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
str = String(str);
|
|
104
|
+
|
|
105
|
+
// Remove whitespace
|
|
106
|
+
str = str.replace(/[\\t\\n\\f\\r ]/g, '');
|
|
107
|
+
|
|
108
|
+
// Validate characters and length
|
|
109
|
+
if (str.length === 0) {
|
|
110
|
+
return '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for invalid characters (before padding normalization)
|
|
114
|
+
for (let i = 0; i < str.length; i++) {
|
|
115
|
+
const c = str[i];
|
|
116
|
+
if (c !== '=' && !base64Lookup.has(c)) {
|
|
117
|
+
throw new DOMException(
|
|
118
|
+
"The string to be decoded is not correctly encoded.",
|
|
119
|
+
"InvalidCharacterError"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Validate padding position (must be at end)
|
|
125
|
+
const paddingIndex = str.indexOf('=');
|
|
126
|
+
if (paddingIndex !== -1) {
|
|
127
|
+
for (let i = paddingIndex; i < str.length; i++) {
|
|
128
|
+
if (str[i] !== '=') {
|
|
129
|
+
throw new DOMException(
|
|
130
|
+
"The string to be decoded is not correctly encoded.",
|
|
131
|
+
"InvalidCharacterError"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const paddingLength = str.length - paddingIndex;
|
|
136
|
+
if (paddingLength > 2) {
|
|
137
|
+
throw new DOMException(
|
|
138
|
+
"The string to be decoded is not correctly encoded.",
|
|
139
|
+
"InvalidCharacterError"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Length without padding must be valid (can't have remainder of 1)
|
|
145
|
+
const strWithoutPadding = str.replace(/=/g, '');
|
|
146
|
+
if (strWithoutPadding.length % 4 === 1) {
|
|
147
|
+
throw new DOMException(
|
|
148
|
+
"The string to be decoded is not correctly encoded.",
|
|
149
|
+
"InvalidCharacterError"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Pad to multiple of 4 if needed (for inputs without explicit padding)
|
|
154
|
+
while (str.length % 4 !== 0) {
|
|
155
|
+
str += '=';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let result = '';
|
|
159
|
+
let i = 0;
|
|
160
|
+
|
|
161
|
+
while (i < str.length) {
|
|
162
|
+
const a = base64Lookup.get(str[i++]) ?? 0;
|
|
163
|
+
const b = base64Lookup.get(str[i++]) ?? 0;
|
|
164
|
+
const c = base64Lookup.get(str[i++]) ?? 0;
|
|
165
|
+
const d = base64Lookup.get(str[i++]) ?? 0;
|
|
166
|
+
|
|
167
|
+
const triplet = (a << 18) | (b << 12) | (c << 6) | d;
|
|
168
|
+
|
|
169
|
+
result += String.fromCharCode((triplet >> 16) & 0xFF);
|
|
170
|
+
if (str[i - 2] !== '=') {
|
|
171
|
+
result += String.fromCharCode((triplet >> 8) & 0xFF);
|
|
172
|
+
}
|
|
173
|
+
if (str[i - 1] !== '=') {
|
|
174
|
+
result += String.fromCharCode(triplet & 0xFF);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
};
|
|
180
|
+
})();
|
|
181
|
+
`;
|
|
182
|
+
async function setupEncoding(context) {
|
|
183
|
+
context.evalSync(encodingCode);
|
|
184
|
+
return {
|
|
185
|
+
dispose() {}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
//# debugId=46FB9F757DF172F564756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type ivm from \"isolated-vm\";\n\nexport interface EncodingHandle {\n dispose(): void;\n}\n\nconst encodingCode = `\n(function() {\n // Define DOMException if not available\n if (typeof DOMException === 'undefined') {\n globalThis.DOMException = class DOMException extends Error {\n constructor(message, name) {\n super(message);\n this.name = name || 'DOMException';\n }\n };\n }\n\n const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n // Build reverse lookup table\n const base64Lookup = new Map();\n for (let i = 0; i < base64Chars.length; i++) {\n base64Lookup.set(base64Chars[i], i);\n }\n\n globalThis.btoa = function btoa(str) {\n if (str === undefined) {\n throw new TypeError(\"1 argument required, but only 0 present.\");\n }\n\n str = String(str);\n\n // Check for characters outside Latin-1 range\n for (let i = 0; i < str.length; i++) {\n if (str.charCodeAt(i) > 255) {\n throw new DOMException(\n \"The string to be encoded contains characters outside of the Latin1 range.\",\n \"InvalidCharacterError\"\n );\n }\n }\n\n if (str.length === 0) {\n return '';\n }\n\n let result = '';\n let i = 0;\n\n while (i < str.length) {\n const a = str.charCodeAt(i++);\n const bExists = i < str.length;\n const b = bExists ? str.charCodeAt(i++) : 0;\n const cExists = i < str.length;\n const c = cExists ? str.charCodeAt(i++) : 0;\n\n const triplet = (a << 16) | (b << 8) | c;\n\n result += base64Chars[(triplet >> 18) & 0x3F];\n result += base64Chars[(triplet >> 12) & 0x3F];\n result += bExists ? base64Chars[(triplet >> 6) & 0x3F] : '=';\n result += cExists ? base64Chars[triplet & 0x3F] : '=';\n }\n\n return result;\n };\n\n globalThis.atob = function atob(str) {\n if (str === undefined) {\n throw new TypeError(\"1 argument required, but only 0 present.\");\n }\n\n str = String(str);\n\n // Remove whitespace\n str = str.replace(/[\\\\t\\\\n\\\\f\\\\r ]/g, '');\n\n // Validate characters and length\n if (str.length === 0) {\n return '';\n }\n\n // Check for invalid characters (before padding normalization)\n for (let i = 0; i < str.length; i++) {\n const c = str[i];\n if (c !== '=' && !base64Lookup.has(c)) {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n }\n\n // Validate padding position (must be at end)\n const paddingIndex = str.indexOf('=');\n if (paddingIndex !== -1) {\n for (let i = paddingIndex; i < str.length; i++) {\n if (str[i] !== '=') {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n }\n const paddingLength = str.length - paddingIndex;\n if (paddingLength > 2) {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n }\n\n // Length without padding must be valid (can't have remainder of 1)\n const strWithoutPadding = str.replace(/=/g, '');\n if (strWithoutPadding.length % 4 === 1) {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n\n // Pad to multiple of 4 if needed (for inputs without explicit padding)\n while (str.length % 4 !== 0) {\n str += '=';\n }\n\n let result = '';\n let i = 0;\n\n while (i < str.length) {\n const a = base64Lookup.get(str[i++]) ?? 0;\n const b = base64Lookup.get(str[i++]) ?? 0;\n const c = base64Lookup.get(str[i++]) ?? 0;\n const d = base64Lookup.get(str[i++]) ?? 0;\n\n const triplet = (a << 18) | (b << 12) | (c << 6) | d;\n\n result += String.fromCharCode((triplet >> 16) & 0xFF);\n if (str[i - 2] !== '=') {\n result += String.fromCharCode((triplet >> 8) & 0xFF);\n }\n if (str[i - 1] !== '=') {\n result += String.fromCharCode(triplet & 0xFF);\n }\n }\n\n return result;\n };\n})();\n`;\n\n/**\n * Setup encoding APIs in an isolated-vm context\n *\n * Injects atob and btoa for Base64 encoding/decoding\n *\n * @example\n * const handle = await setupEncoding(context);\n * await context.eval(`\n * const encoded = btoa(\"hello\");\n * const decoded = atob(encoded);\n * `);\n */\nexport async function setupEncoding(\n context: ivm.Context\n): Promise<EncodingHandle> {\n context.evalSync(encodingCode);\n return {\n dispose() {\n // No resources to cleanup for pure JS injection\n },\n };\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+JrB,eAAsB,aAAa,CACjC,SACyB;AAAA,EACzB,QAAQ,SAAS,YAAY;AAAA,EAC7B,OAAO;AAAA,IACL,OAAO,GAAG;AAAA,EAGZ;AAAA;",
|
|
8
|
+
"debugId": "46FB9F757DF172F564756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
dispose(): void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const encodingCode = `
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/encoding/src/index.ts
|
|
3
|
+
var encodingCode = `
|
|
8
4
|
(function() {
|
|
9
5
|
// Define DOMException if not available
|
|
10
6
|
if (typeof DOMException === 'undefined') {
|
|
@@ -150,26 +146,14 @@ const encodingCode = `
|
|
|
150
146
|
};
|
|
151
147
|
})();
|
|
152
148
|
`;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Setup encoding APIs in an isolated-vm context
|
|
156
|
-
*
|
|
157
|
-
* Injects atob and btoa for Base64 encoding/decoding
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* const handle = await setupEncoding(context);
|
|
161
|
-
* await context.eval(`
|
|
162
|
-
* const encoded = btoa("hello");
|
|
163
|
-
* const decoded = atob(encoded);
|
|
164
|
-
* `);
|
|
165
|
-
*/
|
|
166
|
-
export async function setupEncoding(
|
|
167
|
-
context: ivm.Context
|
|
168
|
-
): Promise<EncodingHandle> {
|
|
149
|
+
async function setupEncoding(context) {
|
|
169
150
|
context.evalSync(encodingCode);
|
|
170
151
|
return {
|
|
171
|
-
dispose() {
|
|
172
|
-
// No resources to cleanup for pure JS injection
|
|
173
|
-
},
|
|
152
|
+
dispose() {}
|
|
174
153
|
};
|
|
175
154
|
}
|
|
155
|
+
export {
|
|
156
|
+
setupEncoding
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
//# debugId=3CBBC64AA7F4E2EC64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type ivm from \"isolated-vm\";\n\nexport interface EncodingHandle {\n dispose(): void;\n}\n\nconst encodingCode = `\n(function() {\n // Define DOMException if not available\n if (typeof DOMException === 'undefined') {\n globalThis.DOMException = class DOMException extends Error {\n constructor(message, name) {\n super(message);\n this.name = name || 'DOMException';\n }\n };\n }\n\n const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n // Build reverse lookup table\n const base64Lookup = new Map();\n for (let i = 0; i < base64Chars.length; i++) {\n base64Lookup.set(base64Chars[i], i);\n }\n\n globalThis.btoa = function btoa(str) {\n if (str === undefined) {\n throw new TypeError(\"1 argument required, but only 0 present.\");\n }\n\n str = String(str);\n\n // Check for characters outside Latin-1 range\n for (let i = 0; i < str.length; i++) {\n if (str.charCodeAt(i) > 255) {\n throw new DOMException(\n \"The string to be encoded contains characters outside of the Latin1 range.\",\n \"InvalidCharacterError\"\n );\n }\n }\n\n if (str.length === 0) {\n return '';\n }\n\n let result = '';\n let i = 0;\n\n while (i < str.length) {\n const a = str.charCodeAt(i++);\n const bExists = i < str.length;\n const b = bExists ? str.charCodeAt(i++) : 0;\n const cExists = i < str.length;\n const c = cExists ? str.charCodeAt(i++) : 0;\n\n const triplet = (a << 16) | (b << 8) | c;\n\n result += base64Chars[(triplet >> 18) & 0x3F];\n result += base64Chars[(triplet >> 12) & 0x3F];\n result += bExists ? base64Chars[(triplet >> 6) & 0x3F] : '=';\n result += cExists ? base64Chars[triplet & 0x3F] : '=';\n }\n\n return result;\n };\n\n globalThis.atob = function atob(str) {\n if (str === undefined) {\n throw new TypeError(\"1 argument required, but only 0 present.\");\n }\n\n str = String(str);\n\n // Remove whitespace\n str = str.replace(/[\\\\t\\\\n\\\\f\\\\r ]/g, '');\n\n // Validate characters and length\n if (str.length === 0) {\n return '';\n }\n\n // Check for invalid characters (before padding normalization)\n for (let i = 0; i < str.length; i++) {\n const c = str[i];\n if (c !== '=' && !base64Lookup.has(c)) {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n }\n\n // Validate padding position (must be at end)\n const paddingIndex = str.indexOf('=');\n if (paddingIndex !== -1) {\n for (let i = paddingIndex; i < str.length; i++) {\n if (str[i] !== '=') {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n }\n const paddingLength = str.length - paddingIndex;\n if (paddingLength > 2) {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n }\n\n // Length without padding must be valid (can't have remainder of 1)\n const strWithoutPadding = str.replace(/=/g, '');\n if (strWithoutPadding.length % 4 === 1) {\n throw new DOMException(\n \"The string to be decoded is not correctly encoded.\",\n \"InvalidCharacterError\"\n );\n }\n\n // Pad to multiple of 4 if needed (for inputs without explicit padding)\n while (str.length % 4 !== 0) {\n str += '=';\n }\n\n let result = '';\n let i = 0;\n\n while (i < str.length) {\n const a = base64Lookup.get(str[i++]) ?? 0;\n const b = base64Lookup.get(str[i++]) ?? 0;\n const c = base64Lookup.get(str[i++]) ?? 0;\n const d = base64Lookup.get(str[i++]) ?? 0;\n\n const triplet = (a << 18) | (b << 12) | (c << 6) | d;\n\n result += String.fromCharCode((triplet >> 16) & 0xFF);\n if (str[i - 2] !== '=') {\n result += String.fromCharCode((triplet >> 8) & 0xFF);\n }\n if (str[i - 1] !== '=') {\n result += String.fromCharCode(triplet & 0xFF);\n }\n }\n\n return result;\n };\n})();\n`;\n\n/**\n * Setup encoding APIs in an isolated-vm context\n *\n * Injects atob and btoa for Base64 encoding/decoding\n *\n * @example\n * const handle = await setupEncoding(context);\n * await context.eval(`\n * const encoded = btoa(\"hello\");\n * const decoded = atob(encoded);\n * `);\n */\nexport async function setupEncoding(\n context: ivm.Context\n): Promise<EncodingHandle> {\n context.evalSync(encodingCode);\n return {\n dispose() {\n // No resources to cleanup for pure JS injection\n },\n };\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;AAMA,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+JrB,eAAsB,aAAa,CACjC,SACyB;AAAA,EACzB,QAAQ,SAAS,YAAY;AAAA,EAC7B,OAAO;AAAA,IACL,OAAO,GAAG;AAAA,EAGZ;AAAA;",
|
|
8
|
+
"debugId": "3CBBC64AA7F4E2EC64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type ivm from "isolated-vm";
|
|
2
|
+
export interface EncodingHandle {
|
|
3
|
+
dispose(): void;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Setup encoding APIs in an isolated-vm context
|
|
7
|
+
*
|
|
8
|
+
* Injects atob and btoa for Base64 encoding/decoding
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const handle = await setupEncoding(context);
|
|
12
|
+
* await context.eval(`
|
|
13
|
+
* const encoded = btoa("hello");
|
|
14
|
+
* const decoded = atob(encoded);
|
|
15
|
+
* `);
|
|
16
|
+
*/
|
|
17
|
+
export declare function setupEncoding(context: ivm.Context): Promise<EncodingHandle>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Type Definitions for @ricsam/isolate-encoding
|
|
3
|
+
*
|
|
4
|
+
* These types define the globals injected by setupEncoding() into an isolated-vm context.
|
|
5
|
+
* Use these types to typecheck user code that will run inside the V8 isolate.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {};
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
/**
|
|
12
|
+
* Decodes a Base64-encoded string.
|
|
13
|
+
*
|
|
14
|
+
* @param encodedData - The Base64 string to decode
|
|
15
|
+
* @returns The decoded string
|
|
16
|
+
* @throws DOMException if the input is not valid Base64
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* atob("SGVsbG8="); // "Hello"
|
|
20
|
+
*/
|
|
21
|
+
function atob(encodedData: string): string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Encodes a string to Base64.
|
|
25
|
+
*
|
|
26
|
+
* @param stringToEncode - The string to encode (must contain only Latin1 characters)
|
|
27
|
+
* @returns The Base64 encoded string
|
|
28
|
+
* @throws DOMException if the string contains characters outside Latin1 range (0-255)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* btoa("Hello"); // "SGVsbG8="
|
|
32
|
+
*/
|
|
33
|
+
function btoa(stringToEncode: string): string;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/isolate-encoding",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"types": "./src/index.ts",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"main": "./dist/cjs/index.cjs",
|
|
5
|
+
"types": "./dist/types/index.d.ts",
|
|
7
6
|
"exports": {
|
|
8
7
|
".": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"require": "./dist/cjs/index.cjs",
|
|
10
|
+
"import": "./dist/mjs/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./isolate": {
|
|
13
|
+
"types": "./dist/types/isolate.d.ts"
|
|
11
14
|
}
|
|
12
15
|
},
|
|
13
16
|
"scripts": {
|
|
@@ -19,11 +22,37 @@
|
|
|
19
22
|
"@ricsam/isolate-core": "*",
|
|
20
23
|
"isolated-vm": "^6"
|
|
21
24
|
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@types/node": "^24",
|
|
24
|
-
"typescript": "^5"
|
|
25
|
-
},
|
|
26
25
|
"peerDependencies": {
|
|
27
26
|
"isolated-vm": "^6"
|
|
28
|
-
}
|
|
29
|
-
|
|
27
|
+
},
|
|
28
|
+
"author": "Richard Samuelsson",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/ricsam/isolate.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/ricsam/isolate/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/ricsam/isolate#readme",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"isolated-vm",
|
|
40
|
+
"sandbox",
|
|
41
|
+
"javascript",
|
|
42
|
+
"runtime",
|
|
43
|
+
"fetch",
|
|
44
|
+
"filesystem",
|
|
45
|
+
"streams",
|
|
46
|
+
"v8",
|
|
47
|
+
"isolate"
|
|
48
|
+
],
|
|
49
|
+
"description": "Base64 encoding APIs (atob, btoa) for isolated-vm V8 sandbox",
|
|
50
|
+
"module": "./dist/mjs/index.mjs",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"README.md"
|
|
57
|
+
]
|
|
58
|
+
}
|
package/CHANGELOG.md
DELETED
package/src/setup.test.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach, afterEach } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import ivm from "isolated-vm";
|
|
4
|
-
import { setupEncoding } from "./index.ts";
|
|
5
|
-
|
|
6
|
-
describe("@ricsam/isolate-encoding", () => {
|
|
7
|
-
let isolate: ivm.Isolate;
|
|
8
|
-
let context: ivm.Context;
|
|
9
|
-
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
isolate = new ivm.Isolate();
|
|
12
|
-
context = await isolate.createContext();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
context.release();
|
|
17
|
-
isolate.dispose();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("btoa", () => {
|
|
21
|
-
test("encodes string to base64", async () => {
|
|
22
|
-
await setupEncoding(context);
|
|
23
|
-
const result = await context.eval(`btoa("hello")`);
|
|
24
|
-
assert.strictEqual(result, "aGVsbG8=");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("handles empty string", async () => {
|
|
28
|
-
await setupEncoding(context);
|
|
29
|
-
const result = await context.eval(`btoa("")`);
|
|
30
|
-
assert.strictEqual(result, "");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("handles Latin-1 characters", async () => {
|
|
34
|
-
await setupEncoding(context);
|
|
35
|
-
// Test with Latin-1 extended characters (char codes 128-255)
|
|
36
|
-
const result = await context.eval(`btoa("café")`);
|
|
37
|
-
// café uses é which is Latin-1 (char code 233)
|
|
38
|
-
assert.strictEqual(result, "Y2Fm6Q==");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("throws on characters outside Latin-1 range", async () => {
|
|
42
|
-
await setupEncoding(context);
|
|
43
|
-
await assert.rejects(
|
|
44
|
-
async () => {
|
|
45
|
-
await context.eval(`btoa("hello 世界")`);
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: "InvalidCharacterError",
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("converts non-string arguments to string", async () => {
|
|
54
|
-
await setupEncoding(context);
|
|
55
|
-
const result = await context.eval(`btoa(123)`);
|
|
56
|
-
assert.strictEqual(result, "MTIz");
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("atob", () => {
|
|
61
|
-
test("decodes base64 to string", async () => {
|
|
62
|
-
await setupEncoding(context);
|
|
63
|
-
const result = await context.eval(`atob("aGVsbG8=")`);
|
|
64
|
-
assert.strictEqual(result, "hello");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("handles empty string", async () => {
|
|
68
|
-
await setupEncoding(context);
|
|
69
|
-
const result = await context.eval(`atob("")`);
|
|
70
|
-
assert.strictEqual(result, "");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("throws on invalid base64", async () => {
|
|
74
|
-
await setupEncoding(context);
|
|
75
|
-
await assert.rejects(
|
|
76
|
-
async () => {
|
|
77
|
-
await context.eval(`atob("not valid base64!@#")`);
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: "InvalidCharacterError",
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("handles input without padding", async () => {
|
|
86
|
-
await setupEncoding(context);
|
|
87
|
-
// "aGVsbG8" is "hello" without the = padding
|
|
88
|
-
const result = await context.eval(`atob("aGVsbG8")`);
|
|
89
|
-
assert.strictEqual(result, "hello");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("ignores whitespace in input", async () => {
|
|
93
|
-
await setupEncoding(context);
|
|
94
|
-
const result = await context.eval(`atob("aGVs bG8=")`);
|
|
95
|
-
assert.strictEqual(result, "hello");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("roundtrip", () => {
|
|
100
|
-
test("btoa and atob are inverse operations", async () => {
|
|
101
|
-
await setupEncoding(context);
|
|
102
|
-
const testStrings = [
|
|
103
|
-
"hello",
|
|
104
|
-
"Hello World!",
|
|
105
|
-
"test123",
|
|
106
|
-
"a",
|
|
107
|
-
"ab",
|
|
108
|
-
"abc",
|
|
109
|
-
"",
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
for (const str of testStrings) {
|
|
113
|
-
const result = await context.eval(
|
|
114
|
-
`atob(btoa(${JSON.stringify(str)}))`
|
|
115
|
-
);
|
|
116
|
-
assert.strictEqual(result, str, `Roundtrip failed for: ${str}`);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("handles binary data roundtrip", async () => {
|
|
121
|
-
await setupEncoding(context);
|
|
122
|
-
// Create a string with all Latin-1 bytes
|
|
123
|
-
const result = await context.eval(`
|
|
124
|
-
const bytes = [];
|
|
125
|
-
for (let i = 0; i < 256; i++) {
|
|
126
|
-
bytes.push(String.fromCharCode(i));
|
|
127
|
-
}
|
|
128
|
-
const str = bytes.join('');
|
|
129
|
-
atob(btoa(str)) === str;
|
|
130
|
-
`);
|
|
131
|
-
assert.strictEqual(result, true);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|