@metamask/snaps-cli 3.0.4 → 4.0.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/CHANGELOG.md +12 -1
- package/README.md +1 -1
- package/dist/cjs/webpack/loaders/wasm.js +78 -24
- package/dist/cjs/webpack/loaders/wasm.js.map +1 -1
- package/dist/cjs/webpack/server.js +5 -1
- package/dist/cjs/webpack/server.js.map +1 -1
- package/dist/esm/webpack/loaders/wasm.js +75 -16
- package/dist/esm/webpack/loaders/wasm.js.map +1 -1
- package/dist/esm/webpack/server.js +5 -1
- package/dist/esm/webpack/server.js.map +1 -1
- package/dist/types/webpack/loaders/wasm.d.ts +33 -11
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [4.0.0]
|
|
10
|
+
### Changed
|
|
11
|
+
- **BREAKING:** Synchronously initialize WASM modules ([#2024](https://github.com/MetaMask/snaps/pull/2024))
|
|
12
|
+
- When the `experimental.wasm` flag in the Snaps CLI is enabled, the WASM module will now be synchronously initialized, rather than being inlined as a Uint8Array.
|
|
13
|
+
|
|
14
|
+
## [3.0.5]
|
|
15
|
+
### Fixed
|
|
16
|
+
- Include Snap icon in allowed server paths ([#2003](https://github.com/MetaMask/snaps/pull/2003))
|
|
17
|
+
|
|
9
18
|
## [3.0.4]
|
|
10
19
|
### Fixed
|
|
11
20
|
- Only serve Snap files from CLI ([#1979](https://github.com/MetaMask/snaps/pull/1979))
|
|
@@ -82,7 +91,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
82
91
|
- The version of the package no longer needs to match the version of all other
|
|
83
92
|
MetaMask Snaps packages.
|
|
84
93
|
|
|
85
|
-
[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@
|
|
94
|
+
[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@4.0.0...HEAD
|
|
95
|
+
[4.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@3.0.5...@metamask/snaps-cli@4.0.0
|
|
96
|
+
[3.0.5]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@3.0.4...@metamask/snaps-cli@3.0.5
|
|
86
97
|
[3.0.4]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@3.0.3...@metamask/snaps-cli@3.0.4
|
|
87
98
|
[3.0.3]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@3.0.2...@metamask/snaps-cli@3.0.3
|
|
88
99
|
[3.0.2]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-cli@3.0.1...@metamask/snaps-cli@3.0.2
|
package/README.md
CHANGED
|
@@ -1,36 +1,90 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
/* eslint-disable no-restricted-globals */ "use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: all[name]
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
getImports: function() {
|
|
13
|
+
return getImports;
|
|
14
|
+
},
|
|
15
|
+
getModuleImports: function() {
|
|
16
|
+
return getModuleImports;
|
|
17
|
+
},
|
|
18
|
+
getExports: function() {
|
|
19
|
+
return getExports;
|
|
20
|
+
},
|
|
21
|
+
default: function() {
|
|
22
|
+
return _default;
|
|
23
|
+
},
|
|
24
|
+
raw: function() {
|
|
25
|
+
return raw;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const _utils = require("@metamask/utils");
|
|
29
|
+
function getImports(importMap) {
|
|
30
|
+
return Object.entries(importMap).map(([moduleName, exportNames])=>`import { ${exportNames.join(', ')} } from ${JSON.stringify(moduleName)};`).join('\n');
|
|
31
|
+
}
|
|
32
|
+
function getModuleImports(importMap) {
|
|
33
|
+
return Object.entries(importMap).map(([moduleName, exportNames])=>`${JSON.stringify(moduleName)}: { ${exportNames.join(', ')} },`).join('\n');
|
|
34
|
+
}
|
|
35
|
+
function getExports(descriptors) {
|
|
36
|
+
return descriptors.map((descriptor)=>{
|
|
37
|
+
if (descriptor.name === 'default') {
|
|
38
|
+
return `export default exports[${JSON.stringify(descriptor.name)}];`;
|
|
39
|
+
}
|
|
40
|
+
return `export const ${descriptor.name} = exports[${JSON.stringify(descriptor.name)}];`;
|
|
41
|
+
}).join('\n');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A Webpack loader that synchronously loads the WASM module. This makes it
|
|
45
|
+
* possible to import the WASM module directly.
|
|
13
46
|
*
|
|
14
|
-
* @param source - The WASM module as
|
|
15
|
-
* @returns
|
|
47
|
+
* @param source - The WASM module as `Uint8Array`.
|
|
48
|
+
* @returns The WASM module as a JavaScript string.
|
|
16
49
|
* @example
|
|
17
50
|
* ```ts
|
|
18
|
-
* import wasm from './program.wasm';
|
|
51
|
+
* import * as wasm from './program.wasm';
|
|
19
52
|
*
|
|
20
|
-
* const { instance } = await WebAssembly.instantiate(wasm, {});
|
|
21
53
|
* // Do something with the WASM module...
|
|
22
54
|
* ```
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
*/ // Note: This function needs to be defined like this, so that Webpack can bind
|
|
56
|
+
// `this` to the loader context, and TypeScript can infer the type of `this`.
|
|
57
|
+
const loader = async function loader(source) {
|
|
58
|
+
(0, _utils.assert)(source instanceof Uint8Array, 'Expected source to be a Uint8Array.');
|
|
59
|
+
const bytes = new Uint8Array(source);
|
|
60
|
+
const wasmModule = await WebAssembly.compile(bytes);
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
62
|
+
const exports1 = WebAssembly.Module.exports(wasmModule);
|
|
63
|
+
const imports = WebAssembly.Module.imports(wasmModule).reduce((target, descriptor)=>{
|
|
64
|
+
var _target, _descriptor_module;
|
|
65
|
+
(_target = target)[_descriptor_module = descriptor.module] ?? (_target[_descriptor_module] = []);
|
|
66
|
+
target[descriptor.module].push(descriptor.name);
|
|
67
|
+
return target;
|
|
68
|
+
}, {});
|
|
69
|
+
// Add the WASM import as a dependency so that Webpack will watch it for
|
|
70
|
+
// changes.
|
|
71
|
+
for (const name of Object.keys(imports)){
|
|
72
|
+
this.addDependency(name);
|
|
27
73
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
}
|
|
74
|
+
return `
|
|
75
|
+
${getImports(imports)}
|
|
76
|
+
|
|
77
|
+
const bytes = new Uint8Array(${JSON.stringify(Array.from(source))});
|
|
78
|
+
const module = new WebAssembly.Module(bytes);
|
|
79
|
+
const instance = new WebAssembly.Instance(module, {
|
|
80
|
+
${getModuleImports(imports)}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const exports = instance.exports;
|
|
84
|
+
${getExports(exports1)}
|
|
85
|
+
`;
|
|
86
|
+
};
|
|
87
|
+
const _default = loader;
|
|
88
|
+
const raw = true;
|
|
35
89
|
|
|
36
90
|
//# sourceMappingURL=wasm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/webpack/loaders/wasm.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../../src/webpack/loaders/wasm.ts"],"sourcesContent":["/* eslint-disable no-restricted-globals */\n\nimport { assert } from '@metamask/utils';\nimport type { LoaderDefinitionFunction } from 'webpack';\n\n/**\n * Get the imports code for the WASM module. This code imports each of the\n * imports from the WASM module.\n *\n * @param importMap - The import map for the WASM module.\n * @returns The imports code for the WASM module.\n */\nexport function getImports(importMap: Record<string, string[]>) {\n return Object.entries(importMap)\n .map(\n ([moduleName, exportNames]) =>\n `import { ${exportNames.join(', ')} } from ${JSON.stringify(\n moduleName,\n )};`,\n )\n .join('\\n');\n}\n\n/**\n * Get the imports code to use in `WebAssembly.Instance`. This code adds each of\n * the imports to the `imports` object.\n *\n * @param importMap - The import map for the WASM module.\n * @returns The imports code for the WASM module.\n */\nexport function getModuleImports(importMap: Record<string, string[]>) {\n return Object.entries(importMap)\n .map(\n ([moduleName, exportNames]) =>\n `${JSON.stringify(moduleName)}: { ${exportNames.join(', ')} },`,\n )\n .join('\\n');\n}\n\n/**\n * Get the exports code for the WASM module. This code exports each of the\n * exports from the WASM module as a variable. This function assumes that the\n * exports are available in a variable named `exports`.\n *\n * @param descriptors - The export descriptors from the WASM module.\n * @returns The exports code for the WASM module.\n */\nexport function getExports(descriptors: WebAssembly.ModuleExportDescriptor[]) {\n return descriptors\n .map((descriptor) => {\n if (descriptor.name === 'default') {\n return `export default exports[${JSON.stringify(descriptor.name)}];`;\n }\n\n return `export const ${descriptor.name} = exports[${JSON.stringify(\n descriptor.name,\n )}];`;\n })\n .join('\\n');\n}\n\n/**\n * A Webpack loader that synchronously loads the WASM module. This makes it\n * possible to import the WASM module directly.\n *\n * @param source - The WASM module as `Uint8Array`.\n * @returns The WASM module as a JavaScript string.\n * @example\n * ```ts\n * import * as wasm from './program.wasm';\n *\n * // Do something with the WASM module...\n * ```\n */\n// Note: This function needs to be defined like this, so that Webpack can bind\n// `this` to the loader context, and TypeScript can infer the type of `this`.\nconst loader: LoaderDefinitionFunction = async function loader(\n source: unknown,\n) {\n assert(source instanceof Uint8Array, 'Expected source to be a Uint8Array.');\n\n const bytes = new Uint8Array(source);\n const wasmModule = await WebAssembly.compile(bytes);\n\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const exports = WebAssembly.Module.exports(wasmModule);\n const imports = WebAssembly.Module.imports(wasmModule).reduce<\n Record<string, string[]>\n >((target, descriptor) => {\n target[descriptor.module] ??= [];\n target[descriptor.module].push(descriptor.name);\n\n return target;\n }, {});\n\n // Add the WASM import as a dependency so that Webpack will watch it for\n // changes.\n for (const name of Object.keys(imports)) {\n this.addDependency(name);\n }\n\n return `\n ${getImports(imports)}\n\n const bytes = new Uint8Array(${JSON.stringify(Array.from(source))});\n const module = new WebAssembly.Module(bytes);\n const instance = new WebAssembly.Instance(module, {\n ${getModuleImports(imports)}\n });\n\n const exports = instance.exports;\n ${getExports(exports)}\n `;\n};\n\nexport default loader;\n\n// By setting `raw` to `true`, we are telling Webpack to provide the source as a\n// `Uint8Array` instead of converting it to a string. This allows us to avoid\n// having to convert the source back to a `Uint8Array` in the loader.\nexport const raw = true;\n"],"names":["getImports","getModuleImports","getExports","raw","importMap","Object","entries","map","moduleName","exportNames","join","JSON","stringify","descriptors","descriptor","name","loader","source","assert","Uint8Array","bytes","wasmModule","WebAssembly","compile","exports","Module","imports","reduce","target","module","push","keys","addDependency","Array","from"],"mappings":"AAAA,wCAAwC;;;;;;;;;;;IAYxBA,UAAU;eAAVA;;IAkBAC,gBAAgB;eAAhBA;;IAiBAC,UAAU;eAAVA;;IAoEhB,OAAsB;eAAtB;;IAKaC,GAAG;eAAHA;;;uBAtHU;AAUhB,SAASH,WAAWI,SAAmC;IAC5D,OAAOC,OAAOC,OAAO,CAACF,WACnBG,GAAG,CACF,CAAC,CAACC,YAAYC,YAAY,GACxB,CAAC,SAAS,EAAEA,YAAYC,IAAI,CAAC,MAAM,QAAQ,EAAEC,KAAKC,SAAS,CACzDJ,YACA,CAAC,CAAC,EAEPE,IAAI,CAAC;AACV;AASO,SAAST,iBAAiBG,SAAmC;IAClE,OAAOC,OAAOC,OAAO,CAACF,WACnBG,GAAG,CACF,CAAC,CAACC,YAAYC,YAAY,GACxB,CAAC,EAAEE,KAAKC,SAAS,CAACJ,YAAY,IAAI,EAAEC,YAAYC,IAAI,CAAC,MAAM,GAAG,CAAC,EAElEA,IAAI,CAAC;AACV;AAUO,SAASR,WAAWW,WAAiD;IAC1E,OAAOA,YACJN,GAAG,CAAC,CAACO;QACJ,IAAIA,WAAWC,IAAI,KAAK,WAAW;YACjC,OAAO,CAAC,uBAAuB,EAAEJ,KAAKC,SAAS,CAACE,WAAWC,IAAI,EAAE,EAAE,CAAC;QACtE;QAEA,OAAO,CAAC,aAAa,EAAED,WAAWC,IAAI,CAAC,WAAW,EAAEJ,KAAKC,SAAS,CAChEE,WAAWC,IAAI,EACf,EAAE,CAAC;IACP,GACCL,IAAI,CAAC;AACV;AAEA;;;;;;;;;;;;CAYC,GACD,8EAA8E;AAC9E,6EAA6E;AAC7E,MAAMM,SAAmC,eAAeA,OACtDC,MAAe;IAEfC,IAAAA,aAAM,EAACD,kBAAkBE,YAAY;IAErC,MAAMC,QAAQ,IAAID,WAAWF;IAC7B,MAAMI,aAAa,MAAMC,YAAYC,OAAO,CAACH;IAE7C,wDAAwD;IACxD,MAAMI,WAAUF,YAAYG,MAAM,CAACD,OAAO,CAACH;IAC3C,MAAMK,UAAUJ,YAAYG,MAAM,CAACC,OAAO,CAACL,YAAYM,MAAM,CAE3D,CAACC,QAAQd;YACTc,SAAOd;QAAPc,CAAAA,UAAAA,OAAM,CAACd,qBAAAA,WAAWe,MAAM,CAAC,KAAzBD,OAAM,CAACd,mBAAkB,GAAK,EAAE;QAChCc,MAAM,CAACd,WAAWe,MAAM,CAAC,CAACC,IAAI,CAAChB,WAAWC,IAAI;QAE9C,OAAOa;IACT,GAAG,CAAC;IAEJ,wEAAwE;IACxE,WAAW;IACX,KAAK,MAAMb,QAAQV,OAAO0B,IAAI,CAACL,SAAU;QACvC,IAAI,CAACM,aAAa,CAACjB;IACrB;IAEA,OAAO,CAAC;IACN,EAAEf,WAAW0B,SAAS;;iCAEO,EAAEf,KAAKC,SAAS,CAACqB,MAAMC,IAAI,CAACjB,SAAS;;;MAGhE,EAAEhB,iBAAiByB,SAAS;;;;IAI9B,EAAExB,WAAWsB,UAAS;EACxB,CAAC;AACH;MAEA,WAAeR;AAKR,MAAMb,MAAM"}
|
|
@@ -40,11 +40,15 @@ function _interop_require_default(obj) {
|
|
|
40
40
|
function getAllowedPaths(config, manifest) {
|
|
41
41
|
const auxiliaryFiles = manifest.source.files?.map((file)=>getRelativePath(config.server.root, (0, _path.resolve)(config.server.root, file))) ?? [];
|
|
42
42
|
const localizationFiles = manifest.source.locales?.map((localization)=>getRelativePath(config.server.root, (0, _path.resolve)(config.server.root, localization))) ?? [];
|
|
43
|
+
const otherFiles = manifest.source.location.npm.iconPath ? [
|
|
44
|
+
getRelativePath(config.server.root, (0, _path.resolve)(config.server.root, manifest.source.location.npm.iconPath))
|
|
45
|
+
] : [];
|
|
43
46
|
return [
|
|
44
47
|
getRelativePath(config.server.root, (0, _path.resolve)(config.server.root, config.output.path, config.output.filename)),
|
|
45
48
|
getRelativePath(config.server.root, (0, _path.resolve)(config.server.root, _snapsutils.NpmSnapFileNames.Manifest)),
|
|
46
49
|
...auxiliaryFiles,
|
|
47
|
-
...localizationFiles
|
|
50
|
+
...localizationFiles,
|
|
51
|
+
...otherFiles
|
|
48
52
|
];
|
|
49
53
|
}
|
|
50
54
|
function getServer(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/webpack/server.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n logError,\n NpmSnapFileNames,\n readJsonFile,\n} from '@metamask/snaps-utils';\nimport type { IncomingMessage, Server, ServerResponse } from 'http';\nimport { createServer } from 'http';\nimport type { AddressInfo } from 'net';\nimport { join, relative, resolve as resolvePath, sep, posix } from 'path';\nimport serveMiddleware from 'serve-handler';\n\nimport type { ProcessedConfig } from '../config';\n\n/**\n * Get the relative path from one path to another.\n *\n * Note: This is a modified version of `path.relative` that uses Posix\n * separators for URL-compatibility.\n *\n * @param from - The path to start from.\n * @param to - The path to end at.\n * @returns The relative path.\n */\nfunction getRelativePath(from: string, to: string) {\n return relative(from, to).split(sep).join(posix.sep);\n}\n\n/**\n * Get the allowed paths for the static server. This includes the output file,\n * the manifest file, and any auxiliary/localization files.\n *\n * @param config - The config object.\n * @param manifest - The Snap manifest object.\n * @returns An array of allowed paths.\n */\nexport function getAllowedPaths(\n config: ProcessedConfig,\n manifest: SnapManifest,\n) {\n const auxiliaryFiles =\n manifest.source.files?.map((file) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, file),\n ),\n ) ?? [];\n\n const localizationFiles =\n manifest.source.locales?.map((localization) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, localization),\n ),\n ) ?? [];\n\n return [\n getRelativePath(\n config.server.root,\n resolvePath(\n config.server.root,\n config.output.path,\n config.output.filename,\n ),\n ),\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, NpmSnapFileNames.Manifest),\n ),\n ...auxiliaryFiles,\n ...localizationFiles,\n ];\n}\n\n/**\n * Get a static server for development purposes.\n *\n * Note: We're intentionally not using `webpack-dev-server` here because it\n * adds a lot of extra stuff to the output that we don't need, and it's\n * difficult to customize.\n *\n * @param config - The config object.\n * @returns An object with a `listen` method that returns a promise that\n * resolves when the server is listening.\n */\nexport function getServer(config: ProcessedConfig) {\n /**\n * Get the response for a request. This is extracted into a function so that\n * we can easily catch errors and send a 500 response.\n *\n * @param request - The request.\n * @param response - The response.\n * @returns A promise that resolves when the response is sent.\n */\n async function getResponse(\n request: IncomingMessage,\n response: ServerResponse,\n ) {\n const manifestPath = join(config.server.root, NpmSnapFileNames.Manifest);\n const { result } = await readJsonFile<SnapManifest>(manifestPath);\n const allowedPaths = getAllowedPaths(config, result);\n\n const path = request.url?.slice(1);\n const allowed = allowedPaths.some((allowedPath) => path === allowedPath);\n\n if (!allowed) {\n response.statusCode = 404;\n response.end();\n return;\n }\n\n await serveMiddleware(request, response, {\n public: config.server.root,\n directoryListing: false,\n headers: [\n {\n source: '**/*',\n headers: [\n {\n key: 'Cache-Control',\n value: 'no-cache',\n },\n {\n key: 'Access-Control-Allow-Origin',\n value: '*',\n },\n ],\n },\n ],\n });\n }\n\n const server = createServer((request, response) => {\n getResponse(request, response).catch(\n /* istanbul ignore next */\n (error) => {\n logError(error);\n response.statusCode = 500;\n response.end();\n },\n );\n });\n\n /**\n * Start the server on the port specified in the config.\n *\n * @param port - The port to listen on.\n * @returns A promise that resolves when the server is listening. The promise\n * resolves to an object with the port and the server instance. Note that if\n * the `config.server.port` is `0`, the OS will choose a random port for us,\n * so we need to get the port from the server after it starts.\n */\n const listen = async (port = config.server.port) => {\n return new Promise<{\n port: number;\n server: Server;\n close: () => Promise<void>;\n }>((resolve, reject) => {\n try {\n server.listen(port, () => {\n const close = async () => {\n await new Promise<void>((resolveClose, rejectClose) => {\n server.close((closeError) => {\n if (closeError) {\n return rejectClose(closeError);\n }\n\n return resolveClose();\n });\n });\n };\n\n const address = server.address() as AddressInfo;\n resolve({ port: address.port, server, close });\n });\n } catch (listenError) {\n reject(listenError);\n }\n });\n };\n\n return { listen };\n}\n"],"names":["getAllowedPaths","getServer","getRelativePath","from","to","relative","split","sep","join","posix","config","manifest","auxiliaryFiles","source","files","map","file","server","root","resolvePath","localizationFiles","locales","localization","output","path","filename","NpmSnapFileNames","Manifest","getResponse","request","response","manifestPath","result","readJsonFile","allowedPaths","url","slice","allowed","some","allowedPath","statusCode","end","serveMiddleware","public","directoryListing","headers","key","value","createServer","catch","error","logError","listen","port","Promise","resolve","reject","close","resolveClose","rejectClose","closeError","address","listenError"],"mappings":";;;;;;;;;;;IAoCgBA,eAAe;eAAfA;;
|
|
1
|
+
{"version":3,"sources":["../../../src/webpack/server.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n logError,\n NpmSnapFileNames,\n readJsonFile,\n} from '@metamask/snaps-utils';\nimport type { IncomingMessage, Server, ServerResponse } from 'http';\nimport { createServer } from 'http';\nimport type { AddressInfo } from 'net';\nimport { join, relative, resolve as resolvePath, sep, posix } from 'path';\nimport serveMiddleware from 'serve-handler';\n\nimport type { ProcessedConfig } from '../config';\n\n/**\n * Get the relative path from one path to another.\n *\n * Note: This is a modified version of `path.relative` that uses Posix\n * separators for URL-compatibility.\n *\n * @param from - The path to start from.\n * @param to - The path to end at.\n * @returns The relative path.\n */\nfunction getRelativePath(from: string, to: string) {\n return relative(from, to).split(sep).join(posix.sep);\n}\n\n/**\n * Get the allowed paths for the static server. This includes the output file,\n * the manifest file, and any auxiliary/localization files.\n *\n * @param config - The config object.\n * @param manifest - The Snap manifest object.\n * @returns An array of allowed paths.\n */\nexport function getAllowedPaths(\n config: ProcessedConfig,\n manifest: SnapManifest,\n) {\n const auxiliaryFiles =\n manifest.source.files?.map((file) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, file),\n ),\n ) ?? [];\n\n const localizationFiles =\n manifest.source.locales?.map((localization) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, localization),\n ),\n ) ?? [];\n\n const otherFiles = manifest.source.location.npm.iconPath\n ? [\n getRelativePath(\n config.server.root,\n resolvePath(\n config.server.root,\n manifest.source.location.npm.iconPath,\n ),\n ),\n ]\n : [];\n\n return [\n getRelativePath(\n config.server.root,\n resolvePath(\n config.server.root,\n config.output.path,\n config.output.filename,\n ),\n ),\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, NpmSnapFileNames.Manifest),\n ),\n ...auxiliaryFiles,\n ...localizationFiles,\n ...otherFiles,\n ];\n}\n\n/**\n * Get a static server for development purposes.\n *\n * Note: We're intentionally not using `webpack-dev-server` here because it\n * adds a lot of extra stuff to the output that we don't need, and it's\n * difficult to customize.\n *\n * @param config - The config object.\n * @returns An object with a `listen` method that returns a promise that\n * resolves when the server is listening.\n */\nexport function getServer(config: ProcessedConfig) {\n /**\n * Get the response for a request. This is extracted into a function so that\n * we can easily catch errors and send a 500 response.\n *\n * @param request - The request.\n * @param response - The response.\n * @returns A promise that resolves when the response is sent.\n */\n async function getResponse(\n request: IncomingMessage,\n response: ServerResponse,\n ) {\n const manifestPath = join(config.server.root, NpmSnapFileNames.Manifest);\n const { result } = await readJsonFile<SnapManifest>(manifestPath);\n const allowedPaths = getAllowedPaths(config, result);\n\n const path = request.url?.slice(1);\n const allowed = allowedPaths.some((allowedPath) => path === allowedPath);\n\n if (!allowed) {\n response.statusCode = 404;\n response.end();\n return;\n }\n\n await serveMiddleware(request, response, {\n public: config.server.root,\n directoryListing: false,\n headers: [\n {\n source: '**/*',\n headers: [\n {\n key: 'Cache-Control',\n value: 'no-cache',\n },\n {\n key: 'Access-Control-Allow-Origin',\n value: '*',\n },\n ],\n },\n ],\n });\n }\n\n const server = createServer((request, response) => {\n getResponse(request, response).catch(\n /* istanbul ignore next */\n (error) => {\n logError(error);\n response.statusCode = 500;\n response.end();\n },\n );\n });\n\n /**\n * Start the server on the port specified in the config.\n *\n * @param port - The port to listen on.\n * @returns A promise that resolves when the server is listening. The promise\n * resolves to an object with the port and the server instance. Note that if\n * the `config.server.port` is `0`, the OS will choose a random port for us,\n * so we need to get the port from the server after it starts.\n */\n const listen = async (port = config.server.port) => {\n return new Promise<{\n port: number;\n server: Server;\n close: () => Promise<void>;\n }>((resolve, reject) => {\n try {\n server.listen(port, () => {\n const close = async () => {\n await new Promise<void>((resolveClose, rejectClose) => {\n server.close((closeError) => {\n if (closeError) {\n return rejectClose(closeError);\n }\n\n return resolveClose();\n });\n });\n };\n\n const address = server.address() as AddressInfo;\n resolve({ port: address.port, server, close });\n });\n } catch (listenError) {\n reject(listenError);\n }\n });\n };\n\n return { listen };\n}\n"],"names":["getAllowedPaths","getServer","getRelativePath","from","to","relative","split","sep","join","posix","config","manifest","auxiliaryFiles","source","files","map","file","server","root","resolvePath","localizationFiles","locales","localization","otherFiles","location","npm","iconPath","output","path","filename","NpmSnapFileNames","Manifest","getResponse","request","response","manifestPath","result","readJsonFile","allowedPaths","url","slice","allowed","some","allowedPath","statusCode","end","serveMiddleware","public","directoryListing","headers","key","value","createServer","catch","error","logError","listen","port","Promise","resolve","reject","close","resolveClose","rejectClose","closeError","address","listenError"],"mappings":"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"}
|
|
@@ -1,26 +1,85 @@
|
|
|
1
|
-
import { assert
|
|
1
|
+
/* eslint-disable no-restricted-globals */ import { assert } from '@metamask/utils';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `WebAssembly.instantiate` function.
|
|
3
|
+
* Get the imports code for the WASM module. This code imports each of the
|
|
4
|
+
* imports from the WASM module.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
6
|
+
* @param importMap - The import map for the WASM module.
|
|
7
|
+
* @returns The imports code for the WASM module.
|
|
8
|
+
*/ export function getImports(importMap) {
|
|
9
|
+
return Object.entries(importMap).map(([moduleName, exportNames])=>`import { ${exportNames.join(', ')} } from ${JSON.stringify(moduleName)};`).join('\n');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the imports code to use in `WebAssembly.Instance`. This code adds each of
|
|
13
|
+
* the imports to the `imports` object.
|
|
14
|
+
*
|
|
15
|
+
* @param importMap - The import map for the WASM module.
|
|
16
|
+
* @returns The imports code for the WASM module.
|
|
17
|
+
*/ export function getModuleImports(importMap) {
|
|
18
|
+
return Object.entries(importMap).map(([moduleName, exportNames])=>`${JSON.stringify(moduleName)}: { ${exportNames.join(', ')} },`).join('\n');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the exports code for the WASM module. This code exports each of the
|
|
22
|
+
* exports from the WASM module as a variable. This function assumes that the
|
|
23
|
+
* exports are available in a variable named `exports`.
|
|
10
24
|
*
|
|
11
|
-
* @param
|
|
12
|
-
* @returns
|
|
25
|
+
* @param descriptors - The export descriptors from the WASM module.
|
|
26
|
+
* @returns The exports code for the WASM module.
|
|
27
|
+
*/ export function getExports(descriptors) {
|
|
28
|
+
return descriptors.map((descriptor)=>{
|
|
29
|
+
if (descriptor.name === 'default') {
|
|
30
|
+
return `export default exports[${JSON.stringify(descriptor.name)}];`;
|
|
31
|
+
}
|
|
32
|
+
return `export const ${descriptor.name} = exports[${JSON.stringify(descriptor.name)}];`;
|
|
33
|
+
}).join('\n');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A Webpack loader that synchronously loads the WASM module. This makes it
|
|
37
|
+
* possible to import the WASM module directly.
|
|
38
|
+
*
|
|
39
|
+
* @param source - The WASM module as `Uint8Array`.
|
|
40
|
+
* @returns The WASM module as a JavaScript string.
|
|
13
41
|
* @example
|
|
14
42
|
* ```ts
|
|
15
|
-
* import wasm from './program.wasm';
|
|
43
|
+
* import * as wasm from './program.wasm';
|
|
16
44
|
*
|
|
17
|
-
* const { instance } = await WebAssembly.instantiate(wasm, {});
|
|
18
45
|
* // Do something with the WASM module...
|
|
19
46
|
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
*/ // Note: This function needs to be defined like this, so that Webpack can bind
|
|
48
|
+
// `this` to the loader context, and TypeScript can infer the type of `this`.
|
|
49
|
+
const loader = async function loader(source) {
|
|
50
|
+
assert(source instanceof Uint8Array, 'Expected source to be a Uint8Array.');
|
|
51
|
+
const bytes = new Uint8Array(source);
|
|
52
|
+
const wasmModule = await WebAssembly.compile(bytes);
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
54
|
+
const exports = WebAssembly.Module.exports(wasmModule);
|
|
55
|
+
const imports = WebAssembly.Module.imports(wasmModule).reduce((target, descriptor)=>{
|
|
56
|
+
var _target, _descriptor_module;
|
|
57
|
+
(_target = target)[_descriptor_module = descriptor.module] ?? (_target[_descriptor_module] = []);
|
|
58
|
+
target[descriptor.module].push(descriptor.name);
|
|
59
|
+
return target;
|
|
60
|
+
}, {});
|
|
61
|
+
// Add the WASM import as a dependency so that Webpack will watch it for
|
|
62
|
+
// changes.
|
|
63
|
+
for (const name of Object.keys(imports)){
|
|
64
|
+
this.addDependency(name);
|
|
65
|
+
}
|
|
66
|
+
return `
|
|
67
|
+
${getImports(imports)}
|
|
68
|
+
|
|
69
|
+
const bytes = new Uint8Array(${JSON.stringify(Array.from(source))});
|
|
70
|
+
const module = new WebAssembly.Module(bytes);
|
|
71
|
+
const instance = new WebAssembly.Instance(module, {
|
|
72
|
+
${getModuleImports(imports)}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const exports = instance.exports;
|
|
76
|
+
${getExports(exports)}
|
|
77
|
+
`;
|
|
78
|
+
};
|
|
79
|
+
export default loader;
|
|
80
|
+
// By setting `raw` to `true`, we are telling Webpack to provide the source as a
|
|
81
|
+
// `Uint8Array` instead of converting it to a string. This allows us to avoid
|
|
82
|
+
// having to convert the source back to a `Uint8Array` in the loader.
|
|
83
|
+
export const raw = true;
|
|
25
84
|
|
|
26
85
|
//# sourceMappingURL=wasm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/webpack/loaders/wasm.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../../src/webpack/loaders/wasm.ts"],"sourcesContent":["/* eslint-disable no-restricted-globals */\n\nimport { assert } from '@metamask/utils';\nimport type { LoaderDefinitionFunction } from 'webpack';\n\n/**\n * Get the imports code for the WASM module. This code imports each of the\n * imports from the WASM module.\n *\n * @param importMap - The import map for the WASM module.\n * @returns The imports code for the WASM module.\n */\nexport function getImports(importMap: Record<string, string[]>) {\n return Object.entries(importMap)\n .map(\n ([moduleName, exportNames]) =>\n `import { ${exportNames.join(', ')} } from ${JSON.stringify(\n moduleName,\n )};`,\n )\n .join('\\n');\n}\n\n/**\n * Get the imports code to use in `WebAssembly.Instance`. This code adds each of\n * the imports to the `imports` object.\n *\n * @param importMap - The import map for the WASM module.\n * @returns The imports code for the WASM module.\n */\nexport function getModuleImports(importMap: Record<string, string[]>) {\n return Object.entries(importMap)\n .map(\n ([moduleName, exportNames]) =>\n `${JSON.stringify(moduleName)}: { ${exportNames.join(', ')} },`,\n )\n .join('\\n');\n}\n\n/**\n * Get the exports code for the WASM module. This code exports each of the\n * exports from the WASM module as a variable. This function assumes that the\n * exports are available in a variable named `exports`.\n *\n * @param descriptors - The export descriptors from the WASM module.\n * @returns The exports code for the WASM module.\n */\nexport function getExports(descriptors: WebAssembly.ModuleExportDescriptor[]) {\n return descriptors\n .map((descriptor) => {\n if (descriptor.name === 'default') {\n return `export default exports[${JSON.stringify(descriptor.name)}];`;\n }\n\n return `export const ${descriptor.name} = exports[${JSON.stringify(\n descriptor.name,\n )}];`;\n })\n .join('\\n');\n}\n\n/**\n * A Webpack loader that synchronously loads the WASM module. This makes it\n * possible to import the WASM module directly.\n *\n * @param source - The WASM module as `Uint8Array`.\n * @returns The WASM module as a JavaScript string.\n * @example\n * ```ts\n * import * as wasm from './program.wasm';\n *\n * // Do something with the WASM module...\n * ```\n */\n// Note: This function needs to be defined like this, so that Webpack can bind\n// `this` to the loader context, and TypeScript can infer the type of `this`.\nconst loader: LoaderDefinitionFunction = async function loader(\n source: unknown,\n) {\n assert(source instanceof Uint8Array, 'Expected source to be a Uint8Array.');\n\n const bytes = new Uint8Array(source);\n const wasmModule = await WebAssembly.compile(bytes);\n\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const exports = WebAssembly.Module.exports(wasmModule);\n const imports = WebAssembly.Module.imports(wasmModule).reduce<\n Record<string, string[]>\n >((target, descriptor) => {\n target[descriptor.module] ??= [];\n target[descriptor.module].push(descriptor.name);\n\n return target;\n }, {});\n\n // Add the WASM import as a dependency so that Webpack will watch it for\n // changes.\n for (const name of Object.keys(imports)) {\n this.addDependency(name);\n }\n\n return `\n ${getImports(imports)}\n\n const bytes = new Uint8Array(${JSON.stringify(Array.from(source))});\n const module = new WebAssembly.Module(bytes);\n const instance = new WebAssembly.Instance(module, {\n ${getModuleImports(imports)}\n });\n\n const exports = instance.exports;\n ${getExports(exports)}\n `;\n};\n\nexport default loader;\n\n// By setting `raw` to `true`, we are telling Webpack to provide the source as a\n// `Uint8Array` instead of converting it to a string. This allows us to avoid\n// having to convert the source back to a `Uint8Array` in the loader.\nexport const raw = true;\n"],"names":["assert","getImports","importMap","Object","entries","map","moduleName","exportNames","join","JSON","stringify","getModuleImports","getExports","descriptors","descriptor","name","loader","source","Uint8Array","bytes","wasmModule","WebAssembly","compile","exports","Module","imports","reduce","target","module","push","keys","addDependency","Array","from","raw"],"mappings":"AAAA,wCAAwC,GAExC,SAASA,MAAM,QAAQ,kBAAkB;AAGzC;;;;;;CAMC,GACD,OAAO,SAASC,WAAWC,SAAmC;IAC5D,OAAOC,OAAOC,OAAO,CAACF,WACnBG,GAAG,CACF,CAAC,CAACC,YAAYC,YAAY,GACxB,CAAC,SAAS,EAAEA,YAAYC,IAAI,CAAC,MAAM,QAAQ,EAAEC,KAAKC,SAAS,CACzDJ,YACA,CAAC,CAAC,EAEPE,IAAI,CAAC;AACV;AAEA;;;;;;CAMC,GACD,OAAO,SAASG,iBAAiBT,SAAmC;IAClE,OAAOC,OAAOC,OAAO,CAACF,WACnBG,GAAG,CACF,CAAC,CAACC,YAAYC,YAAY,GACxB,CAAC,EAAEE,KAAKC,SAAS,CAACJ,YAAY,IAAI,EAAEC,YAAYC,IAAI,CAAC,MAAM,GAAG,CAAC,EAElEA,IAAI,CAAC;AACV;AAEA;;;;;;;CAOC,GACD,OAAO,SAASI,WAAWC,WAAiD;IAC1E,OAAOA,YACJR,GAAG,CAAC,CAACS;QACJ,IAAIA,WAAWC,IAAI,KAAK,WAAW;YACjC,OAAO,CAAC,uBAAuB,EAAEN,KAAKC,SAAS,CAACI,WAAWC,IAAI,EAAE,EAAE,CAAC;QACtE;QAEA,OAAO,CAAC,aAAa,EAAED,WAAWC,IAAI,CAAC,WAAW,EAAEN,KAAKC,SAAS,CAChEI,WAAWC,IAAI,EACf,EAAE,CAAC;IACP,GACCP,IAAI,CAAC;AACV;AAEA;;;;;;;;;;;;CAYC,GACD,8EAA8E;AAC9E,6EAA6E;AAC7E,MAAMQ,SAAmC,eAAeA,OACtDC,MAAe;IAEfjB,OAAOiB,kBAAkBC,YAAY;IAErC,MAAMC,QAAQ,IAAID,WAAWD;IAC7B,MAAMG,aAAa,MAAMC,YAAYC,OAAO,CAACH;IAE7C,wDAAwD;IACxD,MAAMI,UAAUF,YAAYG,MAAM,CAACD,OAAO,CAACH;IAC3C,MAAMK,UAAUJ,YAAYG,MAAM,CAACC,OAAO,CAACL,YAAYM,MAAM,CAE3D,CAACC,QAAQb;YACTa,SAAOb;QAAPa,CAAAA,UAAAA,OAAM,CAACb,qBAAAA,WAAWc,MAAM,CAAC,KAAzBD,OAAM,CAACb,mBAAkB,GAAK,EAAE;QAChCa,MAAM,CAACb,WAAWc,MAAM,CAAC,CAACC,IAAI,CAACf,WAAWC,IAAI;QAE9C,OAAOY;IACT,GAAG,CAAC;IAEJ,wEAAwE;IACxE,WAAW;IACX,KAAK,MAAMZ,QAAQZ,OAAO2B,IAAI,CAACL,SAAU;QACvC,IAAI,CAACM,aAAa,CAAChB;IACrB;IAEA,OAAO,CAAC;IACN,EAAEd,WAAWwB,SAAS;;iCAEO,EAAEhB,KAAKC,SAAS,CAACsB,MAAMC,IAAI,CAAChB,SAAS;;;MAGhE,EAAEN,iBAAiBc,SAAS;;;;IAI9B,EAAEb,WAAWW,SAAS;EACxB,CAAC;AACH;AAEA,eAAeP,OAAO;AAEtB,gFAAgF;AAChF,6EAA6E;AAC7E,qEAAqE;AACrE,OAAO,MAAMkB,MAAM,KAAK"}
|
|
@@ -24,11 +24,15 @@ import serveMiddleware from 'serve-handler';
|
|
|
24
24
|
*/ export function getAllowedPaths(config, manifest) {
|
|
25
25
|
const auxiliaryFiles = manifest.source.files?.map((file)=>getRelativePath(config.server.root, resolvePath(config.server.root, file))) ?? [];
|
|
26
26
|
const localizationFiles = manifest.source.locales?.map((localization)=>getRelativePath(config.server.root, resolvePath(config.server.root, localization))) ?? [];
|
|
27
|
+
const otherFiles = manifest.source.location.npm.iconPath ? [
|
|
28
|
+
getRelativePath(config.server.root, resolvePath(config.server.root, manifest.source.location.npm.iconPath))
|
|
29
|
+
] : [];
|
|
27
30
|
return [
|
|
28
31
|
getRelativePath(config.server.root, resolvePath(config.server.root, config.output.path, config.output.filename)),
|
|
29
32
|
getRelativePath(config.server.root, resolvePath(config.server.root, NpmSnapFileNames.Manifest)),
|
|
30
33
|
...auxiliaryFiles,
|
|
31
|
-
...localizationFiles
|
|
34
|
+
...localizationFiles,
|
|
35
|
+
...otherFiles
|
|
32
36
|
];
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/webpack/server.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n logError,\n NpmSnapFileNames,\n readJsonFile,\n} from '@metamask/snaps-utils';\nimport type { IncomingMessage, Server, ServerResponse } from 'http';\nimport { createServer } from 'http';\nimport type { AddressInfo } from 'net';\nimport { join, relative, resolve as resolvePath, sep, posix } from 'path';\nimport serveMiddleware from 'serve-handler';\n\nimport type { ProcessedConfig } from '../config';\n\n/**\n * Get the relative path from one path to another.\n *\n * Note: This is a modified version of `path.relative` that uses Posix\n * separators for URL-compatibility.\n *\n * @param from - The path to start from.\n * @param to - The path to end at.\n * @returns The relative path.\n */\nfunction getRelativePath(from: string, to: string) {\n return relative(from, to).split(sep).join(posix.sep);\n}\n\n/**\n * Get the allowed paths for the static server. This includes the output file,\n * the manifest file, and any auxiliary/localization files.\n *\n * @param config - The config object.\n * @param manifest - The Snap manifest object.\n * @returns An array of allowed paths.\n */\nexport function getAllowedPaths(\n config: ProcessedConfig,\n manifest: SnapManifest,\n) {\n const auxiliaryFiles =\n manifest.source.files?.map((file) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, file),\n ),\n ) ?? [];\n\n const localizationFiles =\n manifest.source.locales?.map((localization) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, localization),\n ),\n ) ?? [];\n\n return [\n getRelativePath(\n config.server.root,\n resolvePath(\n config.server.root,\n config.output.path,\n config.output.filename,\n ),\n ),\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, NpmSnapFileNames.Manifest),\n ),\n ...auxiliaryFiles,\n ...localizationFiles,\n ];\n}\n\n/**\n * Get a static server for development purposes.\n *\n * Note: We're intentionally not using `webpack-dev-server` here because it\n * adds a lot of extra stuff to the output that we don't need, and it's\n * difficult to customize.\n *\n * @param config - The config object.\n * @returns An object with a `listen` method that returns a promise that\n * resolves when the server is listening.\n */\nexport function getServer(config: ProcessedConfig) {\n /**\n * Get the response for a request. This is extracted into a function so that\n * we can easily catch errors and send a 500 response.\n *\n * @param request - The request.\n * @param response - The response.\n * @returns A promise that resolves when the response is sent.\n */\n async function getResponse(\n request: IncomingMessage,\n response: ServerResponse,\n ) {\n const manifestPath = join(config.server.root, NpmSnapFileNames.Manifest);\n const { result } = await readJsonFile<SnapManifest>(manifestPath);\n const allowedPaths = getAllowedPaths(config, result);\n\n const path = request.url?.slice(1);\n const allowed = allowedPaths.some((allowedPath) => path === allowedPath);\n\n if (!allowed) {\n response.statusCode = 404;\n response.end();\n return;\n }\n\n await serveMiddleware(request, response, {\n public: config.server.root,\n directoryListing: false,\n headers: [\n {\n source: '**/*',\n headers: [\n {\n key: 'Cache-Control',\n value: 'no-cache',\n },\n {\n key: 'Access-Control-Allow-Origin',\n value: '*',\n },\n ],\n },\n ],\n });\n }\n\n const server = createServer((request, response) => {\n getResponse(request, response).catch(\n /* istanbul ignore next */\n (error) => {\n logError(error);\n response.statusCode = 500;\n response.end();\n },\n );\n });\n\n /**\n * Start the server on the port specified in the config.\n *\n * @param port - The port to listen on.\n * @returns A promise that resolves when the server is listening. The promise\n * resolves to an object with the port and the server instance. Note that if\n * the `config.server.port` is `0`, the OS will choose a random port for us,\n * so we need to get the port from the server after it starts.\n */\n const listen = async (port = config.server.port) => {\n return new Promise<{\n port: number;\n server: Server;\n close: () => Promise<void>;\n }>((resolve, reject) => {\n try {\n server.listen(port, () => {\n const close = async () => {\n await new Promise<void>((resolveClose, rejectClose) => {\n server.close((closeError) => {\n if (closeError) {\n return rejectClose(closeError);\n }\n\n return resolveClose();\n });\n });\n };\n\n const address = server.address() as AddressInfo;\n resolve({ port: address.port, server, close });\n });\n } catch (listenError) {\n reject(listenError);\n }\n });\n };\n\n return { listen };\n}\n"],"names":["logError","NpmSnapFileNames","readJsonFile","createServer","join","relative","resolve","resolvePath","sep","posix","serveMiddleware","getRelativePath","from","to","split","getAllowedPaths","config","manifest","auxiliaryFiles","source","files","map","file","server","root","localizationFiles","locales","localization","output","path","filename","Manifest","getServer","getResponse","request","response","manifestPath","result","allowedPaths","url","slice","allowed","some","allowedPath","statusCode","end","public","directoryListing","headers","key","value","catch","error","listen","port","Promise","reject","close","resolveClose","rejectClose","closeError","address","listenError"],"mappings":"AACA,SACEA,QAAQ,EACRC,gBAAgB,EAChBC,YAAY,QACP,wBAAwB;AAE/B,SAASC,YAAY,QAAQ,OAAO;AAEpC,SAASC,IAAI,EAAEC,QAAQ,EAAEC,WAAWC,WAAW,EAAEC,GAAG,EAAEC,KAAK,QAAQ,OAAO;AAC1E,OAAOC,qBAAqB,gBAAgB;AAI5C;;;;;;;;;CASC,GACD,SAASC,gBAAgBC,IAAY,EAAEC,EAAU;IAC/C,OAAOR,SAASO,MAAMC,IAAIC,KAAK,CAACN,KAAKJ,IAAI,CAACK,MAAMD,GAAG;AACrD;AAEA;;;;;;;CAOC,GACD,OAAO,SAASO,gBACdC,MAAuB,EACvBC,QAAsB;IAEtB,MAAMC,iBACJD,SAASE,MAAM,CAACC,KAAK,EAAEC,IAAI,CAACC,OAC1BX,gBACEK,OAAOO,MAAM,CAACC,IAAI,EAClBjB,YAAYS,OAAOO,MAAM,CAACC,IAAI,EAAEF,WAE/B,EAAE;IAET,MAAMG,oBACJR,SAASE,MAAM,CAACO,OAAO,EAAEL,IAAI,CAACM,eAC5BhB,gBACEK,OAAOO,MAAM,CAACC,IAAI,EAClBjB,YAAYS,OAAOO,MAAM,CAACC,IAAI,EAAEG,mBAE/B,EAAE;IAET,OAAO;
|
|
1
|
+
{"version":3,"sources":["../../../src/webpack/server.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n logError,\n NpmSnapFileNames,\n readJsonFile,\n} from '@metamask/snaps-utils';\nimport type { IncomingMessage, Server, ServerResponse } from 'http';\nimport { createServer } from 'http';\nimport type { AddressInfo } from 'net';\nimport { join, relative, resolve as resolvePath, sep, posix } from 'path';\nimport serveMiddleware from 'serve-handler';\n\nimport type { ProcessedConfig } from '../config';\n\n/**\n * Get the relative path from one path to another.\n *\n * Note: This is a modified version of `path.relative` that uses Posix\n * separators for URL-compatibility.\n *\n * @param from - The path to start from.\n * @param to - The path to end at.\n * @returns The relative path.\n */\nfunction getRelativePath(from: string, to: string) {\n return relative(from, to).split(sep).join(posix.sep);\n}\n\n/**\n * Get the allowed paths for the static server. This includes the output file,\n * the manifest file, and any auxiliary/localization files.\n *\n * @param config - The config object.\n * @param manifest - The Snap manifest object.\n * @returns An array of allowed paths.\n */\nexport function getAllowedPaths(\n config: ProcessedConfig,\n manifest: SnapManifest,\n) {\n const auxiliaryFiles =\n manifest.source.files?.map((file) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, file),\n ),\n ) ?? [];\n\n const localizationFiles =\n manifest.source.locales?.map((localization) =>\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, localization),\n ),\n ) ?? [];\n\n const otherFiles = manifest.source.location.npm.iconPath\n ? [\n getRelativePath(\n config.server.root,\n resolvePath(\n config.server.root,\n manifest.source.location.npm.iconPath,\n ),\n ),\n ]\n : [];\n\n return [\n getRelativePath(\n config.server.root,\n resolvePath(\n config.server.root,\n config.output.path,\n config.output.filename,\n ),\n ),\n getRelativePath(\n config.server.root,\n resolvePath(config.server.root, NpmSnapFileNames.Manifest),\n ),\n ...auxiliaryFiles,\n ...localizationFiles,\n ...otherFiles,\n ];\n}\n\n/**\n * Get a static server for development purposes.\n *\n * Note: We're intentionally not using `webpack-dev-server` here because it\n * adds a lot of extra stuff to the output that we don't need, and it's\n * difficult to customize.\n *\n * @param config - The config object.\n * @returns An object with a `listen` method that returns a promise that\n * resolves when the server is listening.\n */\nexport function getServer(config: ProcessedConfig) {\n /**\n * Get the response for a request. This is extracted into a function so that\n * we can easily catch errors and send a 500 response.\n *\n * @param request - The request.\n * @param response - The response.\n * @returns A promise that resolves when the response is sent.\n */\n async function getResponse(\n request: IncomingMessage,\n response: ServerResponse,\n ) {\n const manifestPath = join(config.server.root, NpmSnapFileNames.Manifest);\n const { result } = await readJsonFile<SnapManifest>(manifestPath);\n const allowedPaths = getAllowedPaths(config, result);\n\n const path = request.url?.slice(1);\n const allowed = allowedPaths.some((allowedPath) => path === allowedPath);\n\n if (!allowed) {\n response.statusCode = 404;\n response.end();\n return;\n }\n\n await serveMiddleware(request, response, {\n public: config.server.root,\n directoryListing: false,\n headers: [\n {\n source: '**/*',\n headers: [\n {\n key: 'Cache-Control',\n value: 'no-cache',\n },\n {\n key: 'Access-Control-Allow-Origin',\n value: '*',\n },\n ],\n },\n ],\n });\n }\n\n const server = createServer((request, response) => {\n getResponse(request, response).catch(\n /* istanbul ignore next */\n (error) => {\n logError(error);\n response.statusCode = 500;\n response.end();\n },\n );\n });\n\n /**\n * Start the server on the port specified in the config.\n *\n * @param port - The port to listen on.\n * @returns A promise that resolves when the server is listening. The promise\n * resolves to an object with the port and the server instance. Note that if\n * the `config.server.port` is `0`, the OS will choose a random port for us,\n * so we need to get the port from the server after it starts.\n */\n const listen = async (port = config.server.port) => {\n return new Promise<{\n port: number;\n server: Server;\n close: () => Promise<void>;\n }>((resolve, reject) => {\n try {\n server.listen(port, () => {\n const close = async () => {\n await new Promise<void>((resolveClose, rejectClose) => {\n server.close((closeError) => {\n if (closeError) {\n return rejectClose(closeError);\n }\n\n return resolveClose();\n });\n });\n };\n\n const address = server.address() as AddressInfo;\n resolve({ port: address.port, server, close });\n });\n } catch (listenError) {\n reject(listenError);\n }\n });\n };\n\n return { listen };\n}\n"],"names":["logError","NpmSnapFileNames","readJsonFile","createServer","join","relative","resolve","resolvePath","sep","posix","serveMiddleware","getRelativePath","from","to","split","getAllowedPaths","config","manifest","auxiliaryFiles","source","files","map","file","server","root","localizationFiles","locales","localization","otherFiles","location","npm","iconPath","output","path","filename","Manifest","getServer","getResponse","request","response","manifestPath","result","allowedPaths","url","slice","allowed","some","allowedPath","statusCode","end","public","directoryListing","headers","key","value","catch","error","listen","port","Promise","reject","close","resolveClose","rejectClose","closeError","address","listenError"],"mappings":"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"}
|
|
@@ -1,20 +1,42 @@
|
|
|
1
|
+
import type { LoaderDefinitionFunction } from 'webpack';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* `WebAssembly.instantiate` function.
|
|
3
|
+
* Get the imports code for the WASM module. This code imports each of the
|
|
4
|
+
* imports from the WASM module.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
6
|
+
* @param importMap - The import map for the WASM module.
|
|
7
|
+
* @returns The imports code for the WASM module.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getImports(importMap: Record<string, string[]>): string;
|
|
10
|
+
/**
|
|
11
|
+
* Get the imports code to use in `WebAssembly.Instance`. This code adds each of
|
|
12
|
+
* the imports to the `imports` object.
|
|
13
|
+
*
|
|
14
|
+
* @param importMap - The import map for the WASM module.
|
|
15
|
+
* @returns The imports code for the WASM module.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getModuleImports(importMap: Record<string, string[]>): string;
|
|
18
|
+
/**
|
|
19
|
+
* Get the exports code for the WASM module. This code exports each of the
|
|
20
|
+
* exports from the WASM module as a variable. This function assumes that the
|
|
21
|
+
* exports are available in a variable named `exports`.
|
|
22
|
+
*
|
|
23
|
+
* @param descriptors - The export descriptors from the WASM module.
|
|
24
|
+
* @returns The exports code for the WASM module.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getExports(descriptors: WebAssembly.ModuleExportDescriptor[]): string;
|
|
27
|
+
/**
|
|
28
|
+
* A Webpack loader that synchronously loads the WASM module. This makes it
|
|
29
|
+
* possible to import the WASM module directly.
|
|
9
30
|
*
|
|
10
|
-
* @param source - The WASM module as
|
|
11
|
-
* @returns
|
|
31
|
+
* @param source - The WASM module as `Uint8Array`.
|
|
32
|
+
* @returns The WASM module as a JavaScript string.
|
|
12
33
|
* @example
|
|
13
34
|
* ```ts
|
|
14
|
-
* import wasm from './program.wasm';
|
|
35
|
+
* import * as wasm from './program.wasm';
|
|
15
36
|
*
|
|
16
|
-
* const { instance } = await WebAssembly.instantiate(wasm, {});
|
|
17
37
|
* // Do something with the WASM module...
|
|
18
38
|
* ```
|
|
19
39
|
*/
|
|
20
|
-
|
|
40
|
+
declare const loader: LoaderDefinitionFunction;
|
|
41
|
+
export default loader;
|
|
42
|
+
export declare const raw = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask/snaps-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A CLI for developing MetaMask Snaps.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@babel/plugin-transform-runtime": "^7.13.2",
|
|
55
55
|
"@babel/preset-env": "^7.23.2",
|
|
56
56
|
"@babel/preset-typescript": "^7.23.2",
|
|
57
|
-
"@metamask/snaps-sdk": "^1.
|
|
58
|
-
"@metamask/snaps-utils": "^5.
|
|
57
|
+
"@metamask/snaps-sdk": "^1.3.0",
|
|
58
|
+
"@metamask/snaps-utils": "^5.1.0",
|
|
59
59
|
"@metamask/snaps-webpack-plugin": "^3.1.1",
|
|
60
60
|
"@metamask/utils": "^8.2.1",
|
|
61
61
|
"@swc/core": "1.3.78",
|