@metamask/snaps-utils 0.24.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.
Files changed (78) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +3 -0
  3. package/dist/caveats.d.ts +22 -0
  4. package/dist/caveats.js +27 -0
  5. package/dist/caveats.js.map +1 -0
  6. package/dist/cronjob.d.ts +89 -0
  7. package/dist/cronjob.js +74 -0
  8. package/dist/cronjob.js.map +1 -0
  9. package/dist/deep-clone.d.ts +1 -0
  10. package/dist/deep-clone.js +9 -0
  11. package/dist/deep-clone.js.map +1 -0
  12. package/dist/default-endowments.d.ts +4 -0
  13. package/dist/default-endowments.js +43 -0
  14. package/dist/default-endowments.js.map +1 -0
  15. package/dist/eval-worker.d.ts +1 -0
  16. package/dist/eval-worker.js +32 -0
  17. package/dist/eval-worker.js.map +1 -0
  18. package/dist/eval.d.ts +8 -0
  19. package/dist/eval.js +28 -0
  20. package/dist/eval.js.map +1 -0
  21. package/dist/flatMap.d.ts +22 -0
  22. package/dist/flatMap.js +38 -0
  23. package/dist/flatMap.js.map +1 -0
  24. package/dist/fs.d.ts +69 -0
  25. package/dist/fs.js +144 -0
  26. package/dist/fs.js.map +1 -0
  27. package/dist/index.browser.d.ts +13 -0
  28. package/dist/index.browser.js +30 -0
  29. package/dist/index.browser.js.map +1 -0
  30. package/dist/index.d.ts +19 -0
  31. package/dist/index.js +36 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/json-rpc.d.ts +10 -0
  34. package/dist/json-rpc.js +22 -0
  35. package/dist/json-rpc.js.map +1 -0
  36. package/dist/manifest/index.browser.d.ts +1 -0
  37. package/dist/manifest/index.browser.js +18 -0
  38. package/dist/manifest/index.browser.js.map +1 -0
  39. package/dist/manifest/index.d.ts +2 -0
  40. package/dist/manifest/index.js +19 -0
  41. package/dist/manifest/index.js.map +1 -0
  42. package/dist/manifest/manifest.d.ts +75 -0
  43. package/dist/manifest/manifest.js +237 -0
  44. package/dist/manifest/manifest.js.map +1 -0
  45. package/dist/manifest/validation.d.ts +483 -0
  46. package/dist/manifest/validation.js +142 -0
  47. package/dist/manifest/validation.js.map +1 -0
  48. package/dist/mock.d.ts +14 -0
  49. package/dist/mock.js +107 -0
  50. package/dist/mock.js.map +1 -0
  51. package/dist/namespace.d.ts +275 -0
  52. package/dist/namespace.js +223 -0
  53. package/dist/namespace.js.map +1 -0
  54. package/dist/notification.d.ts +66 -0
  55. package/dist/notification.js +58 -0
  56. package/dist/notification.js.map +1 -0
  57. package/dist/npm.d.ts +20 -0
  58. package/dist/npm.js +73 -0
  59. package/dist/npm.js.map +1 -0
  60. package/dist/object.d.ts +8 -0
  61. package/dist/object.js +15 -0
  62. package/dist/object.js.map +1 -0
  63. package/dist/post-process.d.ts +71 -0
  64. package/dist/post-process.js +321 -0
  65. package/dist/post-process.js.map +1 -0
  66. package/dist/snaps.d.ts +165 -0
  67. package/dist/snaps.js +123 -0
  68. package/dist/snaps.js.map +1 -0
  69. package/dist/types.d.ts +107 -0
  70. package/dist/types.js +82 -0
  71. package/dist/types.js.map +1 -0
  72. package/dist/url.d.ts +7 -0
  73. package/dist/url.js +19 -0
  74. package/dist/url.js.map +1 -0
  75. package/dist/versions.d.ts +44 -0
  76. package/dist/versions.js +77 -0
  77. package/dist/versions.js.map +1 -0
  78. package/package.json +100 -0
