single-file-cli 2.0.19 → 2.0.20

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 CHANGED
@@ -80,9 +80,6 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
80
80
 
81
81
  ```sh
82
82
  unzip master.zip .
83
- ```
84
-
85
- ```sh
86
83
  cd single-file-cli-master
87
84
  ```
88
85
 
@@ -90,9 +87,6 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
90
87
 
91
88
  ```sh
92
89
  git clone --depth 1 --recursive https://github.com/gildas-lormeau/single-file-cli.git
93
- ```
94
-
95
- ```sh
96
90
  cd single-file-cli
97
91
  ```
98
92
 
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@single-file/single-file-cli",
3
- "version": "2.0.19",
3
+ "version": "2.0.20",
4
4
  "description": "SingleFile CLI",
5
5
  "nodeModulesDir": true,
6
6
  "exports": {
package/lib/cdp-client.js CHANGED
@@ -96,16 +96,22 @@ async function getPageData(options) {
96
96
  worldName: SINGLE_FILE_WORLD_NAME
97
97
  });
98
98
  const [contextId] = await Promise.all([
99
- navigate({ Page, Runtime }, options),
99
+ loadPage({ Page, Runtime }, options),
100
100
  options.browserDebug ? waitForDebuggerReady({ Debugger }) : Promise.resolve()
101
101
  ]);
102
+ if (options.browserWaitDelay) {
103
+ setTimeout(() => resolve, options.browserWaitDelay);
104
+ }
102
105
  const { result } = await Runtime.evaluate({
103
106
  expression: `singlefile.getPageData(${JSON.stringify(options)})`,
104
107
  awaitPromise: true,
105
108
  returnByValue: true,
106
109
  contextId
107
110
  });
108
- const { value } = result;
111
+ const { value, subtype, description } = result;
112
+ if (subtype === "error") {
113
+ throw new Error(description);
114
+ }
109
115
  if (options.compressContent) {
110
116
  value.content = new Uint8Array(value.content);
111
117
  }
@@ -126,7 +132,7 @@ async function getPageData(options) {
126
132
  }
127
133
  }
128
134
 
