extension-from-store 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/bin.cjs +66 -59
- package/bin.js +65 -59
- package/dist/588.js +211 -0
- package/dist/browser.cjs +21 -29
- package/dist/browser.js +14 -226
- package/dist/core.cjs +29 -36
- package/dist/core.d.ts +5 -5
- package/dist/core.js +1 -222
- package/dist/index.cjs +19 -26
- package/dist/index.js +42 -257
- package/dist/resolve.d.ts +2 -2
- package/package.json +27 -11
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[npm-version-image]: https://img.shields.io/npm/v/extension-from-store.svg?color=
|
|
1
|
+
[npm-version-image]: https://img.shields.io/npm/v/extension-from-store.svg?color=0971fe
|
|
2
2
|
[npm-version-url]: https://www.npmjs.com/package/extension-from-store
|
|
3
3
|
[npm-downloads-image]: https://img.shields.io/npm/dm/extension-from-store.svg?color=2ecc40
|
|
4
4
|
[npm-downloads-url]: https://www.npmjs.com/package/extension-from-store
|
|
@@ -151,6 +151,14 @@ Library logging is opt-in via the `logger` hooks. The library never writes direc
|
|
|
151
151
|
- `6` filesystem conflict
|
|
152
152
|
- `7` store incompatibility
|
|
153
153
|
|
|
154
|
+
## Related projects
|
|
155
|
+
|
|
156
|
+
* [browser-extension-manifest-fields](https://github.com/cezaraugusto/browser-extension-manifest-fields)
|
|
157
|
+
* [browser-extension-capabilities](https://github.com/cezaraugusto/browser-extension-capabilities)
|
|
158
|
+
* [browser-extension-compat-data](https://github.com/cezaraugusto/browser-extension-compat-data)
|
|
159
|
+
* [chrome-extension-manifest-json-schema](https://github.com/cezaraugusto/chrome-extension-manifest-json-schema)
|
|
160
|
+
* [parse5-asset-patcher](https://github.com/cezaraugusto/parse5-asset-patcher)
|
|
161
|
+
|
|
154
162
|
## License
|
|
155
163
|
|
|
156
164
|
MIT (c) Cezar Augusto.
|
package/bin.cjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
5
|
+
const mod = require('./dist/index.cjs')
|
|
6
|
+
|
|
7
|
+
const {fetchExtensionFromStore} = mod
|
|
8
|
+
const {extensionFromStoreError} = mod
|
|
7
9
|
|
|
8
10
|
const EXIT_CODES = {
|
|
9
11
|
InvalidInput: 1,
|
|
@@ -13,12 +15,13 @@ const EXIT_CODES = {
|
|
|
13
15
|
DownloadFailed: 4,
|
|
14
16
|
ExtractionFailed: 5,
|
|
15
17
|
FilesystemConflict: 6,
|
|
16
|
-
StoreIncompatibility: 7
|
|
17
|
-
}
|
|
18
|
+
StoreIncompatibility: 7
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseArgs (argv) {
|
|
22
|
+
const args = [...argv]
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
const args = [...argv];
|
|
21
|
-
if (args[0] === 'fetch') args.shift();
|
|
24
|
+
if (args[0] === 'fetch') args.shift()
|
|
22
25
|
|
|
23
26
|
const result = {
|
|
24
27
|
url: '',
|
|
@@ -28,126 +31,130 @@ function parseArgs(argv) {
|
|
|
28
31
|
extract: false,
|
|
29
32
|
quiet: false,
|
|
30
33
|
verbose: false,
|
|
31
|
-
json: false
|
|
32
|
-
}
|
|
34
|
+
json: false
|
|
35
|
+
}
|
|
33
36
|
|
|
34
37
|
for (let i = 0; i < args.length; i += 1) {
|
|
35
|
-
const arg = args[i]
|
|
38
|
+
const arg = args[i]
|
|
36
39
|
|
|
37
40
|
if (arg === '--url') {
|
|
38
|
-
result.url = args[++i] || ''
|
|
39
|
-
continue
|
|
41
|
+
result.url = args[++i] || ''
|
|
42
|
+
continue
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
if (arg === '--out') {
|
|
43
|
-
result.out = args[++i] || ''
|
|
44
|
-
continue
|
|
46
|
+
result.out = args[++i] || ''
|
|
47
|
+
continue
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
if (arg === '--version') {
|
|
48
|
-
result.version = args[++i] || ''
|
|
49
|
-
continue
|
|
51
|
+
result.version = args[++i] || ''
|
|
52
|
+
continue
|
|
50
53
|
}
|
|
54
|
+
|
|
51
55
|
if (arg === '--extract') {
|
|
52
|
-
result.extract = true
|
|
53
|
-
continue
|
|
56
|
+
result.extract = true
|
|
57
|
+
continue
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
if (arg === '--user-agent') {
|
|
57
|
-
result.userAgent = args[++i] || ''
|
|
58
|
-
continue
|
|
61
|
+
result.userAgent = args[++i] || ''
|
|
62
|
+
continue
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
if (arg === '--quiet') {
|
|
62
|
-
result.quiet = true
|
|
63
|
-
continue
|
|
66
|
+
result.quiet = true
|
|
67
|
+
continue
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
if (arg === '--verbose') {
|
|
67
|
-
result.verbose = true
|
|
68
|
-
continue
|
|
71
|
+
result.verbose = true
|
|
72
|
+
continue
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
if (arg === '--json') {
|
|
72
|
-
result.json = true
|
|
73
|
-
continue
|
|
76
|
+
result.json = true
|
|
77
|
+
continue
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
throw new Error(`Unknown flag: ${arg}`)
|
|
80
|
+
throw new Error(`Unknown flag: ${arg}`)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
if (!result.url) {
|
|
80
|
-
throw new Error('Missing required flag: --url')
|
|
84
|
+
throw new Error('Missing required flag: --url')
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
return result
|
|
87
|
+
return result
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
function createCliLogger(opts) {
|
|
90
|
+
function createCliLogger (opts) {
|
|
87
91
|
const emit = (level, message, error) => {
|
|
88
|
-
const payload = {
|
|
92
|
+
const payload = {level, message}
|
|
89
93
|
|
|
90
|
-
if (error) payload.error = String(error)
|
|
94
|
+
if (error) payload.error = String(error)
|
|
91
95
|
|
|
92
|
-
console.log(JSON.stringify(payload))
|
|
93
|
-
}
|
|
96
|
+
console.log(JSON.stringify(payload))
|
|
97
|
+
}
|
|
94
98
|
|
|
95
99
|
if (opts.json) {
|
|
96
100
|
return {
|
|
97
101
|
onInfo: (message) => emit('info', message),
|
|
98
102
|
onWarn: (message) => emit('warn', message),
|
|
99
|
-
onError: (message, error) => emit('error', message, error)
|
|
100
|
-
}
|
|
103
|
+
onError: (message, error) => emit('error', message, error)
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
return {
|
|
104
108
|
onInfo: opts.quiet ? undefined : (message) => console.log(message),
|
|
105
109
|
onWarn: opts.quiet ? undefined : (message) => console.error(message),
|
|
106
110
|
onError: (message, error) => {
|
|
107
|
-
const text = error ? `${message}\n${String(error)}` : message
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
const text = error ? `${message}\n${String(error)}` : message
|
|
112
|
+
|
|
113
|
+
console.error(text)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
async function main() {
|
|
114
|
-
let args = null
|
|
118
|
+
async function main () {
|
|
119
|
+
let args = null
|
|
115
120
|
|
|
116
121
|
try {
|
|
117
|
-
args = parseArgs(process.argv.slice(2))
|
|
118
|
-
const logger = createCliLogger(args)
|
|
122
|
+
args = parseArgs(process.argv.slice(2))
|
|
123
|
+
const logger = createCliLogger(args)
|
|
124
|
+
|
|
119
125
|
await fetchExtensionFromStore(args.url, {
|
|
120
126
|
outDir: args.out || undefined,
|
|
121
127
|
userAgent: args.userAgent || undefined,
|
|
122
128
|
version: args.version || undefined,
|
|
123
129
|
extract: args.extract,
|
|
124
|
-
logger
|
|
125
|
-
})
|
|
130
|
+
logger
|
|
131
|
+
})
|
|
126
132
|
|
|
127
|
-
process.exit(0)
|
|
133
|
+
process.exit(0)
|
|
128
134
|
} catch (error) {
|
|
129
135
|
if (error instanceof extensionFromStoreError) {
|
|
130
|
-
const code = EXIT_CODES[error.code] || 1
|
|
131
|
-
const message = error.message || 'Extension fetch failed'
|
|
136
|
+
const code = EXIT_CODES[error.code] || 1
|
|
137
|
+
const message = error.message || 'Extension fetch failed'
|
|
132
138
|
|
|
133
139
|
if (args?.json) {
|
|
134
|
-
console.log(JSON.stringify({
|
|
140
|
+
console.log(JSON.stringify({level: 'error', message}))
|
|
135
141
|
} else if (code !== 0) {
|
|
136
|
-
console.error(message)
|
|
142
|
+
console.error(message)
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
process.exit(code)
|
|
145
|
+
process.exit(code)
|
|
140
146
|
}
|
|
141
|
-
|
|
147
|
+
|
|
148
|
+
const message = String(error?.message || error)
|
|
142
149
|
|
|
143
150
|
if (args?.json) {
|
|
144
|
-
console.log(JSON.stringify({
|
|
151
|
+
console.log(JSON.stringify({level: 'error', message}))
|
|
145
152
|
} else {
|
|
146
|
-
console.error(message)
|
|
153
|
+
console.error(message)
|
|
147
154
|
}
|
|
148
155
|
|
|
149
|
-
process.exit(1)
|
|
156
|
+
process.exit(1)
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
|
|
153
|
-
main()
|
|
160
|
+
main()
|
package/bin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
|
|
3
|
-
const mod = require('./dist/index.cjs')
|
|
4
|
-
|
|
5
|
-
const
|
|
2
|
+
const mod = require('./dist/index.cjs')
|
|
3
|
+
|
|
4
|
+
const {fetchExtensionFromStore} = mod
|
|
5
|
+
const {extensionFromStoreError} = mod
|
|
6
6
|
|
|
7
7
|
const EXIT_CODES = {
|
|
8
8
|
InvalidInput: 1,
|
|
@@ -12,13 +12,13 @@ const EXIT_CODES = {
|
|
|
12
12
|
DownloadFailed: 4,
|
|
13
13
|
ExtractionFailed: 5,
|
|
14
14
|
FilesystemConflict: 6,
|
|
15
|
-
StoreIncompatibility: 7
|
|
16
|
-
}
|
|
15
|
+
StoreIncompatibility: 7
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
function parseArgs(argv) {
|
|
19
|
-
const args = [...argv]
|
|
18
|
+
function parseArgs (argv) {
|
|
19
|
+
const args = [...argv]
|
|
20
20
|
|
|
21
|
-
if (args[0] === 'fetch') args.shift()
|
|
21
|
+
if (args[0] === 'fetch') args.shift()
|
|
22
22
|
|
|
23
23
|
const result = {
|
|
24
24
|
url: '',
|
|
@@ -28,123 +28,129 @@ function parseArgs(argv) {
|
|
|
28
28
|
extract: false,
|
|
29
29
|
quiet: false,
|
|
30
30
|
verbose: false,
|
|
31
|
-
json: false
|
|
32
|
-
}
|
|
31
|
+
json: false
|
|
32
|
+
}
|
|
33
33
|
|
|
34
34
|
for (let i = 0; i < args.length; i += 1) {
|
|
35
|
-
const arg = args[i]
|
|
35
|
+
const arg = args[i]
|
|
36
36
|
|
|
37
37
|
if (arg === '--url') {
|
|
38
|
-
result.url = args[++i] || ''
|
|
39
|
-
continue
|
|
38
|
+
result.url = args[++i] || ''
|
|
39
|
+
continue
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (arg === '--out') {
|
|
43
|
-
result.out = args[++i] || ''
|
|
44
|
-
continue
|
|
43
|
+
result.out = args[++i] || ''
|
|
44
|
+
continue
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
if (arg === '--version') {
|
|
48
|
-
result.version = args[++i] || ''
|
|
49
|
-
continue
|
|
48
|
+
result.version = args[++i] || ''
|
|
49
|
+
continue
|
|
50
50
|
}
|
|
51
|
+
|
|
51
52
|
if (arg === '--extract') {
|
|
52
|
-
result.extract = true
|
|
53
|
-
continue
|
|
53
|
+
result.extract = true
|
|
54
|
+
continue
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
if (arg === '--user-agent') {
|
|
57
|
-
result.userAgent = args[++i] || ''
|
|
58
|
-
continue
|
|
58
|
+
result.userAgent = args[++i] || ''
|
|
59
|
+
continue
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
if (arg === '--quiet') {
|
|
62
|
-
result.quiet = true
|
|
63
|
-
continue
|
|
63
|
+
result.quiet = true
|
|
64
|
+
continue
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
if (arg === '--verbose') {
|
|
67
|
-
result.verbose = true
|
|
68
|
-
continue
|
|
68
|
+
result.verbose = true
|
|
69
|
+
continue
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
if (arg === '--json') {
|
|
72
|
-
result.json = true
|
|
73
|
-
continue
|
|
73
|
+
result.json = true
|
|
74
|
+
continue
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
throw new Error(`Unknown flag: ${arg}`)
|
|
77
|
+
throw new Error(`Unknown flag: ${arg}`)
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
if (!result.url) {
|
|
80
|
-
throw new Error('Missing required flag: --url')
|
|
81
|
+
throw new Error('Missing required flag: --url')
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
return result
|
|
84
|
+
return result
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
function createCliLogger(opts) {
|
|
87
|
+
function createCliLogger (opts) {
|
|
87
88
|
const emit = (level, message, error) => {
|
|
88
|
-
const payload = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
const payload = {level, message}
|
|
90
|
+
|
|
91
|
+
if (error) payload.error = String(error)
|
|
92
|
+
|
|
93
|
+
console.log(JSON.stringify(payload))
|
|
94
|
+
}
|
|
92
95
|
|
|
93
96
|
if (opts.json) {
|
|
94
97
|
return {
|
|
95
98
|
onInfo: (message) => emit('info', message),
|
|
96
99
|
onWarn: (message) => emit('warn', message),
|
|
97
|
-
onError: (message, error) => emit('error', message, error)
|
|
98
|
-
}
|
|
100
|
+
onError: (message, error) => emit('error', message, error)
|
|
101
|
+
}
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
return {
|
|
102
105
|
onInfo: opts.quiet ? undefined : (message) => console.log(message),
|
|
103
106
|
onWarn: opts.quiet ? undefined : (message) => console.error(message),
|
|
104
107
|
onError: (message, error) => {
|
|
105
|
-
const text = error ? `${message}\n${String(error)}` : message
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
const text = error ? `${message}\n${String(error)}` : message
|
|
109
|
+
|
|
110
|
+
console.error(text)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
async function main() {
|
|
112
|
-
let args = null
|
|
115
|
+
async function main () {
|
|
116
|
+
let args = null
|
|
113
117
|
|
|
114
118
|
try {
|
|
115
|
-
args = parseArgs(process.argv.slice(2))
|
|
116
|
-
const logger = createCliLogger(args)
|
|
119
|
+
args = parseArgs(process.argv.slice(2))
|
|
120
|
+
const logger = createCliLogger(args)
|
|
121
|
+
|
|
117
122
|
await fetchExtensionFromStore(args.url, {
|
|
118
123
|
outDir: args.out || undefined,
|
|
119
124
|
userAgent: args.userAgent || undefined,
|
|
120
125
|
version: args.version || undefined,
|
|
121
126
|
extract: args.extract,
|
|
122
|
-
logger
|
|
123
|
-
})
|
|
124
|
-
process.exit(0)
|
|
127
|
+
logger
|
|
128
|
+
})
|
|
129
|
+
process.exit(0)
|
|
125
130
|
} catch (error) {
|
|
126
131
|
if (error instanceof extensionFromStoreError) {
|
|
127
|
-
const code = EXIT_CODES[error.code] || 1
|
|
128
|
-
const message = error.message || 'Extension fetch failed'
|
|
132
|
+
const code = EXIT_CODES[error.code] || 1
|
|
133
|
+
const message = error.message || 'Extension fetch failed'
|
|
129
134
|
|
|
130
135
|
if (args?.json) {
|
|
131
|
-
console.log(JSON.stringify({
|
|
136
|
+
console.log(JSON.stringify({level: 'error', message}))
|
|
132
137
|
} else if (code !== 0) {
|
|
133
|
-
console.error(message)
|
|
138
|
+
console.error(message)
|
|
134
139
|
}
|
|
135
140
|
|
|
136
|
-
process.exit(code)
|
|
141
|
+
process.exit(code)
|
|
137
142
|
}
|
|
138
|
-
|
|
143
|
+
|
|
144
|
+
const message = String(error?.message || error)
|
|
139
145
|
|
|
140
146
|
if (args?.json) {
|
|
141
|
-
console.log(JSON.stringify({
|
|
147
|
+
console.log(JSON.stringify({level: 'error', message}))
|
|
142
148
|
} else {
|
|
143
|
-
console.error(message)
|
|
149
|
+
console.error(message)
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
process.exit(1)
|
|
152
|
+
process.exit(1)
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
main()
|
|
156
|
+
main()
|
package/dist/588.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
3
|
+
value: value,
|
|
4
|
+
enumerable: true,
|
|
5
|
+
configurable: true,
|
|
6
|
+
writable: true
|
|
7
|
+
});
|
|
8
|
+
else obj[key] = value;
|
|
9
|
+
return obj;
|
|
10
|
+
}
|
|
11
|
+
class extensionFromStoreError extends Error {
|
|
12
|
+
constructor(code, message, cause){
|
|
13
|
+
super(message), _define_property(this, "code", void 0), _define_property(this, "cause", void 0);
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function asExtensionFromStoreError(error, fallback) {
|
|
19
|
+
if (error instanceof extensionFromStoreError) return error;
|
|
20
|
+
return new extensionFromStoreError(fallback.code, fallback.message, error);
|
|
21
|
+
}
|
|
22
|
+
function readUInt32LE(bytes, offset) {
|
|
23
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, Uint32Array.BYTES_PER_ELEMENT);
|
|
24
|
+
return view.getUint32(0, true);
|
|
25
|
+
}
|
|
26
|
+
function readMagic(bytes) {
|
|
27
|
+
let out = '';
|
|
28
|
+
const slice = bytes.subarray(0, 4);
|
|
29
|
+
for(let index = 0; index < slice.length; index += 1)out += String.fromCharCode(slice[index]);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function stripCrxHeader(buffer) {
|
|
33
|
+
if (buffer.length < 16) throw new extensionFromStoreError('ExtractionFailed', 'CRX file too small');
|
|
34
|
+
const magic = readMagic(buffer);
|
|
35
|
+
if ('Cr24' !== magic) throw new extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
|
|
36
|
+
const version = readUInt32LE(buffer, 4);
|
|
37
|
+
if (2 === version) {
|
|
38
|
+
const publicKeyLength = readUInt32LE(buffer, 8);
|
|
39
|
+
const signatureLength = readUInt32LE(buffer, 12);
|
|
40
|
+
const headerSize = 16 + publicKeyLength + signatureLength;
|
|
41
|
+
return buffer.subarray(headerSize);
|
|
42
|
+
}
|
|
43
|
+
if (3 === version) {
|
|
44
|
+
const headerSize = readUInt32LE(buffer, 8);
|
|
45
|
+
return buffer.subarray(12 + headerSize);
|
|
46
|
+
}
|
|
47
|
+
throw new extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
|
|
48
|
+
}
|
|
49
|
+
function createLogger(logger) {
|
|
50
|
+
return {
|
|
51
|
+
info: (message)=>logger?.onInfo?.(message),
|
|
52
|
+
warn: (message)=>logger?.onWarn?.(message),
|
|
53
|
+
error: (message, error)=>logger?.onError?.(message, error)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseManifestInfo(raw) {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(raw);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new extensionFromStoreError('ExtractionFailed', 'manifest.json is not valid JSON', error);
|
|
62
|
+
}
|
|
63
|
+
if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json must contain an object');
|
|
64
|
+
const manifest = parsed;
|
|
65
|
+
const manifestVersion = manifest.manifest_version;
|
|
66
|
+
const extensionVersion = manifest.version;
|
|
67
|
+
if (2 !== manifestVersion && 3 !== manifestVersion) throw new extensionFromStoreError('ExtractionFailed', 'manifest_version must be 2 or 3');
|
|
68
|
+
if (!extensionVersion || 'string' != typeof extensionVersion) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json is missing a version');
|
|
69
|
+
return {
|
|
70
|
+
manifest,
|
|
71
|
+
manifestVersion,
|
|
72
|
+
extensionVersion
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function normalizeChromePlatformInfo(platform) {
|
|
76
|
+
return {
|
|
77
|
+
os: platform.os,
|
|
78
|
+
arch: platform.arch,
|
|
79
|
+
naclArch: platform.naclArch || platform.arch
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const DEFAULT_CHROME_PLATFORM = {
|
|
83
|
+
os: 'linux',
|
|
84
|
+
arch: 'x64'
|
|
85
|
+
};
|
|
86
|
+
function getChromeDownloadUrl(id, platformInfo = DEFAULT_CHROME_PLATFORM) {
|
|
87
|
+
const encoded = encodeURIComponent(id);
|
|
88
|
+
const platform = normalizeChromePlatformInfo(platformInfo);
|
|
89
|
+
const productId = 'chromiumcrx';
|
|
90
|
+
const productChannel = 'unknown';
|
|
91
|
+
const productVersion = '9999.0.9999.0';
|
|
92
|
+
return [
|
|
93
|
+
'https://clients2.google.com/service/update2/crx',
|
|
94
|
+
'?response=redirect',
|
|
95
|
+
`&os=${platform.os}`,
|
|
96
|
+
`&arch=${platform.arch}`,
|
|
97
|
+
`&os_arch=${platform.arch}`,
|
|
98
|
+
`&nacl_arch=${platform.naclArch}`,
|
|
99
|
+
`&prod=${productId}`,
|
|
100
|
+
`&prodchannel=${productChannel}`,
|
|
101
|
+
`&prodversion=${productVersion}`,
|
|
102
|
+
'&acceptformat=crx2,crx3',
|
|
103
|
+
`&x=id%3D${encoded}%26uc`
|
|
104
|
+
].join('');
|
|
105
|
+
}
|
|
106
|
+
function getEdgeDownloadUrl(id) {
|
|
107
|
+
const encoded = encodeURIComponent(id);
|
|
108
|
+
return [
|
|
109
|
+
'https://edge.microsoft.com/extensionwebstorebase/v1/crx',
|
|
110
|
+
'?response=redirect',
|
|
111
|
+
'&prodversion=109.0.0.0',
|
|
112
|
+
`&x=id%3D${encoded}%26installsource%3Dondemand%26uc`
|
|
113
|
+
].join('');
|
|
114
|
+
}
|
|
115
|
+
async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
|
|
116
|
+
const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
|
|
117
|
+
const addon = await options.requestJson(baseUrl, options);
|
|
118
|
+
const slugOrId = addon.slug || idOrSlug;
|
|
119
|
+
if (versionHint) {
|
|
120
|
+
const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
|
|
121
|
+
const version = await options.requestJson(versionUrl, options);
|
|
122
|
+
const downloadUrl = version.file?.url;
|
|
123
|
+
if (!downloadUrl) throw new extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
|
|
124
|
+
return {
|
|
125
|
+
downloadUrl,
|
|
126
|
+
version: version.version || versionHint,
|
|
127
|
+
slugOrId
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const downloadUrl = addon.current_version?.file?.url;
|
|
131
|
+
const version = addon.current_version?.version;
|
|
132
|
+
if (!downloadUrl || !version) throw new extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
133
|
+
return {
|
|
134
|
+
downloadUrl,
|
|
135
|
+
version,
|
|
136
|
+
slugOrId
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const chromePattern = /^https?:\/\/(?:chrome\.google\.com\/webstore|chromewebstore\.google\.com)\/.+?\/([a-p]{32})(?=[\/#?]|$)/i;
|
|
140
|
+
const chromeDownloadPattern = /^https?:\/\/clients2\.google\.com\/service\/update2\/crx\b.*?%3D([a-p]{32})%26uc/i;
|
|
141
|
+
const edgePattern = /^https?:\/\/microsoftedge\.microsoft\.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/i;
|
|
142
|
+
const edgeDownloadPattern = /^https?:\/\/edge\.microsoft\.com\/extensionwebstorebase\/v1\/crx\b.*?%3D([a-z]{32})%26/i;
|
|
143
|
+
const firefoxPattern = /^https?:\/\/((?:reviewers\.)?(?:addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org))\/.*?(?:addon|review)\/([^/<>"'?#]+)/i;
|
|
144
|
+
const firefoxDownloadPattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/[^?#]*\/downloads\/latest\/([^/?#]+)/i;
|
|
145
|
+
function detectStoreFromUrl(url) {
|
|
146
|
+
if (chromePattern.test(url) || chromeDownloadPattern.test(url)) return 'chrome';
|
|
147
|
+
if (edgePattern.test(url) || edgeDownloadPattern.test(url)) return 'edge';
|
|
148
|
+
if (firefoxPattern.test(url) || firefoxDownloadPattern.test(url)) return 'firefox';
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function extractChromeIdFromUrl(url) {
|
|
152
|
+
const match = chromePattern.exec(url) || chromeDownloadPattern.exec(url);
|
|
153
|
+
return match ? match[1] : null;
|
|
154
|
+
}
|
|
155
|
+
function extractEdgeIdFromUrl(url) {
|
|
156
|
+
const match = edgePattern.exec(url) || edgeDownloadPattern.exec(url);
|
|
157
|
+
return match ? match[1] : null;
|
|
158
|
+
}
|
|
159
|
+
function extractFirefoxSlugFromUrl(url) {
|
|
160
|
+
const match = firefoxPattern.exec(url) || firefoxDownloadPattern.exec(url);
|
|
161
|
+
return match ? match[2] : null;
|
|
162
|
+
}
|
|
163
|
+
function validateInput(url) {
|
|
164
|
+
if (!url || 'string' != typeof url) throw new extensionFromStoreError('InvalidInput', 'URL is required');
|
|
165
|
+
}
|
|
166
|
+
function sanitizeSegment(value, label) {
|
|
167
|
+
const sanitized = value.replace(/[\\/]/g, '-').trim();
|
|
168
|
+
if (!sanitized) throw new extensionFromStoreError('InvalidInput', `${label} is not a valid path segment`);
|
|
169
|
+
return sanitized;
|
|
170
|
+
}
|
|
171
|
+
async function resolveDownload(url, options) {
|
|
172
|
+
const store = detectStoreFromUrl(url);
|
|
173
|
+
if (!store) throw new extensionFromStoreError('UnsupportedStore', 'URL does not match a supported store');
|
|
174
|
+
if ('chrome' === store) {
|
|
175
|
+
const downloadId = extractChromeIdFromUrl(url);
|
|
176
|
+
if (!downloadId) throw new extensionFromStoreError('NotFound', 'Chrome extension id not found in URL');
|
|
177
|
+
return {
|
|
178
|
+
store,
|
|
179
|
+
downloadUrl: getChromeDownloadUrl(downloadId, options.platform),
|
|
180
|
+
archiveType: 'crx',
|
|
181
|
+
downloadId,
|
|
182
|
+
slugOrId: downloadId
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if ('edge' === store) {
|
|
186
|
+
const downloadId = extractEdgeIdFromUrl(url);
|
|
187
|
+
if (!downloadId) throw new extensionFromStoreError('NotFound', 'Edge extension id not found in URL');
|
|
188
|
+
return {
|
|
189
|
+
store,
|
|
190
|
+
downloadUrl: getEdgeDownloadUrl(downloadId),
|
|
191
|
+
archiveType: 'crx',
|
|
192
|
+
downloadId,
|
|
193
|
+
slugOrId: downloadId
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const slug = extractFirefoxSlugFromUrl(url);
|
|
197
|
+
if (!slug) throw new extensionFromStoreError('NotFound', 'Firefox extension slug not found in URL');
|
|
198
|
+
const firefox = await resolveFirefoxDownload(slug, options.version, {
|
|
199
|
+
userAgent: options.userAgent,
|
|
200
|
+
logger: options.logger,
|
|
201
|
+
requestJson: options.requestJson
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
store,
|
|
205
|
+
downloadUrl: firefox.downloadUrl,
|
|
206
|
+
archiveType: 'xpi',
|
|
207
|
+
versionHint: firefox.version,
|
|
208
|
+
slugOrId: firefox.slugOrId
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export { asExtensionFromStoreError, createLogger, detectStoreFromUrl, extensionFromStoreError, extractChromeIdFromUrl, extractEdgeIdFromUrl, extractFirefoxSlugFromUrl, getChromeDownloadUrl, getEdgeDownloadUrl, normalizeChromePlatformInfo, parseManifestInfo, resolveDownload, resolveFirefoxDownload, sanitizeSegment, stripCrxHeader, validateInput };
|