@percy/core
Advanced tools
Comparing version 1.29.1-alpha.0 to 1.29.1-beta.0
@@ -10,2 +10,6 @@ // Common config options used in Percy commands | ||
}, | ||
useSystemProxy: { | ||
type: 'boolean', | ||
default: false | ||
}, | ||
token: { | ||
@@ -588,2 +592,5 @@ type: 'string' | ||
}, | ||
cookies: { | ||
type: 'string' | ||
}, | ||
resources: { | ||
@@ -590,0 +597,0 @@ type: 'array', |
@@ -67,6 +67,7 @@ import logger from '@percy/logger'; | ||
allowedHostnames, | ||
networkIdleTimeout | ||
networkIdleTimeout, | ||
captureResponsiveAssetsEnabled | ||
} = options; | ||
let filter = r => hostnameMatches(allowedHostnames, r.url); | ||
return page.network.idle(filter, networkIdleTimeout); | ||
return page.network.idle(filter, networkIdleTimeout, captureResponsiveAssetsEnabled); | ||
} | ||
@@ -178,5 +179,40 @@ | ||
} = options; | ||
let cookies; | ||
if (process.env.PERCY_DO_NOT_USE_CAPTURED_COOKIES !== 'true') { | ||
var _snapshot$domSnapshot; | ||
cookies = snapshot === null || snapshot === void 0 ? void 0 : (_snapshot$domSnapshot = snapshot.domSnapshot) === null || _snapshot$domSnapshot === void 0 ? void 0 : _snapshot$domSnapshot.cookies; | ||
} | ||
if (typeof cookies === 'string' && cookies !== '') { | ||
cookies = cookies.split('; ').map(c => c.split('=')); | ||
cookies = cookies.map(([key, value]) => { | ||
return { | ||
name: key, | ||
value: value | ||
}; | ||
}); | ||
} | ||
// iterate over device to trigger reqeusts and capture other dpr width | ||
async function* captureResponsiveAssets() { | ||
for (const device of captureForDevices) { | ||
discovery = { | ||
...discovery, | ||
captureResponsiveAssetsEnabled: true | ||
}; | ||
// We are not adding these widths and pixels ratios in loop below because we want to explicitly reload the page after resize which we dont do below | ||
yield* captureSnapshotResources(page, { | ||
...snapshot, | ||
discovery, | ||
widths: [device.width] | ||
}, { | ||
deviceScaleFactor: device.deviceScaleFactor, | ||
mobile: true | ||
}); | ||
yield waitForDiscoveryNetworkIdle(page, discovery); | ||
} | ||
} | ||
// used to take snapshots and remove any discovered root resource | ||
let takeSnapshot = async (options, width) => { | ||
async function* takeSnapshot(options, width) { | ||
if (captureWidths) options = { | ||
@@ -187,6 +223,8 @@ ...options, | ||
let captured = await page.snapshot(options); | ||
yield* captureResponsiveAssets(); | ||
captured.resources.delete(normalizeURL(captured.url)); | ||
capture(processSnapshotResources(captured)); | ||
return captured; | ||
}; | ||
} | ||
; | ||
@@ -203,3 +241,5 @@ // used to resize the using capture options | ||
yield resizePage(snapshot.widths[0]); | ||
yield page.goto(snapshot.url); | ||
yield page.goto(snapshot.url, { | ||
cookies | ||
}); | ||
if (snapshot.execute) { | ||
@@ -233,21 +273,6 @@ // when any execute options are provided, inject snapshot options | ||
// iterate over device to trigger reqeusts and capture other dpr width | ||
if (captureForDevices) { | ||
for (const device of captureForDevices) { | ||
yield waitForDiscoveryNetworkIdle(page, discovery); | ||
// We are not adding these widths and pixels ratios in loop below because we want to explicitly reload the page after resize which we dont do below | ||
yield* captureSnapshotResources(page, { | ||
...snapshot, | ||
widths: [device.width] | ||
}, { | ||
deviceScaleFactor: device.deviceScaleFactor, | ||
mobile: true | ||
}); | ||
} | ||
} | ||
// iterate over widths to trigger reqeusts and capture other widths | ||
if (isBaseSnapshot || captureWidths) { | ||
for (let i = 0; i < widths.length - 1; i++) { | ||
if (captureWidths) yield takeSnapshot(snap, width); | ||
if (captureWidths) yield* takeSnapshot(snap, width); | ||
yield page.evaluate(execute === null || execute === void 0 ? void 0 : execute.beforeResize); | ||
@@ -261,3 +286,3 @@ yield waitForDiscoveryNetworkIdle(page, discovery); | ||
// capture this snapshot and update the base snapshot after capture | ||
let captured = yield takeSnapshot(snap, width); | ||
let captured = yield* takeSnapshot(snap, width); | ||
if (isBaseSnapshot) baseSnapshot = captured; | ||
@@ -281,2 +306,3 @@ | ||
yield waitForDiscoveryNetworkIdle(page, discovery); | ||
yield* captureResponsiveAssets(); | ||
capture(processSnapshotResources(snapshot)); | ||
@@ -283,0 +309,0 @@ } |
import { request as makeRequest } from '@percy/client/utils'; | ||
import logger from '@percy/logger'; | ||
import mime from 'mime-types'; | ||
import { DefaultMap, createResource, hostnameMatches, normalizeURL, waitFor } from './utils.js'; | ||
import { DefaultMap, createResource, decodeAndEncodeURLWithLogging, hostnameMatches, normalizeURL, waitFor } from './utils.js'; | ||
const MAX_RESOURCE_SIZE = 25 * 1024 ** 2; // 25MB | ||
@@ -74,3 +74,3 @@ const ALLOWED_STATUSES = [200, 201, 301, 302, 304, 307, 308]; | ||
// Resolves after the timeout when there are no more in-flight requests. | ||
async idle(filter = () => true, timeout = this.timeout) { | ||
async idle(filter = () => true, timeout = this.timeout, captureResponsiveAssetsEnabled = false) { | ||
let requests = []; | ||
@@ -95,3 +95,5 @@ this.log.debug(`Wait for ${timeout}ms idle`, this.meta); | ||
if (error.message.startsWith('Timeout')) { | ||
this._throwTimeoutError('Timed out waiting for network requests to idle.', filter); | ||
let message = 'Timed out waiting for network requests to idle.'; | ||
if (captureResponsiveAssetsEnabled) message += '\nWhile capturing responsive assets try setting PERCY_DO_NOT_CAPTURE_RESPONSIVE_ASSETS to true.'; | ||
this._throwTimeoutError(message, filter); | ||
} else { | ||
@@ -101,3 +103,15 @@ throw error; | ||
}); | ||
// After waiting for network to idle check if there are still some request | ||
const activeRequests = this.getActiveRequests(filter); | ||
/* istanbul ignore if: race condition, very hard to mock this */ | ||
if (activeRequests.length > 0) { | ||
this.log.debug(`There are ${activeRequests.length} active requests pending during asset discovery. Try increasing the networkIdleTimeout to resolve this issue. \n ${activeRequests}`); | ||
} | ||
} | ||
getActiveRequests(filter) { | ||
let requests = Array.from(this.#requests.values()).filter(filter); | ||
requests = requests.filter(req => !this.#finishedUrls.has(req.url)); | ||
return requests; | ||
} | ||
@@ -205,2 +219,13 @@ // Validates that requestId is still valid as sometimes request gets cancelled and we have already executed | ||
if (request.url.startsWith('data:')) return; | ||
// Browsers handle URL encoding leniently, but invalid characters can break tools like Jackproxy. | ||
// This code checks for issues such as `%` and leading spaces and warns the user accordingly. | ||
decodeAndEncodeURLWithLogging(request.url, this.log, { | ||
meta: { | ||
...this.meta, | ||
url: request.url | ||
}, | ||
shouldLogWarning: true, | ||
warningMessage: `An invalid URL was detected for url: ${request.url} - the snapshot may fail on Percy. Please verify that your asset URL is valid.` | ||
}); | ||
if (this.intercept) { | ||
@@ -207,0 +232,0 @@ this.#pending.set(requestId, event); |
@@ -44,15 +44,34 @@ import fs from 'fs'; | ||
} | ||
mergeCookies(userPassedCookie, autoCapturedCookie) { | ||
if (!autoCapturedCookie) return userPassedCookie; | ||
if (userPassedCookie.length === 0) return autoCapturedCookie; | ||
// User passed cookie will be prioritized over auto captured cookie | ||
const mergedCookies = [...userPassedCookie, ...autoCapturedCookie]; | ||
const uniqueCookies = []; | ||
const names = new Set(); | ||
for (const cookie of mergedCookies) { | ||
if (!names.has(cookie.name)) { | ||
uniqueCookies.push(cookie); | ||
names.add(cookie.name); | ||
} | ||
} | ||
return uniqueCookies; | ||
} | ||
// Go to a URL and wait for navigation to occur | ||
async goto(url, { | ||
waitUntil = 'load' | ||
waitUntil = 'load', | ||
cookies | ||
} = {}) { | ||
this.log.debug(`Navigate to: ${url}`, this.meta); | ||
let navigate = async () => { | ||
const userPassedCookie = this.session.browser.cookies; | ||
// set cookies before navigation so we can default the domain to this hostname | ||
if (this.session.browser.cookies.length) { | ||
if (userPassedCookie.length || cookies) { | ||
let defaultDomain = hostname(url); | ||
cookies = this.mergeCookies(userPassedCookie, cookies); | ||
await this.session.send('Network.setCookies', { | ||
// spread is used to make a shallow copy of the cookie | ||
cookies: this.session.browser.cookies.map(({ | ||
cookies: cookies.map(({ | ||
...cookie | ||
@@ -59,0 +78,0 @@ }) => { |
@@ -7,5 +7,9 @@ function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } | ||
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } | ||
function _classPrivateMethodGet(s, a, r) { return _assertClassBrand(a, s), r; } | ||
function _classPrivateFieldGet2(e, t) { var r = _classPrivateFieldGet(t, e); return _classApplyDescriptorGet(e, r); } | ||
function _classApplyDescriptorGet(e, t) { return t.get ? t.get.call(e) : t.value; } | ||
function _classPrivateFieldSet(e, t, r) { var s = _classPrivateFieldGet(t, e); return _classApplyDescriptorSet(e, s, r), r; } | ||
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } | ||
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } | ||
function _classApplyDescriptorSet(e, t, l) { if (t.set) t.set.call(e, l);else { if (!t.writable) throw new TypeError("attempted to set read only private field"); t.value = l; } } | ||
import PercyClient from '@percy/client'; | ||
@@ -17,3 +21,3 @@ import PercyConfig from '@percy/config'; | ||
import Pako from 'pako'; | ||
import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets } from './utils.js'; | ||
import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets, detectSystemProxyAndLog } from './utils.js'; | ||
import { createPercyServer, createStaticServer } from './api.js'; | ||
@@ -29,3 +33,4 @@ import { gatherSnapshots, createSnapshotsQueue, validateSnapshotOptions } from './snapshot.js'; | ||
var _snapshots = /*#__PURE__*/new WeakMap(); | ||
var _Percy_brand = /*#__PURE__*/new WeakSet(); | ||
var _displaySuggestionLogs = /*#__PURE__*/new WeakSet(); | ||
var _proxyEnabled = /*#__PURE__*/new WeakSet(); | ||
export class Percy { | ||
@@ -69,7 +74,14 @@ // Static shortcut to create and start an instance in one call | ||
var _config$percy, _config$percy2; | ||
_classPrivateMethodInitSpec(this, _Percy_brand); | ||
_classPrivateMethodInitSpec(this, _proxyEnabled); | ||
_classPrivateMethodInitSpec(this, _displaySuggestionLogs); | ||
_defineProperty(this, "log", logger('core')); | ||
_defineProperty(this, "readyState", null); | ||
_classPrivateFieldInitSpec(this, _discovery, null); | ||
_classPrivateFieldInitSpec(this, _snapshots, null); | ||
_classPrivateFieldInitSpec(this, _discovery, { | ||
writable: true, | ||
value: null | ||
}); | ||
_classPrivateFieldInitSpec(this, _snapshots, { | ||
writable: true, | ||
value: null | ||
}); | ||
let config = PercyConfig.load({ | ||
@@ -102,4 +114,4 @@ overrides: _options, | ||
this.browser = new Browser(this); | ||
_classPrivateFieldSet(_discovery, this, createDiscoveryQueue(this)); | ||
_classPrivateFieldSet(_snapshots, this, createSnapshotsQueue(this)); | ||
_classPrivateFieldSet(this, _discovery, createDiscoveryQueue(this)); | ||
_classPrivateFieldSet(this, _snapshots, createSnapshotsQueue(this)); | ||
@@ -157,6 +169,6 @@ // generator methods are wrapped to autorun and return promises | ||
} = this.config.discovery; | ||
_classPrivateFieldGet(_discovery, this).set({ | ||
_classPrivateFieldGet2(this, _discovery).set({ | ||
concurrency | ||
}); | ||
_classPrivateFieldGet(_snapshots, this).set({ | ||
_classPrivateFieldGet2(this, _snapshots).set({ | ||
concurrency | ||
@@ -176,6 +188,9 @@ }); | ||
} | ||
// Not awaiting proxy check as this can be asyncronous when not enabled | ||
const detectProxy = detectSystemProxyAndLog(this.config.percy.useSystemProxy); | ||
if (this.config.percy.useSystemProxy) await detectProxy; | ||
// start the snapshots queue immediately when not delayed or deferred | ||
if (!this.delayUploads && !this.deferUploads) yield _classPrivateFieldGet(_snapshots, this).start(); | ||
if (!this.delayUploads && !this.deferUploads) yield _classPrivateFieldGet2(this, _snapshots).start(); | ||
// do not start the discovery queue when not needed | ||
if (!this.skipDiscovery) yield _classPrivateFieldGet(_discovery, this).start(); | ||
if (!this.skipDiscovery) yield _classPrivateFieldGet2(this, _discovery).start(); | ||
// start a local API server for SDK communication | ||
@@ -198,4 +213,4 @@ if (this.server) yield this.server.listen(); | ||
await ((_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.close()); | ||
await _classPrivateFieldGet(_discovery, this).end(); | ||
await _classPrivateFieldGet(_snapshots, this).end(); | ||
await _classPrivateFieldGet2(this, _discovery).end(); | ||
await _classPrivateFieldGet2(this, _snapshots).end(); | ||
@@ -219,4 +234,4 @@ // mark this instance as closed unless aborting | ||
async *idle() { | ||
yield* _classPrivateFieldGet(_discovery, this).idle(); | ||
yield* _classPrivateFieldGet(_snapshots, this).idle(); | ||
yield* _classPrivateFieldGet2(this, _discovery).idle(); | ||
yield* _classPrivateFieldGet2(this, _snapshots).idle(); | ||
} | ||
@@ -234,9 +249,9 @@ | ||
// flush and log progress for discovery before snapshots | ||
if (!this.skipDiscovery && _classPrivateFieldGet(_discovery, this).size) { | ||
if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet(_discovery, this).process(o)));else yield* _classPrivateFieldGet(_discovery, this).flush(size => callback === null || callback === void 0 ? void 0 : callback('Processing', size)); | ||
if (!this.skipDiscovery && _classPrivateFieldGet2(this, _discovery).size) { | ||
if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet2(this, _discovery).process(o)));else yield* _classPrivateFieldGet2(this, _discovery).flush(size => callback === null || callback === void 0 ? void 0 : callback('Processing', size)); | ||
} | ||
// flush and log progress for snapshot uploads | ||
if (!this.skipUploads && _classPrivateFieldGet(_snapshots, this).size) { | ||
if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet(_snapshots, this).process(o)));else yield* _classPrivateFieldGet(_snapshots, this).flush(size => callback === null || callback === void 0 ? void 0 : callback('Uploading', size)); | ||
if (!this.skipUploads && _classPrivateFieldGet2(this, _snapshots).size) { | ||
if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet2(this, _snapshots).process(o)));else yield* _classPrivateFieldGet2(this, _snapshots).flush(size => callback === null || callback === void 0 ? void 0 : callback('Uploading', size)); | ||
} | ||
@@ -260,4 +275,4 @@ } | ||
if (force) { | ||
_classPrivateFieldGet(_discovery, this).close(true); | ||
_classPrivateFieldGet(_snapshots, this).close(true); | ||
_classPrivateFieldGet2(this, _discovery).close(true); | ||
_classPrivateFieldGet2(this, _snapshots).close(true); | ||
} | ||
@@ -287,4 +302,4 @@ | ||
// if dry-running, log the total number of snapshots | ||
if (this.dryRun && _classPrivateFieldGet(_snapshots, this).size) { | ||
this.log.info(info('Found', _classPrivateFieldGet(_snapshots, this).size)); | ||
if (this.dryRun && _classPrivateFieldGet2(this, _snapshots).size) { | ||
this.log.info(info('Found', _classPrivateFieldGet2(this, _snapshots).size)); | ||
} | ||
@@ -294,4 +309,4 @@ | ||
await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close()); | ||
await _classPrivateFieldGet(_discovery, this).end(); | ||
await _classPrivateFieldGet(_snapshots, this).end(); | ||
await _classPrivateFieldGet2(this, _discovery).end(); | ||
await _classPrivateFieldGet2(this, _snapshots).end(); | ||
@@ -359,3 +374,3 @@ // mark instance as stopped | ||
// gather snapshots and discover snapshot resources | ||
yield* discoverSnapshotResources(_classPrivateFieldGet(_discovery, this), { | ||
yield* discoverSnapshotResources(_classPrivateFieldGet2(this, _discovery), { | ||
skipDiscovery: this.skipDiscovery, | ||
@@ -380,3 +395,3 @@ dryRun: this.dryRun, | ||
// push each finished snapshot to the snapshots queue | ||
_classPrivateFieldGet(_snapshots, this).push(snapshot); | ||
_classPrivateFieldGet2(this, _snapshots).push(snapshot); | ||
}); | ||
@@ -441,5 +456,5 @@ } finally { | ||
try { | ||
return yield* yieldTo(_classPrivateFieldGet(_snapshots, this).push(options)); | ||
return yield* yieldTo(_classPrivateFieldGet2(this, _snapshots).push(options)); | ||
} catch (error) { | ||
_classPrivateFieldGet(_snapshots, this).cancel(options); | ||
_classPrivateFieldGet2(this, _snapshots).cancel(options); | ||
// Detecting and suggesting fix for errors; | ||
@@ -494,3 +509,3 @@ await this.suggestionsForFix(error.message); | ||
// This is the case for No snapshot command called | ||
_assertClassBrand(_Percy_brand, this, _displaySuggestionLogs).call(this, [{ | ||
_classPrivateMethodGet(this, _displaySuggestionLogs, _displaySuggestionLogs2).call(this, [{ | ||
failure_reason: 'Snapshot command was not called', | ||
@@ -506,3 +521,3 @@ reason_message: 'Snapshot Command was not called. please check your CI for errors', | ||
const suggestionResponse = await this.client.getErrorAnalysis(errors); | ||
_assertClassBrand(_Percy_brand, this, _displaySuggestionLogs).call(this, suggestionResponse, options); | ||
_classPrivateMethodGet(this, _displaySuggestionLogs, _displaySuggestionLogs2).call(this, suggestionResponse, options); | ||
} catch (e) { | ||
@@ -514,3 +529,3 @@ // Common error code for Proxy issues | ||
this.log.error('percy.io might not be reachable, check network connection, proxy and ensure that percy.io is whitelisted.'); | ||
if (!_assertClassBrand(_Percy_brand, this, _proxyEnabled).call(this)) { | ||
if (!_classPrivateMethodGet(this, _proxyEnabled, _proxyEnabled2).call(this)) { | ||
this.log.error('If inside a proxied envirnment, please configure the following environment variables: HTTP_PROXY, [ and optionally HTTPS_PROXY if you need it ]. Refer to our documentation for more details'); | ||
@@ -554,3 +569,3 @@ } | ||
} | ||
function _displaySuggestionLogs(suggestions, options = {}) { | ||
function _displaySuggestionLogs2(suggestions, options = {}) { | ||
if (!(suggestions !== null && suggestions !== void 0 && suggestions.length)) return; | ||
@@ -578,3 +593,3 @@ suggestions.forEach(item => { | ||
} | ||
function _proxyEnabled() { | ||
function _proxyEnabled2() { | ||
return !!(getProxy({ | ||
@@ -581,0 +596,0 @@ protocol: 'https:' |
@@ -7,5 +7,9 @@ function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } | ||
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } | ||
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } | ||
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _classPrivateFieldSet(e, t, r) { var s = _classPrivateFieldGet2(t, e); return _classApplyDescriptorSet(e, s, r), r; } | ||
function _classApplyDescriptorSet(e, t, l) { if (t.set) t.set.call(e, l);else { if (!t.writable) throw new TypeError("attempted to set read only private field"); t.value = l; } } | ||
function _classPrivateMethodGet(s, a, r) { return _assertClassBrand(a, s), r; } | ||
function _classPrivateFieldGet(e, t) { var r = _classPrivateFieldGet2(t, e); return _classApplyDescriptorGet(e, r); } | ||
function _classPrivateFieldGet2(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } | ||
function _classApplyDescriptorGet(e, t) { return t.get ? t.get.call(e) : t.value; } | ||
import { yieldFor, generatePromise, AbortController } from './utils.js'; | ||
@@ -43,21 +47,38 @@ import logger from '@percy/logger'; | ||
var _pending = /*#__PURE__*/new WeakMap(); | ||
var _Queue_brand = /*#__PURE__*/new WeakSet(); | ||
var _dequeue = /*#__PURE__*/new WeakSet(); | ||
var _find = /*#__PURE__*/new WeakSet(); | ||
var _start = /*#__PURE__*/new WeakMap(); | ||
var _end = /*#__PURE__*/new WeakMap(); | ||
var _process = /*#__PURE__*/new WeakSet(); | ||
var _until = /*#__PURE__*/new WeakSet(); | ||
export class Queue { | ||
// item concurrency | ||
constructor(name) { | ||
// Maybe processes the next queued item task. | ||
_classPrivateMethodInitSpec(this, _Queue_brand); | ||
// item concurrency | ||
_classPrivateMethodInitSpec(this, _until); | ||
_classPrivateMethodInitSpec(this, _process); | ||
_classPrivateMethodInitSpec(this, _find); | ||
_classPrivateMethodInitSpec(this, _dequeue); | ||
_defineProperty(this, "concurrency", 10); | ||
_defineProperty(this, "log", logger('core:queue')); | ||
// Configure queue handlers | ||
_classPrivateFieldInitSpec(this, _handlers, {}); | ||
// internal queues | ||
_classPrivateFieldInitSpec(this, _queued, new Set()); | ||
_classPrivateFieldInitSpec(this, _pending, new Set()); | ||
// keep track of start and end tasks | ||
_classPrivateFieldInitSpec(this, _start, null); | ||
_classPrivateFieldInitSpec(this, _end, null); | ||
// represents various queue states such as ready, running, or closed | ||
_classPrivateFieldInitSpec(this, _handlers, { | ||
writable: true, | ||
value: {} | ||
}); | ||
_classPrivateFieldInitSpec(this, _queued, { | ||
writable: true, | ||
value: new Set() | ||
}); | ||
_classPrivateFieldInitSpec(this, _pending, { | ||
writable: true, | ||
value: new Set() | ||
}); | ||
_classPrivateFieldInitSpec(this, _start, { | ||
writable: true, | ||
value: null | ||
}); | ||
_classPrivateFieldInitSpec(this, _end, { | ||
writable: true, | ||
value: null | ||
}); | ||
_defineProperty(this, "readyState", 0); | ||
@@ -74,9 +95,15 @@ this.name = name; | ||
} | ||
// Configure queue handlers | ||
handle(event, handler) { | ||
_classPrivateFieldGet(_handlers, this)[event] = handler; | ||
_classPrivateFieldGet(this, _handlers)[event] = handler; | ||
return this; | ||
} | ||
// internal queues | ||
// Queue size is total queued and pending items | ||
get size() { | ||
return _classPrivateFieldGet(_queued, this).size + _classPrivateFieldGet(_pending, this).size; | ||
return _classPrivateFieldGet(this, _queued).size + _classPrivateFieldGet(this, _pending).size; | ||
} | ||
@@ -92,4 +119,4 @@ | ||
task.deferred = task.deferred.catch(e => { | ||
if (!_classPrivateFieldGet(_handlers, this).error) throw e; | ||
return _classPrivateFieldGet(_handlers, this).error(item, e); | ||
if (!_classPrivateFieldGet(this, _handlers).error) throw e; | ||
return _classPrivateFieldGet(this, _handlers).error(item, e); | ||
}); | ||
@@ -112,8 +139,8 @@ | ||
} | ||
task.item = item = _classPrivateFieldGet(_handlers, this).push ? _classPrivateFieldGet(_handlers, this).push(item, exists) : item; | ||
task.handler = () => _classPrivateFieldGet(_handlers, this).task ? _classPrivateFieldGet(_handlers, this).task(item, ...args) : item; | ||
task.item = item = _classPrivateFieldGet(this, _handlers).push ? _classPrivateFieldGet(this, _handlers).push(item, exists) : item; | ||
task.handler = () => _classPrivateFieldGet(this, _handlers).task ? _classPrivateFieldGet(this, _handlers).task(item, ...args) : item; | ||
// queue this task & maybe dequeue the next task | ||
_classPrivateFieldGet(_queued, this).add(task); | ||
_assertClassBrand(_Queue_brand, this, _dequeue).call(this); | ||
_classPrivateFieldGet(this, _queued).add(task); | ||
_classPrivateMethodGet(this, _dequeue, _dequeue2).call(this); | ||
@@ -123,8 +150,11 @@ // return the deferred task promise | ||
} | ||
// Maybe processes the next queued item task. | ||
// Cancels and aborts a specific item task. | ||
cancel(item) { | ||
let task = _assertClassBrand(_Queue_brand, this, _find).call(this, item); | ||
let task = _classPrivateMethodGet(this, _find, _find2).call(this, item); | ||
task === null || task === void 0 ? void 0 : task.ctrl.abort(); | ||
let queued = _classPrivateFieldGet(_queued, this).delete(task); | ||
let pending = _classPrivateFieldGet(_pending, this).delete(task); | ||
let queued = _classPrivateFieldGet(this, _queued).delete(task); | ||
let pending = _classPrivateFieldGet(this, _pending).delete(task); | ||
@@ -142,12 +172,13 @@ // reject queued tasks that are not pending | ||
// keep track of start and end tasks | ||
// Initialize a starting task or return an existing one. | ||
start() { | ||
var _classPrivateFieldGet2; | ||
_classPrivateFieldGet(_start, this) ?? _classPrivateFieldSet(_start, this, deferred({ | ||
var _classPrivateFieldGet3; | ||
_classPrivateFieldGet(this, _start) ?? _classPrivateFieldSet(this, _start, deferred({ | ||
readyState: 1 | ||
})); | ||
(_classPrivateFieldGet2 = _classPrivateFieldGet(_start, this)).handler ?? (_classPrivateFieldGet2.handler = _classPrivateFieldGet(_end, this) | ||
// wait for any ending task to complete first | ||
? () => _classPrivateFieldGet(_end, this).promise.then(_classPrivateFieldGet(_handlers, this).start) : _classPrivateFieldGet(_handlers, this).start); | ||
return _assertClassBrand(_Queue_brand, this, _process).call(this, _classPrivateFieldGet(_start, this)).deferred; | ||
(_classPrivateFieldGet3 = _classPrivateFieldGet(this, _start)).handler ?? (_classPrivateFieldGet3.handler = _classPrivateFieldGet(this, _end) // wait for any ending task to complete first | ||
? () => _classPrivateFieldGet(this, _end).promise.then(_classPrivateFieldGet(this, _handlers).start) : _classPrivateFieldGet(this, _handlers).start); | ||
return _classPrivateMethodGet(this, _process, _process2).call(this, _classPrivateFieldGet(this, _start)).deferred; | ||
} | ||
@@ -157,18 +188,20 @@ | ||
end() { | ||
var _classPrivateFieldGet3; | ||
_classPrivateFieldGet(_end, this) ?? _classPrivateFieldSet(_end, this, deferred({ | ||
var _classPrivateFieldGet4; | ||
_classPrivateFieldGet(this, _end) ?? _classPrivateFieldSet(this, _end, deferred({ | ||
readyState: 0 | ||
})); | ||
(_classPrivateFieldGet3 = _classPrivateFieldGet(_end, this)).handler ?? (_classPrivateFieldGet3.handler = _classPrivateFieldGet(_start, this) | ||
// wait for any starting task to complete first | ||
? () => _classPrivateFieldGet(_start, this).promise.then(_classPrivateFieldGet(_handlers, this).end) : _classPrivateFieldGet(_handlers, this).end); | ||
return _assertClassBrand(_Queue_brand, this, _process).call(this, _classPrivateFieldGet(_end, this)).deferred; | ||
(_classPrivateFieldGet4 = _classPrivateFieldGet(this, _end)).handler ?? (_classPrivateFieldGet4.handler = _classPrivateFieldGet(this, _start) // wait for any starting task to complete first | ||
? () => _classPrivateFieldGet(this, _start).promise.then(_classPrivateFieldGet(this, _handlers).end) : _classPrivateFieldGet(this, _handlers).end); | ||
return _classPrivateMethodGet(this, _process, _process2).call(this, _classPrivateFieldGet(this, _end)).deferred; | ||
} | ||
// represents various queue states such as ready, running, or closed | ||
// run the queue, starting it if necessary, and start dequeuing tasks | ||
run() { | ||
if (!_classPrivateFieldGet(_start, this)) this.start(); | ||
if (!_classPrivateFieldGet(this, _start)) this.start(); | ||
// when starting, state is updated afterwards | ||
if (this.readyState === 0) _classPrivateFieldGet(_start, this).readyState = 2; | ||
if (this.readyState === 0) _classPrivateFieldGet(this, _start).readyState = 2; | ||
if (this.readyState === 1) this.readyState = 2; | ||
while (_assertClassBrand(_Queue_brand, this, _dequeue).call(this)) _assertClassBrand(_Queue_brand, this, _dequeue).call(this); | ||
while (_classPrivateMethodGet(this, _dequeue, _dequeue2).call(this)) _classPrivateMethodGet(this, _dequeue, _dequeue2).call(this); | ||
return this; | ||
@@ -185,5 +218,5 @@ } | ||
close(abort) { | ||
var _classPrivateFieldGet4; | ||
var _classPrivateFieldGet5; | ||
// when starting, state is updated afterwards | ||
if ((_classPrivateFieldGet4 = _classPrivateFieldGet(_start, this)) !== null && _classPrivateFieldGet4 !== void 0 && _classPrivateFieldGet4.pending) _classPrivateFieldGet(_start, this).readyState = 3; | ||
if ((_classPrivateFieldGet5 = _classPrivateFieldGet(this, _start)) !== null && _classPrivateFieldGet5 !== void 0 && _classPrivateFieldGet5.pending) _classPrivateFieldGet(this, _start).readyState = 3; | ||
if (this.readyState < 3) this.readyState = 3; | ||
@@ -196,5 +229,5 @@ if (abort) this.clear(); | ||
clear() { | ||
let tasks = [..._classPrivateFieldGet(_queued, this)]; | ||
this.log.debug(`Clearing ${this.name} queue, queued state: ${_classPrivateFieldGet(_queued, this).size}, pending state: ${_classPrivateFieldGet(_pending, this).size}`); | ||
_classPrivateFieldGet(_queued, this).clear(); | ||
let tasks = [..._classPrivateFieldGet(this, _queued)]; | ||
this.log.debug(`Clearing ${this.name} queue, queued state: ${_classPrivateFieldGet(this, _queued).size}, pending state: ${_classPrivateFieldGet(this, _pending).size}`); | ||
_classPrivateFieldGet(this, _queued).clear(); | ||
for (let task of tasks) { | ||
@@ -208,6 +241,6 @@ task.ctrl.abort(); | ||
process(item) { | ||
var _classPrivateFieldGet5; | ||
let task = _assertClassBrand(_Queue_brand, this, _find).call(this, item); | ||
if (task && !_classPrivateFieldGet(_start, this)) this.start(); | ||
(_classPrivateFieldGet5 = _classPrivateFieldGet(_start, this)) === null || _classPrivateFieldGet5 === void 0 ? void 0 : _classPrivateFieldGet5.promise.then(() => _assertClassBrand(_Queue_brand, this, _process).call(this, task)); | ||
var _classPrivateFieldGet6; | ||
let task = _classPrivateMethodGet(this, _find, _find2).call(this, item); | ||
if (task && !_classPrivateFieldGet(this, _start)) this.start(); | ||
(_classPrivateFieldGet6 = _classPrivateFieldGet(this, _start)) === null || _classPrivateFieldGet6 === void 0 ? void 0 : _classPrivateFieldGet6.promise.then(() => _classPrivateMethodGet(this, _process, _process2).call(this, task)); | ||
return task === null || task === void 0 ? void 0 : task.deferred; | ||
@@ -222,6 +255,6 @@ } | ||
return yieldFor(() => { | ||
var _classPrivateFieldGet6; | ||
callback === null || callback === void 0 ? void 0 : callback(_classPrivateFieldGet(_pending, this).size); | ||
let starting = ((_classPrivateFieldGet6 = _classPrivateFieldGet(_start, this)) === null || _classPrivateFieldGet6 === void 0 ? void 0 : _classPrivateFieldGet6.pending) === true; | ||
return !starting && !_classPrivateFieldGet(_pending, this).size; | ||
var _classPrivateFieldGet7; | ||
callback === null || callback === void 0 ? void 0 : callback(_classPrivateFieldGet(this, _pending).size); | ||
let starting = ((_classPrivateFieldGet7 = _classPrivateFieldGet(this, _start)) === null || _classPrivateFieldGet7 === void 0 ? void 0 : _classPrivateFieldGet7.pending) === true; | ||
return !starting && !_classPrivateFieldGet(this, _pending).size; | ||
}, { | ||
@@ -235,9 +268,9 @@ idle: 10 | ||
flush(callback) { | ||
this.log.debug(`Flushing ${this.name} queue, queued state: ${_classPrivateFieldGet(_queued, this).size}, pending state: ${_classPrivateFieldGet(_pending, this).size}`); | ||
this.log.debug(`Flushing ${this.name} queue, queued state: ${_classPrivateFieldGet(this, _queued).size}, pending state: ${_classPrivateFieldGet(this, _pending).size}`); | ||
let interrupt = | ||
// check for existing interrupts | ||
[..._classPrivateFieldGet(_pending, this)].find(t => t.stop) ?? [..._classPrivateFieldGet(_queued, this)].find(t => t.stop); | ||
[..._classPrivateFieldGet(this, _pending)].find(t => t.stop) ?? [..._classPrivateFieldGet(this, _queued)].find(t => t.stop); | ||
// get the latest queued or pending task to track | ||
let flush = [..._classPrivateFieldGet(_queued, this)].pop() ?? [..._classPrivateFieldGet(_pending, this)].pop(); | ||
let flush = [..._classPrivateFieldGet(this, _queued)].pop() ?? [..._classPrivateFieldGet(this, _pending)].pop(); | ||
// determine if the queue should be stopped after flushing | ||
@@ -248,3 +281,3 @@ if (flush) flush.stop = (interrupt === null || interrupt === void 0 ? void 0 : interrupt.stop) ?? this.readyState < 2; | ||
// start the queue if not started | ||
if (!_classPrivateFieldGet(_start, this)) this.start(); | ||
if (!_classPrivateFieldGet(this, _start)) this.start(); | ||
// run the queue if stopped | ||
@@ -254,3 +287,3 @@ if (flush !== null && flush !== void 0 && flush.stop) this.run(); | ||
// will yield with the callback until done flushing | ||
return _assertClassBrand(_Queue_brand, this, _until).call(this, flush, callback); | ||
return _classPrivateMethodGet(this, _until, _until2).call(this, flush, callback); | ||
} | ||
@@ -260,14 +293,14 @@ | ||
} | ||
function _dequeue() { | ||
if (!_classPrivateFieldGet(_queued, this).size || this.readyState < 2) return; | ||
if (_classPrivateFieldGet(_pending, this).size >= this.concurrency) return; | ||
let [task] = _classPrivateFieldGet(_queued, this); | ||
return _assertClassBrand(_Queue_brand, this, _process).call(this, task); | ||
function _dequeue2() { | ||
if (!_classPrivateFieldGet(this, _queued).size || this.readyState < 2) return; | ||
if (_classPrivateFieldGet(this, _pending).size >= this.concurrency) return; | ||
let [task] = _classPrivateFieldGet(this, _queued); | ||
return _classPrivateMethodGet(this, _process, _process2).call(this, task); | ||
} | ||
function _find(subject) { | ||
let find = _classPrivateFieldGet(_handlers, this).find | ||
function _find2(subject) { | ||
let find = _classPrivateFieldGet(this, _handlers).find | ||
// use any configured find handler to match items | ||
? ({ | ||
item | ||
}) => _classPrivateFieldGet(_handlers, this).find(subject, item) : ({ | ||
}) => _classPrivateFieldGet(this, _handlers).find(subject, item) : ({ | ||
item | ||
@@ -277,15 +310,15 @@ }) => subject === item; | ||
// look at queued then pending items | ||
[..._classPrivateFieldGet(_queued, this)].find(find) ?? [..._classPrivateFieldGet(_pending, this)].find(find) | ||
[..._classPrivateFieldGet(this, _queued)].find(find) ?? [..._classPrivateFieldGet(this, _pending)].find(find) | ||
); | ||
} | ||
function _process(task) { | ||
function _process2(task) { | ||
var _task$ctrl; | ||
if (!task || task.promise) return task; | ||
let queued = _classPrivateFieldGet(_queued, this).has(task); | ||
let queued = _classPrivateFieldGet(this, _queued).has(task); | ||
// remove queued tasks from the queue | ||
if (queued) _classPrivateFieldGet(_queued, this).delete(task); | ||
if (queued) _classPrivateFieldGet(this, _queued).delete(task); | ||
// clear queued tasks when ending | ||
if (task === _classPrivateFieldGet(_end, this)) this.clear(); | ||
if (task === _classPrivateFieldGet(this, _end)) this.clear(); | ||
// add queued tasks to pending queue | ||
if (queued) _classPrivateFieldGet(_pending, this).add(task); | ||
if (queued) _classPrivateFieldGet(this, _pending).add(task); | ||
// stop the queue when necessary | ||
@@ -299,7 +332,7 @@ if (task.stop) this.stop(); | ||
// clean up pending tasks that have not been aborted | ||
if (queued && !task.ctrl.signal.aborted) _classPrivateFieldGet(_pending, this).delete(task); | ||
if (queued && !task.ctrl.signal.aborted) _classPrivateFieldGet(this, _pending).delete(task); | ||
// update queue state when necessary | ||
if (task.readyState != null) this.readyState = task.readyState; | ||
// clean up internal tasks after ending | ||
if (!this.readyState) _classPrivateFieldSet(_start, this, _classPrivateFieldSet(_end, this, null)); | ||
if (!this.readyState) _classPrivateFieldSet(this, _start, _classPrivateFieldSet(this, _end, null)); | ||
// resolve or reject the deferred task promise | ||
@@ -314,11 +347,11 @@ task[err ? 'reject' : 'resolve'](err ?? val); | ||
} | ||
async function* _until(task, callback) { | ||
async function* _until2(task, callback) { | ||
try { | ||
yield* yieldFor(() => { | ||
var _classPrivateFieldGet7; | ||
if ((_classPrivateFieldGet7 = _classPrivateFieldGet(_start, this)) !== null && _classPrivateFieldGet7 !== void 0 && _classPrivateFieldGet7.pending) return false; | ||
var _classPrivateFieldGet8; | ||
if ((_classPrivateFieldGet8 = _classPrivateFieldGet(this, _start)) !== null && _classPrivateFieldGet8 !== void 0 && _classPrivateFieldGet8.pending) return false; | ||
let queued, | ||
pending = _classPrivateFieldGet(_pending, this).size; | ||
pending = _classPrivateFieldGet(this, _pending).size; | ||
// calculate the position within queued when not pending | ||
if (task && task.pending == null) queued = positionOf(_classPrivateFieldGet(_queued, this), task); | ||
if (task && task.pending == null) queued = positionOf(_classPrivateFieldGet(this, _queued), task); | ||
// call the callback and return true when not queued or pending | ||
@@ -325,0 +358,0 @@ let position = (queued ?? 0) + pending; |
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } | ||
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } | ||
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } | ||
function _classPrivateFieldGet2(e, t) { var r = _classPrivateFieldGet(t, e); return _classApplyDescriptorGet(e, r); } | ||
function _classApplyDescriptorGet(e, t) { return t.get ? t.get.call(e) : t.value; } | ||
function _classPrivateMethodGet(s, a, r) { return _assertClassBrand(a, s), r; } | ||
function _classPrivateFieldSet(e, t, r) { var s = _classPrivateFieldGet(t, e); return _classApplyDescriptorSet(e, s, r), r; } | ||
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } | ||
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } | ||
function _classApplyDescriptorSet(e, t, l) { if (t.set) t.set.call(e, l);else { if (!t.writable) throw new TypeError("attempted to set read only private field"); t.value = l; } } | ||
import fs from 'fs'; | ||
@@ -102,3 +106,4 @@ import path from 'path'; | ||
var _routes = /*#__PURE__*/new WeakMap(); | ||
var _Server_brand = /*#__PURE__*/new WeakSet(); | ||
var _route = /*#__PURE__*/new WeakSet(); | ||
var _handleRequest = /*#__PURE__*/new WeakSet(); | ||
export class Server extends http.Server { | ||
@@ -112,36 +117,44 @@ constructor({ | ||
}); | ||
// adds a route in the correct priority order | ||
_classPrivateMethodInitSpec(this, _Server_brand); | ||
_classPrivateFieldInitSpec(this, _sockets, new Set()); | ||
_classPrivateFieldInitSpec(this, _defaultPort, void 0); | ||
// initial routes include cors and 404 handling | ||
_classPrivateFieldInitSpec(this, _routes, [{ | ||
priority: -1, | ||
handle: (req, res, next) => { | ||
res.setHeader('Access-Control-Allow-Origin', '*'); | ||
if (req.method === 'OPTIONS') { | ||
let allowHeaders = req.headers['access-control-request-headers'] || '*'; | ||
let allowMethods = [...new Set(_classPrivateFieldGet(_routes, this).flatMap(route => (!route.match || route.match(req.url.pathname)) && route.methods || []))].join(', '); | ||
res.setHeader('Access-Control-Allow-Headers', allowHeaders); | ||
res.setHeader('Access-Control-Allow-Methods', allowMethods); | ||
res.writeHead(204).end(); | ||
} else { | ||
res.setHeader('Access-Control-Expose-Headers', '*'); | ||
return next(); | ||
_classPrivateMethodInitSpec(this, _handleRequest); | ||
_classPrivateMethodInitSpec(this, _route); | ||
_classPrivateFieldInitSpec(this, _sockets, { | ||
writable: true, | ||
value: new Set() | ||
}); | ||
_classPrivateFieldInitSpec(this, _defaultPort, { | ||
writable: true, | ||
value: void 0 | ||
}); | ||
_classPrivateFieldInitSpec(this, _routes, { | ||
writable: true, | ||
value: [{ | ||
priority: -1, | ||
handle: (req, res, next) => { | ||
res.setHeader('Access-Control-Allow-Origin', '*'); | ||
if (req.method === 'OPTIONS') { | ||
let allowHeaders = req.headers['access-control-request-headers'] || '*'; | ||
let allowMethods = [...new Set(_classPrivateFieldGet2(this, _routes).flatMap(route => (!route.match || route.match(req.url.pathname)) && route.methods || []))].join(', '); | ||
res.setHeader('Access-Control-Allow-Headers', allowHeaders); | ||
res.setHeader('Access-Control-Allow-Methods', allowMethods); | ||
res.writeHead(204).end(); | ||
} else { | ||
res.setHeader('Access-Control-Expose-Headers', '*'); | ||
return next(); | ||
} | ||
} | ||
} | ||
}, { | ||
priority: 3, | ||
handle: req => ServerError.throw(404) | ||
}]); | ||
_classPrivateFieldSet(_defaultPort, this, port); | ||
}, { | ||
priority: 3, | ||
handle: req => ServerError.throw(404) | ||
}] | ||
}); | ||
_classPrivateFieldSet(this, _defaultPort, port); | ||
// handle requests on end | ||
this.on('request', (req, res) => { | ||
req.on('end', () => _assertClassBrand(_Server_brand, this, _handleRequest).call(this, req, res)); | ||
req.on('end', () => _classPrivateMethodGet(this, _handleRequest, _handleRequest2).call(this, req, res)); | ||
}); | ||
// track open connections to terminate when the server closes | ||
this.on('connection', socket => { | ||
let handleClose = () => _classPrivateFieldGet(_sockets, this).delete(socket); | ||
_classPrivateFieldGet(_sockets, this).add(socket.on('close', handleClose)); | ||
let handleClose = () => _classPrivateFieldGet2(this, _sockets).delete(socket); | ||
_classPrivateFieldGet2(this, _sockets).add(socket.on('close', handleClose)); | ||
}); | ||
@@ -158,3 +171,3 @@ } | ||
var _super$address; | ||
return ((_super$address = super.address()) === null || _super$address === void 0 ? void 0 : _super$address.port) ?? _classPrivateFieldGet(_defaultPort, this); | ||
return ((_super$address = super.address()) === null || _super$address === void 0 ? void 0 : _super$address.port) ?? _classPrivateFieldGet2(this, _defaultPort); | ||
} | ||
@@ -174,3 +187,3 @@ | ||
// return a promise that resolves when the server is listening | ||
listen(port = _classPrivateFieldGet(_defaultPort, this)) { | ||
listen(port = _classPrivateFieldGet2(this, _defaultPort)) { | ||
return new Promise((resolve, reject) => { | ||
@@ -186,6 +199,11 @@ let handle = err => off() && err ? reject(err) : resolve(this); | ||
return new Promise(resolve => { | ||
_classPrivateFieldGet(_sockets, this).forEach(socket => socket.destroy()); | ||
_classPrivateFieldGet2(this, _sockets).forEach(socket => socket.destroy()); | ||
super.close(resolve); | ||
}); | ||
} | ||
// initial routes include cors and 404 handling | ||
// adds a route in the correct priority order | ||
// set request routing and handling for pathnames and methods | ||
@@ -196,3 +214,3 @@ route(method, pathname, handle) { | ||
if (arguments.length === 2 && !Array.isArray(method) && method[0] === '/') [pathname, method] = [method]; | ||
return _assertClassBrand(_Server_brand, this, _route).call(this, { | ||
return _classPrivateMethodGet(this, _route, _route2).call(this, { | ||
priority: !pathname ? 0 : !method ? 1 : 2, | ||
@@ -224,3 +242,3 @@ methods: method && [].concat(method).map(m => m.toUpperCase()), | ||
}); | ||
return _assertClassBrand(_Server_brand, this, _route).call(this, { | ||
return _classPrivateMethodGet(this, _route, _route2).call(this, { | ||
priority: 2, | ||
@@ -249,8 +267,8 @@ methods: ['GET'], | ||
// create a url rewriter from provided rewrite rules | ||
function _route(route) { | ||
let i = _classPrivateFieldGet(_routes, this).findIndex(r => r.priority >= route.priority); | ||
_classPrivateFieldGet(_routes, this).splice(i, 0, route); | ||
function _route2(route) { | ||
let i = _classPrivateFieldGet2(this, _routes).findIndex(r => r.priority >= route.priority); | ||
_classPrivateFieldGet2(this, _routes).splice(i, 0, route); | ||
return this; | ||
} | ||
async function _handleRequest(req, res) { | ||
async function _handleRequest2(req, res) { | ||
// support node < 15.7.0 | ||
@@ -271,3 +289,3 @@ res.req ?? (res.req = req); | ||
return result ? handle(req, res, next) : next(); | ||
}(_classPrivateFieldGet(_routes, this)); | ||
}(_classPrivateFieldGet2(this, _routes)); | ||
} catch (error) { | ||
@@ -274,0 +292,0 @@ var _req$headers$accept, _req$headers$content; |
@@ -6,3 +6,3 @@ import logger from '@percy/logger'; | ||
import Queue from './queue.js'; | ||
import { request, hostnameMatches, yieldTo, snapshotLogName } from './utils.js'; | ||
import { request, hostnameMatches, yieldTo, snapshotLogName, decodeAndEncodeURLWithLogging } from './utils.js'; | ||
import { JobData } from './wait-for-job.js'; | ||
@@ -21,2 +21,19 @@ | ||
} | ||
function validateAndFixSnapshotUrl(snapshot) { | ||
let log = logger('core:snapshot'); | ||
// encoding snapshot url, if contians invalid URI characters/syntax | ||
let modifiedURL = decodeAndEncodeURLWithLogging(snapshot.url, log, { | ||
meta: { | ||
snapshot: { | ||
name: snapshot.name || snapshot.url | ||
} | ||
}, | ||
shouldLogWarning: true, | ||
warningMessage: `Invalid URL detected for url: ${snapshot.url} - the snapshot may fail on Percy. Please confirm that your website URL is valid.` | ||
}); | ||
if (modifiedURL !== snapshot.url) { | ||
log.debug(`Snapshot URL modified to: ${modifiedURL}`); | ||
snapshot.url = modifiedURL; | ||
} | ||
} | ||
@@ -91,5 +108,5 @@ // used to deserialize regular expression strings | ||
}; | ||
validateAndFixSnapshotUrl(snapshot); | ||
let url = validURL(snapshot.url, context === null || context === void 0 ? void 0 : context.baseUrl); | ||
// normalize the snapshot url and use it for the default name | ||
let url = validURL(snapshot.url, context === null || context === void 0 ? void 0 : context.baseUrl); | ||
(_snapshot = snapshot).name || (_snapshot.name = `${url.pathname}${url.search}${url.hash}`); | ||
@@ -96,0 +113,0 @@ snapshot.url = url.href; |
@@ -18,5 +18,5 @@ import logger from '@percy/logger'; | ||
const duration = Date.now() - startTime; | ||
this.log.info(`${name} - ${identifier} - ${duration / 1000}s`); | ||
this.log.debug(`${name} - ${identifier} - ${duration / 1000}s`); | ||
} | ||
} | ||
} |
@@ -8,2 +8,4 @@ import EventEmitter from 'events'; | ||
import { readFileSync } from 'fs'; | ||
import logger from '@percy/logger'; | ||
import DetectProxy from '@percy/client/detect-proxy'; | ||
export { request, getPackageJSON, hostnameMatches } from '@percy/client/utils'; | ||
@@ -381,2 +383,31 @@ export { Server, createServer } from './server.js'; | ||
} | ||
// This function replaces invalid character that are not the | ||
// part of valid URI syntax with there correct encoded value. | ||
// Also, if a character is a part of valid URI syntax, those characters | ||
// are not encoded | ||
// Eg: [abc] -> gets encoded to %5Babc%5D | ||
// ab c -> ab%20c | ||
export function decodeAndEncodeURLWithLogging(url, logger, options = {}) { | ||
// In case the url is partially encoded, then directly using encodeURI() | ||
// will encode those characters again. Therefore decodeURI once helps is decoding | ||
// partially encoded URL and then after encoding it again, full URL get encoded | ||
// correctly. | ||
const { | ||
meta, | ||
shouldLogWarning, | ||
warningMessage | ||
} = options; | ||
try { | ||
let decodedURL = decodeURI(url); // This can throw error, so handle it will trycatch | ||
let encodedURL = encodeURI(decodedURL); | ||
return encodedURL; | ||
} catch (error) { | ||
logger.debug(error, meta); | ||
if (error.name === 'URIError' && shouldLogWarning) { | ||
logger.warn(warningMessage); | ||
} | ||
return url; | ||
} | ||
} | ||
export function snapshotLogName(name, meta) { | ||
@@ -389,2 +420,30 @@ var _meta$snapshot; | ||
} | ||
export async function detectSystemProxyAndLog(applyProxy) { | ||
// if proxy is already set no need to check again | ||
if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) return; | ||
let proxyPresent = false; | ||
const log = logger('core:utils'); | ||
// Checking proxy shouldn't cause failure | ||
try { | ||
const detectProxy = new DetectProxy(); | ||
const proxies = await detectProxy.getSystemProxy(); | ||
proxyPresent = proxies.length !== 0; | ||
if (proxyPresent) { | ||
if (applyProxy) { | ||
proxies.forEach(proxy => { | ||
if (proxy.type === 'HTTPS') { | ||
process.env.HTTPS_PROXY = 'https://' + proxy.host + ':' + proxy.port; | ||
} else if (proxy.type === 'HTTP') { | ||
process.env.HTTP_PROXY = 'http://' + proxy.host + ':' + proxy.port; | ||
} | ||
}); | ||
} else { | ||
log.warn('We have detected a system level proxy in your system. use HTTP_PROXY or HTTPS_PROXY env vars or To auto apply proxy set useSystemProxy: true under percy in config file'); | ||
} | ||
} | ||
} catch (e) { | ||
log.debug(`Failed to detect system proxy ${e}`); | ||
} | ||
return proxyPresent; | ||
} | ||
@@ -391,0 +450,0 @@ // DefaultMap, which returns a default value for an uninitialized key |
{ | ||
"name": "@percy/core", | ||
"version": "1.29.1-alpha.0", | ||
"version": "1.29.1-beta.0", | ||
"license": "MIT", | ||
@@ -12,3 +12,3 @@ "repository": { | ||
"access": "public", | ||
"tag": "alpha" | ||
"tag": "beta" | ||
}, | ||
@@ -47,7 +47,7 @@ "engines": { | ||
"dependencies": { | ||
"@percy/client": "1.29.1-alpha.0", | ||
"@percy/config": "1.29.1-alpha.0", | ||
"@percy/dom": "1.29.1-alpha.0", | ||
"@percy/logger": "1.29.1-alpha.0", | ||
"@percy/webdriver-utils": "1.29.1-alpha.0", | ||
"@percy/client": "1.29.1-beta.0", | ||
"@percy/config": "1.29.1-beta.0", | ||
"@percy/dom": "1.29.1-beta.0", | ||
"@percy/logger": "1.29.1-beta.0", | ||
"@percy/webdriver-utils": "1.29.1-beta.0", | ||
"content-disposition": "^0.5.4", | ||
@@ -65,3 +65,3 @@ "cross-spawn": "^7.0.3", | ||
}, | ||
"gitHead": "5044adb5caa7507fdec629eda8f33d6ddde07997" | ||
"gitHead": "d325b7bbe56764dbde494477d1f4f3bfdc562d6e" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
405972
5380
26
29
+ Added@percy/client@1.29.1-beta.0(transitive)
+ Added@percy/config@1.29.1-beta.0(transitive)
+ Added@percy/dom@1.29.1-beta.0(transitive)
+ Added@percy/env@1.29.1-beta.0(transitive)
+ Added@percy/logger@1.29.1-beta.0(transitive)
+ Added@percy/sdk-utils@1.29.1-beta.0(transitive)
+ Added@percy/webdriver-utils@1.29.1-beta.0(transitive)
- Removed@percy/client@1.29.1-alpha.0(transitive)
- Removed@percy/config@1.29.1-alpha.0(transitive)
- Removed@percy/dom@1.29.1-alpha.0(transitive)
- Removed@percy/env@1.29.1-alpha.0(transitive)
- Removed@percy/logger@1.29.1-alpha.0(transitive)
- Removed@percy/sdk-utils@1.29.1-alpha.0(transitive)
- Removed@percy/webdriver-utils@1.29.1-alpha.0(transitive)
Updated@percy/client@1.29.1-beta.0
Updated@percy/config@1.29.1-beta.0
Updated@percy/dom@1.29.1-beta.0
Updated@percy/logger@1.29.1-beta.0