@@ -0,0 +1,66 @@
1
+ import { Infer } from 'superstruct';
2
+ export declare const EventStruct: import("superstruct").Struct<{
3
+ name: string;
4
+ data: unknown;
5
+ }, {
6
+ name: import("superstruct").Struct<string, null>;
7
+ data: import("superstruct").Struct<unknown, null>;
8
+ }>;
9
+ export declare type Event = Infer<typeof EventStruct>;
10
+ /**
11
+ * Check if a value is a SIP-2 event.
12
+ *
13
+ * @param value - The value to check.
14
+ * @returns Whether the value is a SIP-2 event.
15
+ */
16
+ export declare function isEvent(value: unknown): value is Event;
17
+ /**
18
+ * Assert that a value is a SIP-2 event.
19
+ *
20
+ * @param value - The value to check.
21
+ * @throws If the value is not a SIP-2 event.
22
+ */
23
+ export declare function assertIsEvent(value: unknown): asserts value is Event;
24
+ export declare const MetaMaskNotificationStruct: import("superstruct").Struct<{
25
+ params: {
26
+ chainId: string;
27
+ event: {
28
+ name: string;
29
+ data: unknown;
30
+ };
31
+ };
32
+ method: "multichainHack_metamask_event";
33
+ }, {
34
+ method: import("superstruct").Struct<"multichainHack_metamask_event", "multichainHack_metamask_event">;
35
+ params: import("superstruct").Struct<{
36
+ chainId: string;
37
+ event: {
38
+ name: string;
39
+ data: unknown;
40
+ };
41
+ }, {
42
+ chainId: import("superstruct").Struct<string, null>;
43
+ event: import("superstruct").Struct<{
44
+ name: string;
45
+ data: unknown;
46
+ }, {
47
+ name: import("superstruct").Struct<string, null>;
48
+ data: import("superstruct").Struct<unknown, null>;
49
+ }>;
50
+ }>;
51
+ }>;
52
+ export declare type MetaMaskNotification = Infer<typeof MetaMaskNotificationStruct>;
53
+ /**
54
+ * Check if a value is a SIP-2 notification.
55
+ *
56
+ * @param value - The value to check.
57
+ * @returns Whether the value is a SIP-2 notification.
58
+ */
59
+ export declare function isMetaMaskNotification(value: unknown): value is MetaMaskNotification;
60
+ /**
61
+ * Assert that a value is a SIP-2 notification.
62
+ *
63
+ * @param value - The value to check.
64
+ * @throws If the value is not a SIP-2 notification.
65
+ */
66
+ export declare function assertIsMetaMaskNotification(value: unknown): asserts value is MetaMaskNotification;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertIsMetaMaskNotification = exports.isMetaMaskNotification = exports.MetaMaskNotificationStruct = exports.assertIsEvent = exports.isEvent = exports.EventStruct = void 0;
4
+ const superstruct_1 = require("superstruct");
5
+ const utils_1 = require("@metamask/utils");
6
+ const namespace_1 = require("./namespace");
7
+ exports.EventStruct = (0, superstruct_1.object)({
8
+ name: (0, superstruct_1.string)(),
9
+ data: (0, superstruct_1.unknown)(),
10
+ });
11
+ /**
12
+ * Check if a value is a SIP-2 event.
13
+ *
14
+ * @param value - The value to check.
15
+ * @returns Whether the value is a SIP-2 event.
16
+ */
17
+ function isEvent(value) {
18
+ return (0, superstruct_1.is)(value, exports.EventStruct);
19
+ }
20
+ exports.isEvent = isEvent;
21
+ /**
22
+ * Assert that a value is a SIP-2 event.
23
+ *
24
+ * @param value - The value to check.
25
+ * @throws If the value is not a SIP-2 event.
26
+ */
27
+ function assertIsEvent(value) {
28
+ (0, utils_1.assertStruct)(value, exports.EventStruct, 'Invalid event');
29
+ }
30
+ exports.assertIsEvent = assertIsEvent;
31
+ exports.MetaMaskNotificationStruct = (0, superstruct_1.object)({
32
+ method: (0, superstruct_1.literal)('multichainHack_metamask_event'),
33
+ params: (0, superstruct_1.object)({
34
+ chainId: namespace_1.ChainIdStruct,
35
+ event: exports.EventStruct,
36
+ }),
37
+ });
38
+ /**
39
+ * Check if a value is a SIP-2 notification.
40
+ *
41
+ * @param value - The value to check.
42
+ * @returns Whether the value is a SIP-2 notification.
43
+ */
44
+ function isMetaMaskNotification(value) {
45
+ return (0, superstruct_1.is)(value, exports.MetaMaskNotificationStruct);
46
+ }
47
+ exports.isMetaMaskNotification = isMetaMaskNotification;
48
+ /**
49
+ * Assert that a value is a SIP-2 notification.
50
+ *
51
+ * @param value - The value to check.
52
+ * @throws If the value is not a SIP-2 notification.
53
+ */
54
+ function assertIsMetaMaskNotification(value) {
55
+ (0, utils_1.assertStruct)(value, exports.MetaMaskNotificationStruct, 'Invalid notification');
56
+ }
57
+ exports.assertIsMetaMaskNotification = assertIsMetaMaskNotification;
58
+ //# sourceMappingURL=notification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.js","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":";;;AAAA,6CAA0E;AAC1E,2CAA+C;AAC/C,2CAA4C;AAE/B,QAAA,WAAW,GAAG,IAAA,oBAAM,EAAC;IAChC,IAAI,EAAE,IAAA,oBAAM,GAAE;IACd,IAAI,EAAE,IAAA,qBAAO,GAAE;CAChB,CAAC,CAAC;AAIH;;;;;GAKG;AACH,SAAgB,OAAO,CAAC,KAAc;IACpC,OAAO,IAAA,gBAAE,EAAC,KAAK,EAAE,mBAAW,CAAC,CAAC;AAChC,CAAC;AAFD,0BAEC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,KAAc;IAC1C,IAAA,oBAAY,EAAC,KAAK,EAAE,mBAAW,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAFD,sCAEC;AAEY,QAAA,0BAA0B,GAAG,IAAA,oBAAM,EAAC;IAC/C,MAAM,EAAE,IAAA,qBAAO,EAAC,+BAA+B,CAAC;IAChD,MAAM,EAAE,IAAA,oBAAM,EAAC;QACb,OAAO,EAAE,yBAAa;QACtB,KAAK,EAAE,mBAAW;KACnB,CAAC;CACH,CAAC,CAAC;AAIH;;;;;GAKG;AACH,SAAgB,sBAAsB,CACpC,KAAc;IAEd,OAAO,IAAA,gBAAE,EAAC,KAAK,EAAE,kCAA0B,CAAC,CAAC;AAC/C,CAAC;AAJD,wDAIC;AAED;;;;;GAKG;AACH,SAAgB,4BAA4B,CAC1C,KAAc;IAEd,IAAA,oBAAY,EAAC,KAAK,EAAE,kCAA0B,EAAE,sBAAsB,CAAC,CAAC;AAC1E,CAAC;AAJD,oEAIC","sourcesContent":["import { Infer, is, literal, object, string, unknown } from 'superstruct';\nimport { assertStruct } from '@metamask/utils';\nimport { ChainIdStruct } from './namespace';\n\nexport const EventStruct = object({\n name: string(),\n data: unknown(),\n});\n\nexport type Event = Infer<typeof EventStruct>;\n\n/**\n * Check if a value is a SIP-2 event.\n *\n * @param value - The value to check.\n * @returns Whether the value is a SIP-2 event.\n */\nexport function isEvent(value: unknown): value is Event {\n return is(value, EventStruct);\n}\n\n/**\n * Assert that a value is a SIP-2 event.\n *\n * @param value - The value to check.\n * @throws If the value is not a SIP-2 event.\n */\nexport function assertIsEvent(value: unknown): asserts value is Event {\n assertStruct(value, EventStruct, 'Invalid event');\n}\n\nexport const MetaMaskNotificationStruct = object({\n method: literal('multichainHack_metamask_event'),\n params: object({\n chainId: ChainIdStruct,\n event: EventStruct,\n }),\n});\n\nexport type MetaMaskNotification = Infer<typeof MetaMaskNotificationStruct>;\n\n/**\n * Check if a value is a SIP-2 notification.\n *\n * @param value - The value to check.\n * @returns Whether the value is a SIP-2 notification.\n */\nexport function isMetaMaskNotification(\n value: unknown,\n): value is MetaMaskNotification {\n return is(value, MetaMaskNotificationStruct);\n}\n\n/**\n * Assert that a value is a SIP-2 notification.\n *\n * @param value - The value to check.\n * @throws If the value is not a SIP-2 notification.\n */\nexport function assertIsMetaMaskNotification(\n value: unknown,\n): asserts value is MetaMaskNotification {\n assertStruct(value, MetaMaskNotificationStruct, 'Invalid notification');\n}\n"]}
package/dist/npm.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { NpmSnapFileNames, SnapFiles, UnvalidatedSnapFiles } from './types';
2
+ export declare const SVG_MAX_BYTE_SIZE = 100000;
3
+ export declare const SVG_MAX_BYTE_SIZE_TEXT: string;
4
+ export declare const EXPECTED_SNAP_FILES: readonly ["manifest", "packageJson", "sourceCode"];
5
+ export declare const SnapFileNameFromKey: {
6
+ readonly manifest: NpmSnapFileNames.Manifest;
7
+ readonly packageJson: NpmSnapFileNames.PackageJson;
8
+ readonly sourceCode: "source code bundle";
9
+ };
10
+ /**
11
+ * Validates the files extracted from an npm Snap package tarball by ensuring
12
+ * that they're non-empty and that the Json files match their respective schemas
13
+ * and the Snaps publishing specification.
14
+ *
15
+ * @param snapFiles - The object containing the expected Snap file contents,
16
+ * if any.
17
+ * @param errorPrefix - The prefix of the error message.
18
+ * @returns A tuple of the Snap manifest object and the Snap source code.
19
+ */
20
+ export declare function validateNpmSnap(snapFiles: UnvalidatedSnapFiles, errorPrefix?: `${string}: `): SnapFiles;
package/dist/npm.js ADDED
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateNpmSnap = exports.SnapFileNameFromKey = exports.EXPECTED_SNAP_FILES = exports.SVG_MAX_BYTE_SIZE_TEXT = exports.SVG_MAX_BYTE_SIZE = void 0;
4
+ const manifest_1 = require("./manifest/manifest");
5
+ const validation_1 = require("./manifest/validation");
6
+ const types_1 = require("./types");
7
+ exports.SVG_MAX_BYTE_SIZE = 100000;
8
+ exports.SVG_MAX_BYTE_SIZE_TEXT = `${Math.floor(exports.SVG_MAX_BYTE_SIZE / 1000)}kb`;
9
+ exports.EXPECTED_SNAP_FILES = [
10
+ 'manifest',
11
+ 'packageJson',
12
+ 'sourceCode',
13
+ ];
14
+ exports.SnapFileNameFromKey = {
15
+ manifest: types_1.NpmSnapFileNames.Manifest,
16
+ packageJson: types_1.NpmSnapFileNames.PackageJson,
17
+ sourceCode: 'source code bundle',
18
+ };
19
+ /**
20
+ * Validates the files extracted from an npm Snap package tarball by ensuring
21
+ * that they're non-empty and that the Json files match their respective schemas
22
+ * and the Snaps publishing specification.
23
+ *
24
+ * @param snapFiles - The object containing the expected Snap file contents,
25
+ * if any.
26
+ * @param errorPrefix - The prefix of the error message.
27
+ * @returns A tuple of the Snap manifest object and the Snap source code.
28
+ */
29
+ function validateNpmSnap(snapFiles, errorPrefix) {
30
+ exports.EXPECTED_SNAP_FILES.forEach((key) => {
31
+ if (!snapFiles[key]) {
32
+ throw new Error(`${errorPrefix !== null && errorPrefix !== void 0 ? errorPrefix : ''}Missing file "${exports.SnapFileNameFromKey[key]}".`);
33
+ }
34
+ });
35
+ // Typecast: We are assured that the required files exist if we get here.
36
+ const { manifest, packageJson, sourceCode, svgIcon } = snapFiles;
37
+ try {
38
+ (0, validation_1.assertIsSnapManifest)(manifest);
39
+ }
40
+ catch (error) {
41
+ throw new Error(`${errorPrefix !== null && errorPrefix !== void 0 ? errorPrefix : ''}${error.message}`);
42
+ }
43
+ const validatedManifest = manifest;
44
+ const { iconPath } = validatedManifest.source.location.npm;
45
+ if (iconPath && !svgIcon) {
46
+ throw new Error(`Missing file "${iconPath}".`);
47
+ }
48
+ try {
49
+ (0, types_1.assertIsNpmSnapPackageJson)(packageJson);
50
+ }
51
+ catch (error) {
52
+ throw new Error(`${errorPrefix !== null && errorPrefix !== void 0 ? errorPrefix : ''}${error.message}`);
53
+ }
54
+ const validatedPackageJson = packageJson;
55
+ (0, manifest_1.validateNpmSnapManifest)({
56
+ manifest: validatedManifest,
57
+ packageJson: validatedPackageJson,
58
+ sourceCode,
59
+ });
60
+ if (svgIcon) {
61
+ if (Buffer.byteLength(svgIcon, 'utf8') > exports.SVG_MAX_BYTE_SIZE) {
62
+ throw new Error(`${errorPrefix !== null && errorPrefix !== void 0 ? errorPrefix : ''}The specified SVG icon exceeds the maximum size of ${exports.SVG_MAX_BYTE_SIZE_TEXT}.`);
63
+ }
64
+ }
65
+ return {
66
+ manifest: validatedManifest,
67
+ packageJson: validatedPackageJson,
68
+ sourceCode,
69
+ svgIcon,
70
+ };
71
+ }
72
+ exports.validateNpmSnap = validateNpmSnap;
73
+ //# sourceMappingURL=npm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm.js","sourceRoot":"","sources":["../src/npm.ts"],"names":[],"mappings":";;;AAAA,kDAA8D;AAC9D,sDAA2E;AAC3E,mCAMiB;AAEJ,QAAA,iBAAiB,GAAG,MAAO,CAAC;AAC5B,QAAA,sBAAsB,GAAG,GAAG,IAAI,CAAC,KAAK,CACjD,yBAAiB,GAAG,IAAI,CACzB,IAAI,CAAC;AAEO,QAAA,mBAAmB,GAAG;IACjC,UAAU;IACV,aAAa;IACb,YAAY;CACJ,CAAC;AAEE,QAAA,mBAAmB,GAAG;IACjC,QAAQ,EAAE,wBAAgB,CAAC,QAAQ;IACnC,WAAW,EAAE,wBAAgB,CAAC,WAAW;IACzC,UAAU,EAAE,oBAAoB;CACxB,CAAC;AAEX;;;;;;;;;GASG;AACH,SAAgB,eAAe,CAC7B,SAA+B,EAC/B,WAA2B;IAE3B,2BAAmB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAClC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE;YACnB,MAAM,IAAI,KAAK,CACb,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EAAE,iBAAiB,2BAAmB,CAAC,GAAG,CAAC,IAAI,CAClE,CAAC;SACH;IACH,CAAC,CAAC,CAAC;IAEH,yEAAyE;IACzE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,SAAsB,CAAC;IAC9E,IAAI;QACF,IAAA,iCAAoB,EAAC,QAAQ,CAAC,CAAC;KAChC;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,IAAI,KAAK,CAAC,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;KACzD;IACD,MAAM,iBAAiB,GAAG,QAAwB,CAAC;IAEnD,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;IAC3D,IAAI,QAAQ,IAAI,CAAC,OAAO,EAAE;QACxB,MAAM,IAAI,KAAK,CAAC,iBAAiB,QAAQ,IAAI,CAAC,CAAC;KAChD;IAED,IAAI;QACF,IAAA,kCAA0B,EAAC,WAAW,CAAC,CAAC;KACzC;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,IAAI,KAAK,CAAC,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;KACzD;IACD,MAAM,oBAAoB,GAAG,WAAiC,CAAC;IAE/D,IAAA,kCAAuB,EAAC;QACtB,QAAQ,EAAE,iBAAiB;QAC3B,WAAW,EAAE,oBAAoB;QACjC,UAAU;KACX,CAAC,CAAC;IAEH,IAAI,OAAO,EAAE;QACX,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,yBAAiB,EAAE;YAC1D,MAAM,IAAI,KAAK,CACb,GACE,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EACjB,sDAAsD,8BAAsB,GAAG,CAChF,CAAC;SACH;KACF;IAED,OAAO;QACL,QAAQ,EAAE,iBAAiB;QAC3B,WAAW,EAAE,oBAAoB;QACjC,UAAU;QACV,OAAO;KACR,CAAC;AACJ,CAAC;AAvDD,0CAuDC","sourcesContent":["import { validateNpmSnapManifest } from './manifest/manifest';\nimport { assertIsSnapManifest, SnapManifest } from './manifest/validation';\nimport {\n assertIsNpmSnapPackageJson,\n NpmSnapFileNames,\n NpmSnapPackageJson,\n SnapFiles,\n UnvalidatedSnapFiles,\n} from './types';\n\nexport const SVG_MAX_BYTE_SIZE = 100_000;\nexport const SVG_MAX_BYTE_SIZE_TEXT = `${Math.floor(\n SVG_MAX_BYTE_SIZE / 1000,\n)}kb`;\n\nexport const EXPECTED_SNAP_FILES = [\n 'manifest',\n 'packageJson',\n 'sourceCode',\n] as const;\n\nexport const SnapFileNameFromKey = {\n manifest: NpmSnapFileNames.Manifest,\n packageJson: NpmSnapFileNames.PackageJson,\n sourceCode: 'source code bundle',\n} as const;\n\n/**\n * Validates the files extracted from an npm Snap package tarball by ensuring\n * that they're non-empty and that the Json files match their respective schemas\n * and the Snaps publishing specification.\n *\n * @param snapFiles - The object containing the expected Snap file contents,\n * if any.\n * @param errorPrefix - The prefix of the error message.\n * @returns A tuple of the Snap manifest object and the Snap source code.\n */\nexport function validateNpmSnap(\n snapFiles: UnvalidatedSnapFiles,\n errorPrefix?: `${string}: `,\n): SnapFiles {\n EXPECTED_SNAP_FILES.forEach((key) => {\n if (!snapFiles[key]) {\n throw new Error(\n `${errorPrefix ?? ''}Missing file \"${SnapFileNameFromKey[key]}\".`,\n );\n }\n });\n\n // Typecast: We are assured that the required files exist if we get here.\n const { manifest, packageJson, sourceCode, svgIcon } = snapFiles as SnapFiles;\n try {\n assertIsSnapManifest(manifest);\n } catch (error) {\n throw new Error(`${errorPrefix ?? ''}${error.message}`);\n }\n const validatedManifest = manifest as SnapManifest;\n\n const { iconPath } = validatedManifest.source.location.npm;\n if (iconPath && !svgIcon) {\n throw new Error(`Missing file \"${iconPath}\".`);\n }\n\n try {\n assertIsNpmSnapPackageJson(packageJson);\n } catch (error) {\n throw new Error(`${errorPrefix ?? ''}${error.message}`);\n }\n const validatedPackageJson = packageJson as NpmSnapPackageJson;\n\n validateNpmSnapManifest({\n manifest: validatedManifest,\n packageJson: validatedPackageJson,\n sourceCode,\n });\n\n if (svgIcon) {\n if (Buffer.byteLength(svgIcon, 'utf8') > SVG_MAX_BYTE_SIZE) {\n throw new Error(\n `${\n errorPrefix ?? ''\n }The specified SVG icon exceeds the maximum size of ${SVG_MAX_BYTE_SIZE_TEXT}.`,\n );\n }\n }\n\n return {\n manifest: validatedManifest,\n packageJson: validatedPackageJson,\n sourceCode,\n svgIcon,\n };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * An alternative implementation of `Object.fromEntries`, which works in older
3
+ * browsers.
4
+ *
5
+ * @param entries - The entries to convert to an object.
6
+ * @returns The object.
7
+ */
8
+ export declare const fromEntries: <Key extends string, Value>(entries: readonly (readonly [Key, Value])[]) => Record<Key, Value>;
package/dist/object.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fromEntries = void 0;
4
+ /**
5
+ * An alternative implementation of `Object.fromEntries`, which works in older
6
+ * browsers.
7
+ *
8
+ * @param entries - The entries to convert to an object.
9
+ * @returns The object.
10
+ */
11
+ const fromEntries = (entries) => {
12
+ return entries.reduce((acc, [key, value]) => (Object.assign(Object.assign({}, acc), { [key]: value })), {});
13
+ };
14
+ exports.fromEntries = fromEntries;
15
+ //# sourceMappingURL=object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object.js","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACI,MAAM,WAAW,GAAG,CACzB,OAA2C,EACvB,EAAE;IACtB,OAAO,OAAO,CAAC,MAAM,CACnB,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,iCAAM,GAAG,KAAE,CAAC,GAAG,CAAC,EAAE,KAAK,IAAG,EACjD,EAAwB,CACzB,CAAC;AACJ,CAAC,CAAC;AAPW,QAAA,WAAW,eAOtB","sourcesContent":["/**\n * An alternative implementation of `Object.fromEntries`, which works in older\n * browsers.\n *\n * @param entries - The entries to convert to an object.\n * @returns The object.\n */\nexport const fromEntries = <Key extends string, Value>(\n entries: readonly (readonly [Key, Value])[],\n): Record<Key, Value> => {\n return entries.reduce<Record<Key, Value>>(\n (acc, [key, value]) => ({ ...acc, [key]: value }),\n {} as Record<Key, Value>,\n );\n};\n"]}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Source map declaration taken from `@babel/core`. Babel doesn't export the
3
+ * type for this, so it's copied from the source code instead here.
4
+ */
5
+ export declare type SourceMap = {
6
+ version: number;
7
+ sources: string[];
8
+ names: string[];
9
+ sourceRoot?: string | undefined;
10
+ sourcesContent?: string[] | undefined;
11
+ mappings: string;
12
+ file: string;
13
+ };
14
+ /**
15
+ * The post process options.
16
+ *
17
+ * @property stripComments - Whether to strip comments. Defaults to `true`.
18
+ * @property sourceMap - Whether to generate a source map for the modified code.
19
+ * See also `inputSourceMap`.
20
+ * @property inputSourceMap - The source map for the input code. When provided,
21
+ * the source map will be used to generate a source map for the modified code.
22
+ * This ensures that the source map is correct for the modified code, and still
23
+ * points to the original source. If not provided, a new source map will be
24
+ * generated instead.
25
+ */
26
+ export declare type PostProcessOptions = {
27
+ stripComments?: boolean;
28
+ sourceMap?: boolean | 'inline';
29
+ inputSourceMap?: SourceMap;
30
+ };
31
+ /**
32
+ * The post processed bundle output.
33
+ *
34
+ * @property code - The modified code.
35
+ * @property sourceMap - The source map for the modified code, if the source map
36
+ * option was enabled.
37
+ * @property warnings - Any warnings that occurred during the post-processing.
38
+ */
39
+ export declare type PostProcessedBundle = {
40
+ code: string;
41
+ sourceMap?: SourceMap | null;
42
+ warnings: PostProcessWarning[];
43
+ };
44
+ export declare enum PostProcessWarning {
45
+ UnsafeMathRandom = "`Math.random` was detected in the bundle. This is not a secure source of randomness."
46
+ }
47
+ /**
48
+ * Post process code with AST such that it can be evaluated in SES.
49
+ *
50
+ * Currently:
51
+ * - Makes all direct calls to eval indirect.
52
+ * - Handles certain Babel-related edge cases.
53
+ * - Removes the `Buffer` provided by Browserify.
54
+ * - Optionally removes comments.
55
+ * - Breaks up tokens that would otherwise result in SES errors, such as HTML
56
+ * comment tags `<!--` and `-->` and `import(n)` statements.
57
+ *
58
+ * @param code - The code to post process.
59
+ * @param options - The post-process options.
60
+ * @param options.stripComments - Whether to strip comments. Defaults to `true`.
61
+ * @param options.sourceMap - Whether to generate a source map for the modified
62
+ * code. See also `inputSourceMap`.
63
+ * @param options.inputSourceMap - The source map for the input code. When
64
+ * provided, the source map will be used to generate a source map for the
65
+ * modified code. This ensures that the source map is correct for the modified
66
+ * code, and still points to the original source. If not provided, a new source
67
+ * map will be generated instead.
68
+ * @returns An object containing the modified code, and source map, or null if
69
+ * the provided code is null.
70
+ */
71
+ export declare function postProcessBundle(code: string, { stripComments, sourceMap: sourceMaps, inputSourceMap, }?: Partial<PostProcessOptions>): PostProcessedBundle;
@@ -0,0 +1,321 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.postProcessBundle = exports.PostProcessWarning = void 0;
4
+ const types_1 = require("@babel/types");
5
+ const core_1 = require("@babel/core");
6
+ var PostProcessWarning;
7
+ (function (PostProcessWarning) {
8
+ PostProcessWarning["UnsafeMathRandom"] = "`Math.random` was detected in the bundle. This is not a secure source of randomness.";
9
+ })(PostProcessWarning = exports.PostProcessWarning || (exports.PostProcessWarning = {}));
10
+ // The RegEx below consists of multiple groups joined by a boolean OR.
11
+ // Each part consists of two groups which capture a part of each string
12
+ // which needs to be split up, e.g., `<!--` is split into `<!` and `--`.
13
+ const TOKEN_REGEX = /(<!)(--)|(--)(>)|(import)(\(.*?\))/gu;
14
+ // An empty template element, i.e., a part of a template literal without any
15
+ // value ("").
16
+ const EMPTY_TEMPLATE_ELEMENT = (0, types_1.templateElement)({ raw: '', cooked: '' });
17
+ const evalWrapper = core_1.template.statement(`
18
+ (1, REF)(ARGS)
19
+ `);
20
+ const objectEvalWrapper = core_1.template.statement(`
21
+ (1, OBJECT.REF)
22
+ `);
23
+ const regeneratorRuntimeWrapper = core_1.template.statement(`
24
+ var regeneratorRuntime;
25
+ `);
26
+ /**
27
+ * Breaks up tokens that would otherwise result in SES errors. The tokens are
28
+ * broken up in a non-destructive way where possible. Currently works with:
29
+ * - HTML comment tags `<!--` and `-->`, broken up into `<!`, `--`, and `--`,
30
+ * `>`.
31
+ * - `import(n)` statements, broken up into `import`, `(n)`.
32
+ *
33
+ * @param value - The string value to break up.
34
+ * @returns The string split into an array, in a way that it can be joined
35
+ * together to form the same string, but with the tokens separated into single
36
+ * array elements.
37
+ */
38
+ function breakTokens(value) {
39
+ const tokens = value.split(TOKEN_REGEX);
40
+ return (tokens
41
+ // TODO: The `split` above results in some values being `undefined`.
42
+ // There may be a better solution to avoid having to filter those out.
43
+ .filter((token) => token !== '' && token !== undefined));
44
+ }
45
+ /**
46
+ * Breaks up tokens that would otherwise result in SES errors. The tokens are
47
+ * broken up in a non-destructive way where possible. Currently works with:
48
+ * - HTML comment tags `<!--` and `-->`, broken up into `<!`, `--`, and `--`,
49
+ * `>`.
50
+ * - `import(n)` statements, broken up into `import`, `(n)`.
51
+ *
52
+ * @param value - The string value to break up.
53
+ * @returns The string split into a tuple consisting of the new template
54
+ * elements and string literal expressions.
55
+ */
56
+ function breakTokensTemplateLiteral(value) {
57
+ // @ts-expect-error `matchAll` is not available in ES2017, but this code
58
+ // should only be used in environments where the function is supported.
59
+ const matches = Array.from(value.matchAll(TOKEN_REGEX));
60
+ if (matches.length > 0) {
61
+ const output = matches.reduce(([elements, expressions], rawMatch, index, values) => {
62
+ const [, first, last] = rawMatch.filter((raw) => raw !== undefined);
63
+ // Slice the text in front of the match, which does not need to be
64
+ // broken up.
65
+ const prefix = value.slice(index === 0
66
+ ? 0
67
+ : values[index - 1].index + values[index - 1][0].length, rawMatch.index);
68
+ return [
69
+ [
70
+ ...elements,
71
+ (0, types_1.templateElement)({
72
+ raw: getRawTemplateValue(prefix),
73
+ cooked: prefix,
74
+ }),
75
+ EMPTY_TEMPLATE_ELEMENT,
76
+ ],
77
+ [...expressions, (0, types_1.stringLiteral)(first), (0, types_1.stringLiteral)(last)],
78
+ ];
79
+ }, [[], []]);
80
+ // Add the text after the last match to the output.
81
+ const lastMatch = matches[matches.length - 1];
82
+ const suffix = value.slice(lastMatch.index + lastMatch[0].length);
83
+ return [
84
+ [
85
+ ...output[0],
86
+ (0, types_1.templateElement)({ raw: getRawTemplateValue(suffix), cooked: suffix }),
87
+ ],
88
+ output[1],
89
+ ];
90
+ }
91
+ // If there are no matches, simply return the original value.
92
+ return [
93
+ [(0, types_1.templateElement)({ raw: getRawTemplateValue(value), cooked: value })],
94
+ [],
95
+ ];
96
+ }
97
+ /**
98
+ * Get a raw template literal value from a cooked value. This adds a backslash
99
+ * before every '`', '\' and '${' characters.
100
+ *
101
+ * @see https://github.com/babel/babel/issues/9242#issuecomment-532529613
102
+ * @param value - The cooked string to get the raw string for.
103
+ * @returns The value as raw value.
104
+ */
105
+ function getRawTemplateValue(value) {
106
+ return value.replace(/\\|`|\$\{/gu, '\\$&');
107
+ }
108
+ /**
109
+ * Post process code with AST such that it can be evaluated in SES.
110
+ *
111
+ * Currently:
112
+ * - Makes all direct calls to eval indirect.
113
+ * - Handles certain Babel-related edge cases.
114
+ * - Removes the `Buffer` provided by Browserify.
115
+ * - Optionally removes comments.
116
+ * - Breaks up tokens that would otherwise result in SES errors, such as HTML
117
+ * comment tags `<!--` and `-->` and `import(n)` statements.
118
+ *
119
+ * @param code - The code to post process.
120
+ * @param options - The post-process options.
121
+ * @param options.stripComments - Whether to strip comments. Defaults to `true`.
122
+ * @param options.sourceMap - Whether to generate a source map for the modified
123
+ * code. See also `inputSourceMap`.
124
+ * @param options.inputSourceMap - The source map for the input code. When
125
+ * provided, the source map will be used to generate a source map for the
126
+ * modified code. This ensures that the source map is correct for the modified
127
+ * code, and still points to the original source. If not provided, a new source
128
+ * map will be generated instead.
129
+ * @returns An object containing the modified code, and source map, or null if
130
+ * the provided code is null.
131
+ */
132
+ function postProcessBundle(code, { stripComments = true, sourceMap: sourceMaps, inputSourceMap, } = {}) {
133
+ const warnings = new Set();
134
+ const pre = ({ ast }) => {
135
+ var _a;
136
+ (_a = ast.comments) === null || _a === void 0 ? void 0 : _a.forEach((comment) => {
137
+ // Break up tokens that could be parsed as HTML comment terminators. The
138
+ // regular expressions below are written strangely so as to avoid the
139
+ // appearance of such tokens in our source code. For reference:
140
+ // https://github.com/endojs/endo/blob/70cc86eb400655e922413b99c38818d7b2e79da0/packages/ses/error-codes/SES_HTML_COMMENT_REJECTED.md
141
+ comment.value = comment.value
142
+ .replace(new RegExp(`<!${'--'}`, 'gu'), '< !--')
143
+ .replace(new RegExp(`${'--'}>`, 'gu'), '-- >')
144
+ .replace(/import(\(.*\))/gu, 'import\\$1');
145
+ });
146
+ };
147
+ const visitor = {
148
+ FunctionExpression(path) {
149
+ var _a;
150
+ const { node } = path;
151
+ // Browserify provides the `Buffer` global as an argument to modules that
152
+ // use it, but this does not work in SES. Since we pass in `Buffer` as an
153
+ // endowment, we can simply remove the argument.
154
+ //
155
+ // Note that this only removes `Buffer` from a wrapped function
156
+ // expression, e.g., `(function (Buffer) { ... })`. Regular functions
157
+ // are not affected.
158
+ //
159
+ // TODO: Since we're working on the AST level, we could check the scope
160
+ // of the function expression, and possibly prevent false positives?
161
+ if (node.type === 'FunctionExpression' && ((_a = node.extra) === null || _a === void 0 ? void 0 : _a.parenthesized)) {
162
+ node.params = node.params.filter((param) => !(param.type === 'Identifier' && param.name === 'Buffer'));
163
+ }
164
+ },
165
+ CallExpression(path) {
166
+ const { node } = path;
167
+ // Replace `eval(foo)` with `(1, eval)(foo)`.
168
+ if (node.callee.type === 'Identifier' && node.callee.name === 'eval') {
169
+ path.replaceWith(evalWrapper({
170
+ REF: node.callee,
171
+ ARGS: node.arguments,
172
+ }));
173
+ }
174
+ // Detect the use of `Math.random()` and add a warning.
175
+ if (node.callee.type === 'MemberExpression' &&
176
+ node.callee.object.type === 'Identifier' &&
177
+ node.callee.object.name === 'Math' &&
178
+ node.callee.property.type === 'Identifier' &&
179
+ node.callee.property.name === 'random') {
180
+ warnings.add(PostProcessWarning.UnsafeMathRandom);
181
+ }
182
+ },
183
+ MemberExpression(path) {
184
+ const { node } = path;
185
+ // Replace `object.eval(foo)` with `(1, object.eval)(foo)`.
186
+ if (node.property.type === 'Identifier' &&
187
+ node.property.name === 'eval' &&
188
+ // If the expression is already wrapped we can ignore it
189
+ path.parent.type !== 'SequenceExpression') {
190
+ path.replaceWith(objectEvalWrapper({
191
+ OBJECT: node.object,
192
+ REF: node.property,
193
+ }));
194
+ }
195
+ },
196
+ Identifier(path) {
197
+ const { node } = path;
198
+ // Insert `regeneratorRuntime` global if it's used in the code.
199
+ if (node.name === 'regeneratorRuntime') {
200
+ const program = path.findParent((parent) => parent.node.type === 'Program');
201
+ // We know that `program` is a Program node here, but this keeps
202
+ // TypeScript happy.
203
+ if ((program === null || program === void 0 ? void 0 : program.node.type) === 'Program') {
204
+ const body = program.node.body[0];
205
+ // This stops it from inserting `regeneratorRuntime` multiple times.
206
+ if (body.type === 'VariableDeclaration' &&
207
+ body.declarations[0].id.name ===
208
+ 'regeneratorRuntime') {
209
+ return;
210
+ }
211
+ program === null || program === void 0 ? void 0 : program.node.body.unshift(regeneratorRuntimeWrapper());
212
+ }
213
+ }
214
+ },
215
+ TemplateLiteral(path) {
216
+ const { node } = path;
217
+ // This checks if the template literal was visited before. Without this,
218
+ // it would cause an infinite loop resulting in a stack overflow. We can't
219
+ // skip the path here, because we need to visit the children of the node.
220
+ if (path.getData('visited')) {
221
+ return;
222
+ }
223
+ // Break up tokens that could be parsed as HTML comment terminators, or
224
+ // `import()` statements.
225
+ // For reference:
226
+ // - https://github.com/endojs/endo/blob/70cc86eb400655e922413b99c38818d7b2e79da0/packages/ses/error-codes/SES_HTML_COMMENT_REJECTED.md
227
+ // - https://github.com/MetaMask/snaps-monorepo/issues/505
228
+ const [replacementQuasis, replacementExpressions] = node.quasis.reduce(([elements, expressions], quasi, index) => {
229
+ // Note: Template literals have two variants, "cooked" and "raw". Here
230
+ // we use the cooked version.
231
+ // https://exploringjs.com/impatient-js/ch_template-literals.html#template-strings-cooked-vs-raw
232
+ const tokens = breakTokensTemplateLiteral(quasi.value.cooked);
233
+ // Only update the node if something changed.
234
+ if (tokens[0].length <= 1) {
235
+ return [
236
+ [...elements, quasi],
237
+ [...expressions, node.expressions[index]],
238
+ ];
239
+ }
240
+ return [
241
+ [...elements, ...tokens[0]],
242
+ [
243
+ ...expressions,
244
+ ...tokens[1],
245
+ node.expressions[index],
246
+ ],
247
+ ];
248
+ }, [[], []]);
249
+ path.replaceWith((0, types_1.templateLiteral)(replacementQuasis, replacementExpressions.filter((e) => e !== undefined)));
250
+ path.setData('visited', true);
251
+ },
252
+ StringLiteral(path) {
253
+ const { node } = path;
254
+ // Break up tokens that could be parsed as HTML comment terminators, or
255
+ // `import()` statements.
256
+ // For reference:
257
+ // - https://github.com/endojs/endo/blob/70cc86eb400655e922413b99c38818d7b2e79da0/packages/ses/error-codes/SES_HTML_COMMENT_REJECTED.md
258
+ // - https://github.com/MetaMask/snaps-monorepo/issues/505
259
+ const tokens = breakTokens(node.value);
260
+ // Only update the node if the string literal was broken up.
261
+ if (tokens.length <= 1) {
262
+ return;
263
+ }
264
+ const replacement = tokens
265
+ .slice(1)
266
+ .reduce((acc, value) => (0, types_1.binaryExpression)('+', acc, (0, types_1.stringLiteral)(value)), (0, types_1.stringLiteral)(tokens[0]));
267
+ path.replaceWith(replacement);
268
+ path.skip();
269
+ },
270
+ BinaryExpression(path) {
271
+ const source = path.getSource();
272
+ // Throw an error if HTML comments are used as a binary expression.
273
+ if (source.includes('<!--') || source.includes('-->')) {
274
+ throw new Error('Using HTML comments (`<!--` and `-->`) as operators is not allowed. The behaviour of ' +
275
+ 'these comments is ambiguous, and differs per browser and environment. If you want ' +
276
+ 'to use them as operators, break them up into separate characters, i.e., `a-- > b` ' +
277
+ 'and `a < ! --b`.');
278
+ }
279
+ },
280
+ };
281
+ try {
282
+ const file = (0, core_1.transformSync)(code, {
283
+ // Prevent Babel from searching for a config file.
284
+ configFile: false,
285
+ parserOpts: {
286
+ // Strict mode isn't enabled by default, so we need to enable it here.
287
+ strictMode: true,
288
+ // If this is disabled, the AST does not include any comments. This is
289
+ // useful for performance reasons, and we use it for stripping comments.
290
+ attachComment: !stripComments,
291
+ },
292
+ // By default, Babel optimises bundles that exceed 500 KB, but that
293
+ // results in characters which look like HTML comments, which breaks SES.
294
+ compact: false,
295
+ // This configures Babel to generate a new source map from the existing
296
+ // source map if specified. If `sourceMap` is `true` but an input source
297
+ // map is not provided, a new source map will be generated instead.
298
+ inputSourceMap,
299
+ sourceMaps,
300
+ plugins: [
301
+ () => ({
302
+ pre,
303
+ visitor,
304
+ }),
305
+ ],
306
+ });
307
+ if (!(file === null || file === void 0 ? void 0 : file.code)) {
308
+ throw new Error('Bundled code is empty.');
309
+ }
310
+ return {
311
+ code: file.code,
312
+ sourceMap: file.map,
313
+ warnings: Array.from(warnings),
314
+ };
315
+ }
316
+ catch (error) {
317
+ throw new Error(`Failed to post process code:\n${error.message}`);
318
+ }
319
+ }
320
+ exports.postProcessBundle = postProcessBundle;
321
+ //# sourceMappingURL=post-process.js.map