@hibi_10000/crx 0.5.1
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 +88 -0
- package/LICENSE.txt +23 -0
- package/README.md +205 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +89 -0
- package/dist/cli.js.map +1 -0
- package/dist/crx2.js +36 -0
- package/dist/crx2.js.map +1 -0
- package/dist/crx3.js +90 -0
- package/dist/crx3.js.map +1 -0
- package/dist/crx3.pb.js +17 -0
- package/dist/crx3.pb.js.map +1 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +157 -0
- package/dist/index.js.map +1 -0
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/resolver.js +41 -0
- package/dist/resolver.js.map +1 -0
- package/package.json +71 -0
- package/src/cli.ts +175 -0
- package/src/crx2.ts +43 -0
- package/src/crx3.pb.d.ts +22 -0
- package/src/crx3.pb.js +37 -0
- package/src/crx3.ts +111 -0
- package/src/index.ts +232 -0
- package/src/resolver.ts +54 -0
- package/tsconfig.json +22 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
/** @enum {number} CrxVersion */
|
|
5
|
+
declare const CrxVersion: {
|
|
6
|
+
readonly VERSION_2: 2;
|
|
7
|
+
readonly VERSION_3: 3;
|
|
8
|
+
};
|
|
9
|
+
interface BrowserManifest {
|
|
10
|
+
minimum_chrome_version?: string;
|
|
11
|
+
version: string;
|
|
12
|
+
}
|
|
13
|
+
type BrowserExtensionOptions = { [K in keyof Omit<ChromeExtension, "loaded"> as ChromeExtension[K] extends Function ? never : K]?: ChromeExtension[K] };
|
|
14
|
+
declare class ChromeExtension {
|
|
15
|
+
appId?: string;
|
|
16
|
+
rootDirectory: string;
|
|
17
|
+
publicKey?: Buffer;
|
|
18
|
+
privateKey?: crypto.KeyLike;
|
|
19
|
+
codebase?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
src: string;
|
|
22
|
+
ignore: string[];
|
|
23
|
+
version: number;
|
|
24
|
+
loaded: boolean;
|
|
25
|
+
manifest?: BrowserManifest;
|
|
26
|
+
constructor(attrs: BrowserExtensionOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Packs the content of the extension in a crx file.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
*
|
|
32
|
+
* crx.pack().then(function(crxContent){
|
|
33
|
+
* // do something with the crxContent binary data
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
pack(contentsBuffer?: Buffer): Promise<Buffer>;
|
|
38
|
+
/**
|
|
39
|
+
* Loads extension manifest and copies its content to a workable path.
|
|
40
|
+
*/
|
|
41
|
+
load(path?: string | string[]): Promise<ChromeExtension>;
|
|
42
|
+
/**
|
|
43
|
+
* Generates a public key.
|
|
44
|
+
*
|
|
45
|
+
* BC BREAK `this.publicKey` is not stored anymore (since 1.0.0)
|
|
46
|
+
* BC BREAK callback parameter has been removed in favor to the promise interface.
|
|
47
|
+
*
|
|
48
|
+
* @returns Resolves to {Buffer} containing the public key
|
|
49
|
+
* @example
|
|
50
|
+
*
|
|
51
|
+
* crx.generatePublicKey(function(publicKey){
|
|
52
|
+
* // do something with publicKey
|
|
53
|
+
* });
|
|
54
|
+
*/
|
|
55
|
+
generatePublicKey(): Promise<Buffer>;
|
|
56
|
+
/**
|
|
57
|
+
* BC BREAK `this.contents` is not stored anymore (since 1.0.0)
|
|
58
|
+
*/
|
|
59
|
+
loadContents(): Promise<Buffer>;
|
|
60
|
+
/**
|
|
61
|
+
* Generates an appId from the publicKey.
|
|
62
|
+
* Public key has to be set for this to work, otherwise an error is thrown.
|
|
63
|
+
*
|
|
64
|
+
* BC BREAK `this.appId` is not stored anymore (since 1.0.0)
|
|
65
|
+
* BC BREAK introduced `publicKey` parameter as it is not stored any more since 2.0.0
|
|
66
|
+
*
|
|
67
|
+
* @param keyOrPath the public key to use to generate the app ID
|
|
68
|
+
*/
|
|
69
|
+
generateAppId(keyOrPath?: Buffer | string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Generates an updateXML file from the extension content.
|
|
72
|
+
*
|
|
73
|
+
* If manifest does not include `minimum_chrome_version`, defaults to:
|
|
74
|
+
* - '29.0.0' for CRX2, which is earliest extensions API available
|
|
75
|
+
* - '64.0.3242' for CRX3, which is when Chrome etension packager switched to CRX3
|
|
76
|
+
*
|
|
77
|
+
* BC BREAK `this.updateXML` is not stored anymore (since 1.0.0)
|
|
78
|
+
*
|
|
79
|
+
* [Chrome Extensions APIs]{@link https://developer.chrome.com/extensions/api_index}
|
|
80
|
+
* [Chrome verions]{@link https://en.wikipedia.org/wiki/Google_Chrome_version_history}
|
|
81
|
+
* [Chromium switches to CRX3]{@link https://chromium.googlesource.com/chromium/src.git/+/b8bc9f99ef4ad6223dfdcafd924051561c05ac75}
|
|
82
|
+
*/
|
|
83
|
+
generateUpdateXML(): Buffer;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { CrxVersion, ChromeExtension as default, ChromeExtension as "module.exports" };
|
|
87
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import resolve from "./resolver.js";
|
|
2
|
+
import generatePackage from "./crx2.js";
|
|
3
|
+
import generatePackage$1 from "./crx3.js";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import archiver from "archiver";
|
|
8
|
+
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
/** @enum {number} CrxVersion */
|
|
11
|
+
const CrxVersion = {
|
|
12
|
+
VERSION_2: 2,
|
|
13
|
+
VERSION_3: 3
|
|
14
|
+
};
|
|
15
|
+
var ChromeExtension = class {
|
|
16
|
+
appId;
|
|
17
|
+
rootDirectory = "";
|
|
18
|
+
publicKey;
|
|
19
|
+
privateKey;
|
|
20
|
+
codebase;
|
|
21
|
+
path;
|
|
22
|
+
src = "**";
|
|
23
|
+
ignore = ["*.crx"];
|
|
24
|
+
version = CrxVersion.VERSION_3;
|
|
25
|
+
loaded;
|
|
26
|
+
manifest;
|
|
27
|
+
constructor(attrs) {
|
|
28
|
+
Object.assign(this, attrs);
|
|
29
|
+
this.loaded = false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Packs the content of the extension in a crx file.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
*
|
|
36
|
+
* crx.pack().then(function(crxContent){
|
|
37
|
+
* // do something with the crxContent binary data
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
async pack(contentsBuffer) {
|
|
42
|
+
if (!this.loaded) return this.load().then(this.pack.bind(this, contentsBuffer));
|
|
43
|
+
const publicKey = await this.generatePublicKey();
|
|
44
|
+
const contents = contentsBuffer || await this.loadContents();
|
|
45
|
+
this.publicKey = publicKey;
|
|
46
|
+
if (this.version === 2) return generatePackage(this.privateKey, publicKey, contents);
|
|
47
|
+
return generatePackage$1(this.privateKey, publicKey, contents);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Loads extension manifest and copies its content to a workable path.
|
|
51
|
+
*/
|
|
52
|
+
async load(path$1) {
|
|
53
|
+
const metadata = await resolve(path$1 || this.rootDirectory);
|
|
54
|
+
this.path = metadata.path;
|
|
55
|
+
this.src = metadata.src;
|
|
56
|
+
const manifestPath = join(this.path, "manifest.json");
|
|
57
|
+
this.manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
58
|
+
this.loaded = true;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generates a public key.
|
|
63
|
+
*
|
|
64
|
+
* BC BREAK `this.publicKey` is not stored anymore (since 1.0.0)
|
|
65
|
+
* BC BREAK callback parameter has been removed in favor to the promise interface.
|
|
66
|
+
*
|
|
67
|
+
* @returns Resolves to {Buffer} containing the public key
|
|
68
|
+
* @example
|
|
69
|
+
*
|
|
70
|
+
* crx.generatePublicKey(function(publicKey){
|
|
71
|
+
* // do something with publicKey
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
async generatePublicKey() {
|
|
75
|
+
const privateKey = this.privateKey;
|
|
76
|
+
if (!privateKey) throw new Error("Impossible to generate a public key: privateKey option has not been defined or is empty.");
|
|
77
|
+
return crypto.createPublicKey(privateKey).export({
|
|
78
|
+
type: "spki",
|
|
79
|
+
format: "der"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* BC BREAK `this.contents` is not stored anymore (since 1.0.0)
|
|
84
|
+
*/
|
|
85
|
+
loadContents() {
|
|
86
|
+
return new Promise((resolve$1, reject) => {
|
|
87
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
88
|
+
let contents = Buffer.from("");
|
|
89
|
+
if (!this.loaded) throw new Error("crx.load needs to be called first in order to prepare the workspace.");
|
|
90
|
+
archive.on("error", reject);
|
|
91
|
+
archive.on("data", (buf) => {
|
|
92
|
+
contents = Buffer.concat([contents, buf]);
|
|
93
|
+
});
|
|
94
|
+
archive.on("finish", () => {
|
|
95
|
+
resolve$1(contents);
|
|
96
|
+
});
|
|
97
|
+
archive.glob(this.src, {
|
|
98
|
+
cwd: this.path,
|
|
99
|
+
matchBase: true,
|
|
100
|
+
ignore: [
|
|
101
|
+
"*.pem",
|
|
102
|
+
".git",
|
|
103
|
+
...this.ignore
|
|
104
|
+
]
|
|
105
|
+
}).finalize();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generates an appId from the publicKey.
|
|
110
|
+
* Public key has to be set for this to work, otherwise an error is thrown.
|
|
111
|
+
*
|
|
112
|
+
* BC BREAK `this.appId` is not stored anymore (since 1.0.0)
|
|
113
|
+
* BC BREAK introduced `publicKey` parameter as it is not stored any more since 2.0.0
|
|
114
|
+
*
|
|
115
|
+
* @param keyOrPath the public key to use to generate the app ID
|
|
116
|
+
*/
|
|
117
|
+
generateAppId(keyOrPath) {
|
|
118
|
+
keyOrPath = keyOrPath || this.publicKey;
|
|
119
|
+
if (typeof keyOrPath !== "string" && !(keyOrPath instanceof Buffer)) throw new Error("Public key is neither set, nor given");
|
|
120
|
+
if (typeof keyOrPath === "string") {
|
|
121
|
+
const charCode = keyOrPath.charCodeAt(0);
|
|
122
|
+
if (charCode >= 65 && charCode <= 122 && keyOrPath[1] === ":") {
|
|
123
|
+
keyOrPath = keyOrPath[0].toUpperCase() + keyOrPath.slice(1);
|
|
124
|
+
keyOrPath = Buffer.from(keyOrPath, "utf16le");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return crypto.createHash("sha256").update(keyOrPath).digest().toString("hex").split("").map((x) => (parseInt(x, 16) + 10).toString(26)).join("").slice(0, 32);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Generates an updateXML file from the extension content.
|
|
131
|
+
*
|
|
132
|
+
* If manifest does not include `minimum_chrome_version`, defaults to:
|
|
133
|
+
* - '29.0.0' for CRX2, which is earliest extensions API available
|
|
134
|
+
* - '64.0.3242' for CRX3, which is when Chrome etension packager switched to CRX3
|
|
135
|
+
*
|
|
136
|
+
* BC BREAK `this.updateXML` is not stored anymore (since 1.0.0)
|
|
137
|
+
*
|
|
138
|
+
* [Chrome Extensions APIs]{@link https://developer.chrome.com/extensions/api_index}
|
|
139
|
+
* [Chrome verions]{@link https://en.wikipedia.org/wiki/Google_Chrome_version_history}
|
|
140
|
+
* [Chromium switches to CRX3]{@link https://chromium.googlesource.com/chromium/src.git/+/b8bc9f99ef4ad6223dfdcafd924051561c05ac75}
|
|
141
|
+
*/
|
|
142
|
+
generateUpdateXML() {
|
|
143
|
+
if (!this.codebase) throw new Error("No URL provided for update.xml.");
|
|
144
|
+
if (!this.loaded) throw new Error("crx.load needs to be called first in order to generate update.xml.");
|
|
145
|
+
const browserVersion = this.manifest.minimum_chrome_version || (this.version < 3 ? "29.0.0" : void 0) || "64.0.3242";
|
|
146
|
+
return Buffer.from(`<?xml version='1.0' encoding='UTF-8'?>
|
|
147
|
+
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
|
148
|
+
<app appid='${this.appId || this.generateAppId()}'>
|
|
149
|
+
<updatecheck codebase='${this.codebase}' version='${this.manifest.version}' prodversionmin='${browserVersion}' />
|
|
150
|
+
</app>
|
|
151
|
+
</gupdate>`);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { CrxVersion, ChromeExtension as default, ChromeExtension as "module.exports" };
|
|
157
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["crx2","crx3","path"],"sources":["../src/index.ts"],"sourcesContent":["\"use strict\";\n\nimport fs from \"node:fs\";\nimport { join } from \"node:path\";\nimport crypto from \"node:crypto\";\nimport archiver from \"archiver\";\nimport resolve from \"./resolver.ts\";\nimport crx2 from \"./crx2.ts\";\nimport crx3 from \"./crx3.ts\";\n\n/** @enum {number} CrxVersion */\nexport const CrxVersion = {\n VERSION_2: 2,\n VERSION_3: 3,\n} as const;\n\ninterface BrowserManifest {\n minimum_chrome_version?: string;\n version: string;\n}\n\ntype BrowserExtensionOptions = {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n [K in keyof Omit<ChromeExtension, \"loaded\"> as ChromeExtension[K] extends Function ? never : K]?: ChromeExtension[K];\n};\n\nclass ChromeExtension {\n appId?: string;\n rootDirectory: string = \"\";\n publicKey?: Buffer;\n privateKey?: crypto.KeyLike;\n codebase?: string;\n path?: string;\n src: string = \"**\";\n ignore: string[] = [\"*.crx\"];\n version: number = CrxVersion.VERSION_3;\n loaded: boolean;\n manifest?: BrowserManifest;\n\n constructor(attrs: BrowserExtensionOptions) {\n Object.assign(this, attrs);\n this.loaded = false;\n }\n\n /**\n * Packs the content of the extension in a crx file.\n *\n * @example\n *\n * crx.pack().then(function(crxContent){\n * // do something with the crxContent binary data\n * });\n *\n */\n async pack(contentsBuffer?: Buffer): Promise<Buffer> {\n if (!this.loaded) {\n return this.load().then(this.pack.bind(this, contentsBuffer));\n }\n\n const publicKey = await this.generatePublicKey();\n const contents = contentsBuffer || await this.loadContents();\n\n this.publicKey = publicKey;\n\n if (this.version === 2) {\n return crx2(this.privateKey!, publicKey, contents);\n }\n\n return crx3(this.privateKey!, publicKey, contents);\n }\n\n /**\n * Loads extension manifest and copies its content to a workable path.\n */\n async load(path?: string | string[]): Promise<ChromeExtension> {\n const metadata = await resolve(path || this.rootDirectory);\n this.path = metadata.path;\n this.src = metadata.src;\n\n const manifestPath = join(this.path, \"manifest.json\");\n\n this.manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf-8\")) as BrowserManifest;\n this.loaded = true;\n\n return this;\n }\n\n /**\n * Generates a public key.\n *\n * BC BREAK `this.publicKey` is not stored anymore (since 1.0.0)\n * BC BREAK callback parameter has been removed in favor to the promise interface.\n *\n * @returns Resolves to {Buffer} containing the public key\n * @example\n *\n * crx.generatePublicKey(function(publicKey){\n * // do something with publicKey\n * });\n */\n // eslint-disable-next-line @typescript-eslint/require-await\n async generatePublicKey(): Promise<Buffer> {\n const privateKey = this.privateKey;\n\n if (!privateKey) {\n throw new Error(\"Impossible to generate a public key: privateKey option has not been defined or is empty.\");\n }\n\n const key = crypto.createPublicKey(privateKey);\n\n return key.export({ type: \"spki\", format: \"der\" });\n }\n\n /**\n * BC BREAK `this.contents` is not stored anymore (since 1.0.0)\n */\n loadContents(): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const archive = archiver(\"zip\", { zlib: { level: 9 } });\n let contents = Buffer.from(\"\");\n\n if (!this.loaded) {\n throw new Error(\n \"crx.load needs to be called first in order to prepare the workspace.\",\n );\n }\n\n archive.on(\"error\", reject);\n\n /*\n TODO: Remove in v4.\n It will be better to resolve an archive object\n rather than fitting everything in memory.\n\n @see https://github.com/oncletom/crx/issues/61\n */\n archive.on(\"data\", (buf) => {\n contents = Buffer.concat([contents, buf]);\n });\n\n archive.on(\"finish\", () => {\n resolve(contents);\n });\n\n void archive\n .glob(this.src, {\n cwd: this.path,\n matchBase: true,\n ignore: [\"*.pem\", \".git\", ...this.ignore],\n })\n .finalize();\n });\n }\n\n /**\n * Generates an appId from the publicKey.\n * Public key has to be set for this to work, otherwise an error is thrown.\n *\n * BC BREAK `this.appId` is not stored anymore (since 1.0.0)\n * BC BREAK introduced `publicKey` parameter as it is not stored any more since 2.0.0\n *\n * @param keyOrPath the public key to use to generate the app ID\n */\n generateAppId(keyOrPath?: Buffer | string): string {\n keyOrPath = keyOrPath || this.publicKey;\n\n if (typeof keyOrPath !== \"string\" && !(keyOrPath instanceof Buffer)) {\n throw new Error(\"Public key is neither set, nor given\");\n }\n\n // Handling Windows Path\n // Possibly to be moved in a different method\n if (typeof keyOrPath === \"string\") {\n const charCode = keyOrPath.charCodeAt(0);\n\n // 65 (A) < charCode < 122 (z)\n if (charCode >= 65 && charCode <= 122 && keyOrPath[1] === \":\") {\n keyOrPath = keyOrPath[0].toUpperCase() + keyOrPath.slice(1);\n\n keyOrPath = Buffer.from(keyOrPath, \"utf16le\");\n }\n }\n\n return crypto\n .createHash(\"sha256\")\n .update(keyOrPath)\n .digest()\n .toString(\"hex\")\n .split(\"\")\n .map((x) => (parseInt(x, 16) + 0x0a).toString(26))\n .join(\"\")\n .slice(0, 32);\n }\n\n /**\n * Generates an updateXML file from the extension content.\n *\n * If manifest does not include `minimum_chrome_version`, defaults to:\n * - '29.0.0' for CRX2, which is earliest extensions API available\n * - '64.0.3242' for CRX3, which is when Chrome etension packager switched to CRX3\n *\n * BC BREAK `this.updateXML` is not stored anymore (since 1.0.0)\n *\n * [Chrome Extensions APIs]{@link https://developer.chrome.com/extensions/api_index}\n * [Chrome verions]{@link https://en.wikipedia.org/wiki/Google_Chrome_version_history}\n * [Chromium switches to CRX3]{@link https://chromium.googlesource.com/chromium/src.git/+/b8bc9f99ef4ad6223dfdcafd924051561c05ac75}\n */\n generateUpdateXML(): Buffer {\n if (!this.codebase) {\n throw new Error(\"No URL provided for update.xml.\");\n }\n if (!this.loaded) {\n throw new Error(\n \"crx.load needs to be called first in order to generate update.xml.\",\n );\n }\n\n const browserVersion = this.manifest!.minimum_chrome_version\n || (this.version < 3 ? \"29.0.0\" : undefined) // Earliest version with extensions API\n || \"64.0.3242\"; // Chrome started generating CRX3 packages\n\n return Buffer.from(`<?xml version='1.0' encoding='UTF-8'?>\n<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>\n <app appid='${this.appId || this.generateAppId()}'>\n <updatecheck codebase='${this.codebase}' version='${this.manifest!.version}' prodversionmin='${browserVersion}' />\n </app>\n</gupdate>`);\n }\n}\n\nexport { ChromeExtension as default, ChromeExtension as \"module.exports\" };\n"],"mappings":"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"}
|
package/dist/package.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"author\": \"Jed Schmidt <tr@nslator.jp> (http://jed.is)\",\n \"name\": \"crx\",\n \"description\": \"crx is a utility to package Google Chrome extensions via a Node API and the command line\",\n \"version\": \"5.0.1\",\n \"license\": \"MIT\",\n \"homepage\": \"https://github.com/oncletom/crx\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git://github.com/oncletom/crx.git\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \"require\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n }\n },\n \"bin\": {\n \"crx\": \"./dist/cli.js\"\n },\n \"engines\": {\n \"node\": \">=18.20\",\n \"pnpm\": \">=10.16\"\n },\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc\",\n \"lint\": \"eslint .\",\n \"format\": \"eslint --fix .\",\n \"test\": \"node --experimental-test-coverage --test-coverage-exclude=src/crx3.pb.js --test ./test/index.test.ts\"\n },\n \"dependencies\": {\n \"archiver\": \"^7.0.1\",\n \"commander\": \"^14.0.2\",\n \"pbf\": \"^4.0.1\"\n },\n \"devEngines\": {\n \"runtime\": {\n \"name\": \"node\",\n \"version\": \"^24.11\",\n \"onFail\": \"download\"\n }\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.38.0\",\n \"@stylistic/eslint-plugin\": \"^5.5.0\",\n \"@types/adm-zip\": \"^0.5.7\",\n \"@types/archiver\": \"^7.0.0\",\n \"@types/node\": \"^24.10.1\",\n \"adm-zip\": \"^0.4.13\",\n \"eslint\": \"^9.38.0\",\n \"globals\": \"^16.4.0\",\n \"tsdown\": \"^0.16.5\",\n \"typescript\": \"^5.9.3\",\n \"typescript-eslint\": \"^8.55.0\"\n }\n}\n"],"mappings":";cAIa"}
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
//#region src/resolver.ts
|
|
4
|
+
function resolve(pathOrFiles) {
|
|
5
|
+
return new Promise((resolve$1, reject) => {
|
|
6
|
+
if (typeof pathOrFiles === "string") {
|
|
7
|
+
resolve$1({
|
|
8
|
+
path: path.resolve(pathOrFiles),
|
|
9
|
+
src: "**"
|
|
10
|
+
});
|
|
11
|
+
return;
|
|
12
|
+
} else if (Array.isArray(pathOrFiles)) {
|
|
13
|
+
let manifestFile = "";
|
|
14
|
+
pathOrFiles.some((f) => {
|
|
15
|
+
if (/(^|\/)manifest.json$/.test(f)) {
|
|
16
|
+
manifestFile = f;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
if (!manifestFile) {
|
|
21
|
+
reject(/* @__PURE__ */ new Error("Unable to locate a manifest file in your list of files."));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const manifestDir = path.dirname(manifestFile);
|
|
25
|
+
resolve$1({
|
|
26
|
+
path: path.resolve(manifestDir),
|
|
27
|
+
src: `{${pathOrFiles.map((f) => {
|
|
28
|
+
return path.relative(manifestDir, f);
|
|
29
|
+
}).join(",")}}`
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
} else {
|
|
33
|
+
reject(/* @__PURE__ */ new Error("load path is none of a folder location nor a list of files to pack"));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { resolve as default };
|
|
41
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","names":["resolve"],"sources":["../src/resolver.ts"],"sourcesContent":["\"use strict\";\n\nimport path from \"node:path\";\n\ninterface PathMetadata {\n path: string;\n src: string;\n}\n\nexport default function resolve(pathOrFiles: string | string[]): Promise<PathMetadata> {\n return new Promise((resolve, reject) => {\n // legacy and original mode\n if (typeof pathOrFiles === \"string\") {\n return void resolve({\n path: path.resolve(pathOrFiles),\n src: \"**\",\n });\n }\n\n // new mode, with a list of files\n else if (Array.isArray(pathOrFiles)) {\n let manifestFile = \"\";\n\n pathOrFiles.some((f) => {\n if (/(^|\\/)manifest.json$/.test(f)) {\n manifestFile = f;\n return true;\n }\n });\n\n if (!manifestFile) {\n return void reject(\n new Error(\"Unable to locate a manifest file in your list of files.\"),\n );\n }\n\n const manifestDir = path.dirname(manifestFile);\n\n return void resolve({\n path: path.resolve(manifestDir),\n src: `{${\n pathOrFiles\n .map((f) => {\n return path.relative(manifestDir, f);\n })\n .join(\",\")\n }}`,\n });\n }\n\n //\n else {\n return void reject(\n new Error(\n \"load path is none of a folder location nor a list of files to pack\",\n ),\n );\n }\n });\n};\n"],"mappings":";;;AASA,SAAwB,QAAQ,aAAuD;AACrF,QAAO,IAAI,SAAS,WAAS,WAAW;AAEtC,MAAI,OAAO,gBAAgB,UAAU;AAC5B,GAAKA,UAAQ;IAClB,MAAM,KAAK,QAAQ,YAAY;IAC/B,KAAK;IACN,CAAC;AAHF;aAOO,MAAM,QAAQ,YAAY,EAAE;GACnC,IAAI,eAAe;AAEnB,eAAY,MAAM,MAAM;AACtB,QAAI,uBAAuB,KAAK,EAAE,EAAE;AAClC,oBAAe;AACf,YAAO;;KAET;AAEF,OAAI,CAAC,cAAc;AACV,IAAK,uBACV,IAAI,MAAM,0DAA0D,CACrE;AAFD;;GAKF,MAAM,cAAc,KAAK,QAAQ,aAAa;AAEvC,GAAKA,UAAQ;IAClB,MAAM,KAAK,QAAQ,YAAY;IAC/B,KAAK,IACH,YACG,KAAK,MAAM;AACV,YAAO,KAAK,SAAS,aAAa,EAAE;MACpC,CACD,KAAK,IAAI,CACb;IACF,CAAC;AATF;SAaG;AACI,GAAK,uBACV,IAAI,MACF,qEACD,CACF;AAJD;;GAMF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Hibi_10000",
|
|
3
|
+
"name": "@hibi_10000/crx",
|
|
4
|
+
"description": "crx is a utility to package Google Chrome extensions via a Node API and the command line",
|
|
5
|
+
"version": "0.5.1",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Jed Schmidt <tr@nslator.jp> (http://jed.is)"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/Hibi-10000/crx",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git://github.com/Hibi-10000/crx.git"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
"require": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"crx": "dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": "^18.20 || >=20.10"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"typecheck": "tsc",
|
|
37
|
+
"lint": "eslint .",
|
|
38
|
+
"format": "eslint --fix .",
|
|
39
|
+
"test": "node --experimental-test-coverage --test-coverage-exclude=src/crx3.pb.js --test ./test/index.test.ts"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"archiver": "^7.0.1",
|
|
43
|
+
"commander": "^14.0.2",
|
|
44
|
+
"pbf": "^4.0.1"
|
|
45
|
+
},
|
|
46
|
+
"devEngines": {
|
|
47
|
+
"runtime": {
|
|
48
|
+
"name": "node",
|
|
49
|
+
"version": "^24.11",
|
|
50
|
+
"onFail": "download"
|
|
51
|
+
},
|
|
52
|
+
"packageManager": {
|
|
53
|
+
"name": "pnpm",
|
|
54
|
+
"version": ">=10.16",
|
|
55
|
+
"onFail": "warn"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@eslint/js": "^9.38.0",
|
|
60
|
+
"@stylistic/eslint-plugin": "^5.5.0",
|
|
61
|
+
"@types/adm-zip": "^0.5.7",
|
|
62
|
+
"@types/archiver": "^7.0.0",
|
|
63
|
+
"@types/node": "^24.10.1",
|
|
64
|
+
"adm-zip": "^0.4.13",
|
|
65
|
+
"eslint": "^9.38.0",
|
|
66
|
+
"globals": "^16.4.0",
|
|
67
|
+
"tsdown": "^0.16.5",
|
|
68
|
+
"typescript": "^5.9.3",
|
|
69
|
+
"typescript-eslint": "^8.55.0"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
|
|
7
|
+
import { program } from "commander";
|
|
8
|
+
import ChromeExtension from "./index.ts";
|
|
9
|
+
|
|
10
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
11
|
+
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
|
|
14
|
+
program.version(packageJson.version);
|
|
15
|
+
// coming soon
|
|
16
|
+
// .option("-x, --xml", "output autoupdate xml instead of extension ")
|
|
17
|
+
|
|
18
|
+
interface InterfaceCli {
|
|
19
|
+
crxVersion?: number;
|
|
20
|
+
force: boolean;
|
|
21
|
+
privateKey: string;
|
|
22
|
+
output?: string;
|
|
23
|
+
zipOutput?: string;
|
|
24
|
+
//maxBuffer?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("keygen [directory]")
|
|
29
|
+
.option("--force", "overwrite the private key if it exists")
|
|
30
|
+
.option(
|
|
31
|
+
"-c, --crx-version [number]",
|
|
32
|
+
"CRX format version, can be either 2 or 3, defaults to 3",
|
|
33
|
+
parseInt,
|
|
34
|
+
)
|
|
35
|
+
.description("generate a private key in [directory]/key.pem")
|
|
36
|
+
.action(keygen);
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command("pack [directory]")
|
|
40
|
+
.description("pack [directory] into a .crx extension")
|
|
41
|
+
.option(
|
|
42
|
+
"-o, --output <file>",
|
|
43
|
+
"write the crx content to <file> instead of stdout",
|
|
44
|
+
)
|
|
45
|
+
.option("--zip-output <file>", "write the zip content to <file>")
|
|
46
|
+
.option(
|
|
47
|
+
"-p, --private-key <file>",
|
|
48
|
+
"relative path to private key [key.pem], defaults to [directory/../key.pem]")
|
|
49
|
+
//.option(
|
|
50
|
+
// "-b, --max-buffer <total>",
|
|
51
|
+
// "max amount of memory allowed to generate the crx, in byte",
|
|
52
|
+
//)
|
|
53
|
+
.option(
|
|
54
|
+
"-c, --crx-version [number]",
|
|
55
|
+
"CRX format version, can be either 2 or 3, defaults to 3",
|
|
56
|
+
parseInt,
|
|
57
|
+
)
|
|
58
|
+
.action(pack);
|
|
59
|
+
|
|
60
|
+
program.parse(process.argv);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate a new key file
|
|
64
|
+
* @param keyPath path of the key file to create
|
|
65
|
+
* @param opts
|
|
66
|
+
*/
|
|
67
|
+
function generateKeyFile(keyPath: string, opts: InterfaceCli): Promise<void> {
|
|
68
|
+
const { privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
69
|
+
modulusLength: 2048,
|
|
70
|
+
publicKeyEncoding: {
|
|
71
|
+
type: "spki",
|
|
72
|
+
format: "der",
|
|
73
|
+
},
|
|
74
|
+
privateKeyEncoding: {
|
|
75
|
+
// Chromium (tested on 72.0.3626.109) which generates CRX v3 files requires pkcs8 key
|
|
76
|
+
type: `pkcs${opts.crxVersion === 2 ? "1" : "8"}`,
|
|
77
|
+
format: "pem",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return fs.promises.writeFile(keyPath, privateKey);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generates a Private Key
|
|
85
|
+
*/
|
|
86
|
+
async function keygen(dir: string, opts: InterfaceCli) {
|
|
87
|
+
dir = dir ? path.resolve(cwd, dir) : cwd;
|
|
88
|
+
|
|
89
|
+
const keyPath = path.join(dir, "key.pem");
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
fs.accessSync(keyPath);
|
|
93
|
+
if (!opts.force) {
|
|
94
|
+
throw new Error("key.pem already exists in the given location.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (_err) {
|
|
98
|
+
await generateKeyFile(keyPath, opts);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function pack(dir: string, opts: InterfaceCli) {
|
|
103
|
+
const input = dir ? path.resolve(cwd, dir) : cwd;
|
|
104
|
+
const keyPath = opts.privateKey
|
|
105
|
+
? path.resolve(cwd, opts.privateKey)
|
|
106
|
+
: path.join(input, "..", "key.pem");
|
|
107
|
+
let output: string;
|
|
108
|
+
|
|
109
|
+
if (opts.output) {
|
|
110
|
+
if (path.extname(opts.output) !== ".crx") {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`-o file is expected to have a \`.crx\` suffix: [${opts.output}] was given.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (opts.zipOutput) {
|
|
118
|
+
if (path.extname(opts.zipOutput) !== ".zip") {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`--zip-output file is expected to have a \`.zip\` suffix: [${opts.zipOutput}] was given.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const crx = new ChromeExtension({
|
|
126
|
+
rootDirectory: input,
|
|
127
|
+
//maxBuffer: opts.maxBuffer,
|
|
128
|
+
version: opts.crxVersion || 3,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await fs.promises.readFile(keyPath)
|
|
132
|
+
.then(null, async (err: unknown) => {
|
|
133
|
+
// If the key file doesn't exist, create one
|
|
134
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
135
|
+
await generateKeyFile(keyPath, opts);
|
|
136
|
+
process.stderr.write(`Created new private key at: ${keyPath}.\n`);
|
|
137
|
+
return fs.readFileSync(keyPath);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
.then(async (key) => {
|
|
144
|
+
crx.privateKey = key;
|
|
145
|
+
await crx.load();
|
|
146
|
+
const fileBuffer = await crx.loadContents();
|
|
147
|
+
if (opts.zipOutput) {
|
|
148
|
+
const outFile = path.resolve(cwd, opts.zipOutput);
|
|
149
|
+
|
|
150
|
+
fs.createWriteStream(outFile).end(fileBuffer);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const crxBuffer = await crx.pack(fileBuffer);
|
|
154
|
+
if (opts.zipOutput) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
else if (opts.output) {
|
|
158
|
+
output = opts.output;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
output = `${path.basename(cwd)}.crx`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const outFile = path.resolve(cwd, output);
|
|
165
|
+
if (outFile) {
|
|
166
|
+
fs.createWriteStream(outFile).end(crxBuffer);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
process.stdout.end(crxBuffer);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default program;
|
package/src/crx2.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates and returns a signed package from extension content.
|
|
7
|
+
*
|
|
8
|
+
* BC BREAK `this.package` is not stored anymore (since 1.0.0)
|
|
9
|
+
*/
|
|
10
|
+
export default function generatePackage(privateKey: crypto.KeyLike, publicKey: Buffer, contents: Buffer): Buffer {
|
|
11
|
+
const signature = generateSignature(privateKey, contents);
|
|
12
|
+
|
|
13
|
+
const keyLength = publicKey.length;
|
|
14
|
+
const sigLength = signature.length;
|
|
15
|
+
const zipLength = contents.length;
|
|
16
|
+
const length = 16 + keyLength + sigLength + zipLength;
|
|
17
|
+
|
|
18
|
+
const crx = Buffer.alloc(length);
|
|
19
|
+
|
|
20
|
+
crx.write("Cr24" + new Array(13).join("\x00"), "binary");
|
|
21
|
+
|
|
22
|
+
crx[4] = 2;
|
|
23
|
+
crx.writeUInt32LE(keyLength, 8);
|
|
24
|
+
crx.writeUInt32LE(sigLength, 12);
|
|
25
|
+
|
|
26
|
+
publicKey.copy(crx, 16);
|
|
27
|
+
signature.copy(crx, 16 + keyLength);
|
|
28
|
+
contents.copy(crx, 16 + keyLength + sigLength);
|
|
29
|
+
|
|
30
|
+
return crx;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generates a SHA1 package signature.
|
|
35
|
+
*
|
|
36
|
+
* BC BREAK `this.signature` is not stored anymore (since 1.0.0)
|
|
37
|
+
*/
|
|
38
|
+
function generateSignature(privateKey: crypto.KeyLike, contents: Buffer): Buffer {
|
|
39
|
+
return crypto
|
|
40
|
+
.createSign("sha1")
|
|
41
|
+
.update(contents)
|
|
42
|
+
.sign(privateKey);
|
|
43
|
+
}
|
package/src/crx3.pb.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type Pbf from "pbf";
|
|
2
|
+
|
|
3
|
+
interface CrxFileHeader {
|
|
4
|
+
sha256_with_rsa?: AsymmetricKeyProof[];
|
|
5
|
+
sha256_with_ecdsa?: AsymmetricKeyProof[];
|
|
6
|
+
signed_header_data?: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
export function readCrxFileHeader(pbf: Pbf, end?: number): CrxFileHeader;
|
|
9
|
+
export function writeCrxFileHeader(obj: CrxFileHeader, pbf: Pbf): void;
|
|
10
|
+
|
|
11
|
+
interface AsymmetricKeyProof {
|
|
12
|
+
public_key?: Uint8Array;
|
|
13
|
+
signature?: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
export function readAsymmetricKeyProof(pbf: Pbf, end?: number): AsymmetricKeyProof;
|
|
16
|
+
export function writeAsymmetricKeyProof(obj: AsymmetricKeyProof, pbf: Pbf): void;
|
|
17
|
+
|
|
18
|
+
interface SignedData {
|
|
19
|
+
crx_id?: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
export function readSignedData(pbf: Pbf, end?: number): SignedData;
|
|
22
|
+
export function writeSignedData(obj: SignedData, pbf: Pbf): void;
|
package/src/crx3.pb.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// code generated by pbf v4.0.1 from https://github.com/chromium/chromium/blob/e4a3bada6aab7aed90460ec7d27f8c7167c5666e/components/crx_file/crx3.proto
|
|
2
|
+
|
|
3
|
+
export function readCrxFileHeader(pbf, end) {
|
|
4
|
+
return pbf.readFields(readCrxFileHeaderField, {sha256_with_rsa: [], sha256_with_ecdsa: [], signed_header_data: undefined}, end);
|
|
5
|
+
}
|
|
6
|
+
function readCrxFileHeaderField(tag, obj, pbf) {
|
|
7
|
+
if (tag === 2) obj.sha256_with_rsa.push(readAsymmetricKeyProof(pbf, pbf.readVarint() + pbf.pos));
|
|
8
|
+
else if (tag === 3) obj.sha256_with_ecdsa.push(readAsymmetricKeyProof(pbf, pbf.readVarint() + pbf.pos));
|
|
9
|
+
else if (tag === 10000) obj.signed_header_data = pbf.readBytes();
|
|
10
|
+
}
|
|
11
|
+
export function writeCrxFileHeader(obj, pbf) {
|
|
12
|
+
if (obj.sha256_with_rsa) for (const item of obj.sha256_with_rsa) pbf.writeMessage(2, writeAsymmetricKeyProof, item);
|
|
13
|
+
if (obj.sha256_with_ecdsa) for (const item of obj.sha256_with_ecdsa) pbf.writeMessage(3, writeAsymmetricKeyProof, item);
|
|
14
|
+
if (obj.signed_header_data != null) pbf.writeBytesField(10000, obj.signed_header_data);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readAsymmetricKeyProof(pbf, end) {
|
|
18
|
+
return pbf.readFields(readAsymmetricKeyProofField, {public_key: undefined, signature: undefined}, end);
|
|
19
|
+
}
|
|
20
|
+
function readAsymmetricKeyProofField(tag, obj, pbf) {
|
|
21
|
+
if (tag === 1) obj.public_key = pbf.readBytes();
|
|
22
|
+
else if (tag === 2) obj.signature = pbf.readBytes();
|
|
23
|
+
}
|
|
24
|
+
export function writeAsymmetricKeyProof(obj, pbf) {
|
|
25
|
+
if (obj.public_key != null) pbf.writeBytesField(1, obj.public_key);
|
|
26
|
+
if (obj.signature != null) pbf.writeBytesField(2, obj.signature);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readSignedData(pbf, end) {
|
|
30
|
+
return pbf.readFields(readSignedDataField, {crx_id: undefined}, end);
|
|
31
|
+
}
|
|
32
|
+
function readSignedDataField(tag, obj, pbf) {
|
|
33
|
+
if (tag === 1) obj.crx_id = pbf.readBytes();
|
|
34
|
+
}
|
|
35
|
+
export function writeSignedData(obj, pbf) {
|
|
36
|
+
if (obj.crx_id != null) pbf.writeBytesField(1, obj.crx_id);
|
|
37
|
+
}
|