129
- async function navigate({ Page, Runtime }, options) {
135
+ async function loadPage({ Page, Runtime }, options) {
130
136
  await Runtime.enable();
131
137
  await Page.enable();
132
138
  try {
@@ -134,7 +140,7 @@ async function navigate({ Page, Runtime }, options) {
134
140
  const loadTimeoutAbortSignal = loadTimeoutAbortController.signal;
135
141
  const [contextId] = await Promise.race([
136
142
  Promise.all([getTopFrameContextId({ Page, Runtime }, options), Page.navigate({ url: options.url })]),
137
- options.browserLoadMaxTime ? waitForLoadTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime) : Promise.resolve()
143
+ waitForLoadTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime)
138
144
  ]);
139
145
  if (!loadTimeoutAbortSignal.aborted) {
140
146
  loadTimeoutAbortController.abort();
@@ -146,34 +152,82 @@ async function navigate({ Page, Runtime }, options) {
146
152
  }
147
153
  }
148
154
 
149
- async function getTopFrameContextId({ Page, Runtime }, options) {
150
- let frameId, contextId;
151
- await new Promise(resolve => {
155
+ function getTopFrameContextId({ Page, Runtime }, options) {
156
+ return new Promise((resolve, reject) => {
152
157
  const FRAME_NAVIGATED_EVENT = "frameNavigated";
153
158
  const CONTEXT_CREATED_EVENT = "executionContextCreated";
159
+ const CONTEXT_DESTROYED_EVENT = "executionContextDestroyed";
160
+ const contextIds = [];
161
+ let frameId, navigationAbortController;
154
162
  Page.addEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
155
- Runtime.addEventListener(CONTEXT_CREATED_EVENT, executionContextCreated);
163
+ Runtime.addEventListener(CONTEXT_CREATED_EVENT, onExecutionContextCreated);
164
+ Runtime.addEventListener(CONTEXT_DESTROYED_EVENT, onExecutionContextDestroyed);
156
165
 
157
166
  async function onFrameNavigated({ params }) {
158
167
  const { frame } = params;
159
168
  if (!frame.parentId) {
160
169
  frameId = frame.id;
161
- await waitForPageReady({ Page }, frameId, options);
170
+ if (navigationAbortController) {
171
+ navigationAbortController.abort();
172
+ }
173
+ navigationAbortController = new AbortController();
174
+ await onTopFrameNavigated(navigationAbortController.signal);
175
+ }
176
+ }
177
+
178
+ async function onTopFrameNavigated(navigationSignal) {
179
+ await waitForPageReady({ Page }, frameId, options, navigationSignal);
180
+ if (!navigationSignal.aborted) {
162
181
  Page.removeEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
163
- Runtime.removeEventListener(CONTEXT_CREATED_EVENT, executionContextCreated);
164
- resolve();
182
+ Runtime.removeEventListener(CONTEXT_CREATED_EVENT, onExecutionContextCreated);
183
+ Runtime.removeEventListener(CONTEXT_DESTROYED_EVENT, onExecutionContextDestroyed);
184
+ let contextId;
185
+ if (contextIds.length) {
186
+ let contextIdIndex = 0;
187
+ do {
188
+ if (await testExecutionContext(contextIds[contextIdIndex])) {
189
+ contextId = contextIds[contextIdIndex];
190
+ }
191
+ contextIdIndex++;
192
+ } while (contextId === undefined && contextIdIndex < contextIds.length);
193
+ }
194
+ if (contextId === undefined) {
195
+ reject(new Error("Execution context not found"));
196
+ } else {
197
+ resolve(contextId);
198
+ }
165
199
  }
166
200
  }
167
201
 
168
- function executionContextCreated({ params }) {
202
+ function onExecutionContextCreated({ params }) {
169
203
  const { context } = params;
170
204
  const { name, origin, auxData } = context;
171
205
  if (frameId !== undefined && origin === "://" && name === SINGLE_FILE_WORLD_NAME && auxData && auxData.frameId === frameId) {
172
- contextId = context.id;
206
+ contextIds.push(context.id);
207
+ }
208
+ }
209
+
210
+ function onExecutionContextDestroyed({ params }) {
211
+ const { executionContextId } = params;
212
+ const index = contextIds.indexOf(executionContextId);
213
+ if (index > -1) {
214
+ contextIds.splice(index, 1);
173
215
  }
174
216
  }
217
+
218
+ async function testExecutionContext(contextId) {
219
+ try {
220
+ const { result } = await Runtime.evaluate({
221
+ expression: "singlefile !== undefined",
222
+ contextId
223
+ });
224
+ return result.value === true;
225
+ } catch (error) {
226
+ // ignored
227
+ }
228
+ return false;
229
+ }
175
230
  });
176
- return contextId;
177
231
  }
178
232
 
179
233
  function waitForLoadTimeout(abortSignal, maxDelay) {
@@ -194,41 +248,46 @@ function waitForLoadTimeout(abortSignal, maxDelay) {
194
248
  });
195
249
  }
196
250
 
197
- async function waitForPageReady({ Page }, topFrameId, options) {
251
+ async function waitForPageReady({ Page }, topFrameId, options, navigationSignal) {
198
252
  await Page.setLifecycleEventsEnabled({ enabled: true });
199
- await new Promise(resolve => {
200
- const LIFE_CYCLE_EVENT = "lifecycleEvent";
201
- Page.addEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
202
-
203
- function onLifecycleEvent({ params }) {
204
- const { name, frameId } = params;
205
- if (frameId === topFrameId) {
206
- if ((name === options.browserWaitUntil || name === INTERACTIVE_TIME_STATE)) {
207
- Page.removeEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
208
- resolve();
253
+ try {
254
+ await new Promise(resolve => {
255
+ const LIFE_CYCLE_EVENT = "lifecycleEvent";
256
+ Page.addEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
257
+
258
+ function onLifecycleEvent({ params }) {
259
+ const { name, frameId } = params;
260
+ if (frameId === topFrameId) {
261
+ if (name === options.browserWaitUntil || name === INTERACTIVE_TIME_STATE || navigationSignal.aborted) {
262
+ Page.removeEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
263
+ resolve();
264
+ }
209
265
  }
210
266
  }
267
+ });
268
+ } finally {
269
+ if (!navigationSignal.aborted) {
270
+ await Page.setLifecycleEventsEnabled({ enabled: false });
211
271
  }
212
- });
213
- await Page.setLifecycleEventsEnabled({ enabled: false });
214
- if (options.browserWaitDelay) {
215
- setTimeout(() => resolve, options.browserWaitDelay);
216
272
  }
217
273
  }
218
274
 
219
275
  async function waitForDebuggerReady({ Debugger }) {
220
276
  await Debugger.enable();
221
- await Debugger.pause();
222
- await new Promise(resolve => {
223
- const RESUMED_EVENT = "resumed";
224
- Debugger.addEventListener(RESUMED_EVENT, onResumed);
277
+ try {
278
+ await Debugger.pause();
279
+ await new Promise(resolve => {
280
+ const RESUMED_EVENT = "resumed";
281
+ Debugger.addEventListener(RESUMED_EVENT, onResumed);
225
282
 
226
- function onResumed() {
227
- Debugger.removeEventListener(RESUMED_EVENT, onResumed);
228
- resolve();
229
- }
230
- });
231
- await Debugger.disable();
283
+ function onResumed() {
284
+ Debugger.removeEventListener(RESUMED_EVENT, onResumed);
285
+ resolve();
286
+ }
287
+ });
288
+ } finally {
289
+ await Debugger.disable();
290
+ }
232
291
  }
233
292
 
234
293
  function getBrowserOptions(options) {
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "2.0.19";
1
+ export const version = "2.0.20";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-cli",
3
- "version": "2.0.19",
3
+ "version": "2.0.20",
4
4
  "description": "SingleFile CLI",
5
5
  "author": "Gildas Lormeau",
6
6
  "engines": {