';
}
- })
- return JSON.stringify(serializedArgs)
+ });
+ return JSON.stringify(serializedArgs);
}
/**
@@ -386,15 +386,15 @@ export class DDGProxy {
* @param {string} property
* @param {ProxyObject} proxyObject
*/
- constructor (feature, objectScope, property, proxyObject) {
- this.objectScope = objectScope
- this.property = property
- this.feature = feature
- this.featureName = feature.name
- this.camelFeatureName = camelcase(this.featureName)
+ constructor(feature, objectScope, property, proxyObject) {
+ this.objectScope = objectScope;
+ this.property = property;
+ this.feature = feature;
+ this.featureName = feature.name;
+ this.camelFeatureName = camelcase(this.featureName);
const outputHandler = (...args) => {
- this.feature.addDebugFlag()
- const isExempt = shouldExemptMethod(this.camelFeatureName)
+ this.feature.addDebugFlag();
+ const isExempt = shouldExemptMethod(this.camelFeatureName);
// Keep this here as getStack() is expensive
if (debug) {
postDebugMessage(this.camelFeatureName, {
@@ -403,103 +403,103 @@ export class DDGProxy {
kind: this.property,
documentUrl: document.location.href,
stack: getStack(),
- args: debugSerialize(args[2])
- })
+ args: debugSerialize(args[2]),
+ });
}
// The normal return value
if (isExempt) {
- return DDGReflect.apply(...args)
+ return DDGReflect.apply(...args);
}
- return proxyObject.apply(...args)
- }
+ return proxyObject.apply(...args);
+ };
const getMethod = (target, prop, receiver) => {
- this.feature.addDebugFlag()
+ this.feature.addDebugFlag();
if (prop === 'toString') {
- const method = Reflect.get(target, prop, receiver).bind(target)
+ const method = Reflect.get(target, prop, receiver).bind(target);
Object.defineProperty(method, 'toString', {
value: String.toString.bind(String.toString),
- enumerable: false
- })
- return method
+ enumerable: false,
+ });
+ return method;
}
- return DDGReflect.get(target, prop, receiver)
- }
+ return DDGReflect.get(target, prop, receiver);
+ };
if (hasMozProxies) {
- this._native = objectScope[property]
- const handler = new globalObj.wrappedJSObject.Object()
- handler.apply = exportFunction(outputHandler, globalObj)
- handler.get = exportFunction(getMethod, globalObj)
+ this._native = objectScope[property];
+ const handler = new globalObj.wrappedJSObject.Object();
+ handler.apply = exportFunction(outputHandler, globalObj);
+ handler.get = exportFunction(getMethod, globalObj);
// @ts-expect-error wrappedJSObject is not a property of objectScope
- this.internal = new globalObj.wrappedJSObject.Proxy(objectScope.wrappedJSObject[property], handler)
+ this.internal = new globalObj.wrappedJSObject.Proxy(objectScope.wrappedJSObject[property], handler);
} else {
- this._native = objectScope[property]
- const handler = {}
- handler.apply = outputHandler
- handler.get = getMethod
- this.internal = new globalObj.Proxy(objectScope[property], handler)
+ this._native = objectScope[property];
+ const handler = {};
+ handler.apply = outputHandler;
+ handler.get = getMethod;
+ this.internal = new globalObj.Proxy(objectScope[property], handler);
}
}
// Actually apply the proxy to the native property
- overload () {
+ overload() {
if (hasMozProxies) {
// @ts-expect-error wrappedJSObject is not a property of objectScope
- exportFunction(this.internal, this.objectScope, { defineAs: this.property })
+ exportFunction(this.internal, this.objectScope, { defineAs: this.property });
} else {
- this.objectScope[this.property] = this.internal
+ this.objectScope[this.property] = this.internal;
}
}
- overloadDescriptor () {
+ overloadDescriptor() {
// TODO: this is not always correct! Use wrap* or shim* methods instead
this.feature.defineProperty(this.objectScope, this.property, {
value: this.internal,
writable: true,
enumerable: true,
- configurable: true
- })
+ configurable: true,
+ });
}
}
-const maxCounter = new Map()
-function numberOfTimesDebugged (feature) {
+const maxCounter = new Map();
+function numberOfTimesDebugged(feature) {
if (!maxCounter.has(feature)) {
- maxCounter.set(feature, 1)
+ maxCounter.set(feature, 1);
} else {
- maxCounter.set(feature, maxCounter.get(feature) + 1)
+ maxCounter.set(feature, maxCounter.get(feature) + 1);
}
- return maxCounter.get(feature)
+ return maxCounter.get(feature);
}
-const DEBUG_MAX_TIMES = 5000
+const DEBUG_MAX_TIMES = 5000;
-export function postDebugMessage (feature, message, allowNonDebug = false) {
+export function postDebugMessage(feature, message, allowNonDebug = false) {
if (!debug && !allowNonDebug) {
- return
+ return;
}
if (numberOfTimesDebugged(feature) > DEBUG_MAX_TIMES) {
- return
+ return;
}
if (message.stack) {
- const scriptOrigins = [...getStackTraceOrigins(message.stack)]
- message.scriptOrigins = scriptOrigins
+ const scriptOrigins = [...getStackTraceOrigins(message.stack)];
+ message.scriptOrigins = scriptOrigins;
}
globalObj.postMessage({
action: feature,
- message
- })
+ message,
+ });
}
-export let DDGReflect
-export let DDGPromise
+export let DDGReflect;
+export let DDGPromise;
// Exports for usage where we have to cross the xray boundary: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts
if (hasMozProxies) {
- DDGPromise = globalObj.wrappedJSObject.Promise
- DDGReflect = globalObj.wrappedJSObject.Reflect
+ DDGPromise = globalObj.wrappedJSObject.Promise;
+ DDGReflect = globalObj.wrappedJSObject.Reflect;
} else {
- DDGPromise = globalObj.Promise
- DDGReflect = globalObj.Reflect
+ DDGPromise = globalObj.Promise;
+ DDGReflect = globalObj.Reflect;
}
/**
@@ -507,23 +507,23 @@ if (hasMozProxies) {
* @param {object[]} featureList
* @returns {boolean}
*/
-export function isUnprotectedDomain (topLevelHostname, featureList) {
- let unprotectedDomain = false
+export function isUnprotectedDomain(topLevelHostname, featureList) {
+ let unprotectedDomain = false;
if (!topLevelHostname) {
- return false
+ return false;
}
- const domainParts = topLevelHostname.split('.')
+ const domainParts = topLevelHostname.split('.');
// walk up the domain to see if it's unprotected
while (domainParts.length > 1 && !unprotectedDomain) {
- const partialDomain = domainParts.join('.')
+ const partialDomain = domainParts.join('.');
- unprotectedDomain = featureList.filter(domain => domain.domain === partialDomain).length > 0
+ unprotectedDomain = featureList.filter((domain) => domain.domain === partialDomain).length > 0;
- domainParts.shift()
+ domainParts.shift();
}
- return unprotectedDomain
+ return unprotectedDomain;
}
/**
@@ -545,11 +545,11 @@ export function isUnprotectedDomain (topLevelHostname, featureList) {
/**
* Used to inialize extension code in the load phase
*/
-export function computeLimitedSiteObject () {
- const topLevelHostname = getTabHostname()
+export function computeLimitedSiteObject() {
+ const topLevelHostname = getTabHostname();
return {
- domain: topLevelHostname
- }
+ domain: topLevelHostname,
+ };
}
/**
@@ -557,14 +557,14 @@ export function computeLimitedSiteObject () {
* @param {UserPreferences} preferences
* @returns {string | number | undefined}
*/
-function getPlatformVersion (preferences) {
+function getPlatformVersion(preferences) {
if (preferences.versionNumber) {
- return preferences.versionNumber
+ return preferences.versionNumber;
}
if (preferences.versionString) {
- return preferences.versionString
+ return preferences.versionString;
}
- return undefined
+ return undefined;
}
/**
@@ -572,25 +572,25 @@ function getPlatformVersion (preferences) {
* @param {string} version
* @returns string
*/
-export function stripVersion (version, keepComponents = 1) {
- const splitVersion = version.split('.')
- const filteredVersion = []
- let foundNonZero = false
- let keptComponents = 0
+export function stripVersion(version, keepComponents = 1) {
+ const splitVersion = version.split('.');
+ const filteredVersion = [];
+ let foundNonZero = false;
+ let keptComponents = 0;
splitVersion.forEach((v) => {
if (v !== '0' && (!foundNonZero || keptComponents < keepComponents)) {
- filteredVersion.push(v)
- foundNonZero = true
- keptComponents++
+ filteredVersion.push(v);
+ foundNonZero = true;
+ keptComponents++;
} else {
- filteredVersion.push('0')
+ filteredVersion.push('0');
}
- })
- return filteredVersion.join('.')
+ });
+ return filteredVersion.join('.');
}
-function parseVersionString (versionString) {
- return versionString.split('.').map(Number)
+function parseVersionString(versionString) {
+ return versionString.split('.').map(Number);
}
/**
@@ -598,21 +598,21 @@ function parseVersionString (versionString) {
* @param {string} applicationVersionString
* @returns {boolean}
*/
-export function satisfiesMinVersion (minVersionString, applicationVersionString) {
- const minVersions = parseVersionString(minVersionString)
- const currentVersions = parseVersionString(applicationVersionString)
- const maxLength = Math.max(minVersions.length, currentVersions.length)
+export function satisfiesMinVersion(minVersionString, applicationVersionString) {
+ const minVersions = parseVersionString(minVersionString);
+ const currentVersions = parseVersionString(applicationVersionString);
+ const maxLength = Math.max(minVersions.length, currentVersions.length);
for (let i = 0; i < maxLength; i++) {
- const minNumberPart = minVersions[i] || 0
- const currentVersionPart = currentVersions[i] || 0
+ const minNumberPart = minVersions[i] || 0;
+ const currentVersionPart = currentVersions[i] || 0;
if (currentVersionPart > minNumberPart) {
- return true
+ return true;
}
if (currentVersionPart < minNumberPart) {
- return false
+ return false;
}
}
- return true
+ return true;
}
/**
@@ -620,17 +620,17 @@ export function satisfiesMinVersion (minVersionString, applicationVersionString)
* @param {string | number | undefined} currentVersion
* @returns {boolean}
*/
-function isSupportedVersion (minSupportedVersion, currentVersion) {
+function isSupportedVersion(minSupportedVersion, currentVersion) {
if (typeof currentVersion === 'string' && typeof minSupportedVersion === 'string') {
if (satisfiesMinVersion(minSupportedVersion, currentVersion)) {
- return true
+ return true;
}
} else if (typeof currentVersion === 'number' && typeof minSupportedVersion === 'number') {
if (minSupportedVersion <= currentVersion) {
- return true
+ return true;
}
}
- return false
+ return false;
}
/**
@@ -645,32 +645,32 @@ function isSupportedVersion (minSupportedVersion, currentVersion) {
* @param {UserPreferences} preferences
* @param {string[]} platformSpecificFeatures
*/
-export function processConfig (data, userList, preferences, platformSpecificFeatures = []) {
- const topLevelHostname = getTabHostname()
- const site = computeLimitedSiteObject()
- const allowlisted = userList.filter(domain => domain === topLevelHostname).length > 0
+export function processConfig(data, userList, preferences, platformSpecificFeatures = []) {
+ const topLevelHostname = getTabHostname();
+ const site = computeLimitedSiteObject();
+ const allowlisted = userList.filter((domain) => domain === topLevelHostname).length > 0;
/** @type {Record} */
- const output = { ...preferences }
+ const output = { ...preferences };
if (output.platform) {
- const version = getPlatformVersion(preferences)
+ const version = getPlatformVersion(preferences);
if (version) {
- output.platform.version = version
+ output.platform.version = version;
}
}
- const enabledFeatures = computeEnabledFeatures(data, topLevelHostname, preferences.platform?.version, platformSpecificFeatures)
- const isBroken = isUnprotectedDomain(topLevelHostname, data.unprotectedTemporary)
+ const enabledFeatures = computeEnabledFeatures(data, topLevelHostname, preferences.platform?.version, platformSpecificFeatures);
+ const isBroken = isUnprotectedDomain(topLevelHostname, data.unprotectedTemporary);
output.site = Object.assign(site, {
isBroken,
allowlisted,
- enabledFeatures
- })
+ enabledFeatures,
+ });
// Copy feature settings from remote config to preferences object
- output.featureSettings = parseFeatureSettings(data, enabledFeatures)
- output.trackerLookup = import.meta.trackerLookup
- output.bundledConfig = data
+ output.featureSettings = parseFeatureSettings(data, enabledFeatures);
+ output.trackerLookup = import.meta.trackerLookup;
+ output.bundledConfig = data;
- return output
+ return output;
}
/**
@@ -681,20 +681,24 @@ export function processConfig (data, userList, preferences, platformSpecificFeat
* @param {string[]} platformSpecificFeatures
* @returns {string[]}
*/
-export function computeEnabledFeatures (data, topLevelHostname, platformVersion, platformSpecificFeatures = []) {
- const remoteFeatureNames = Object.keys(data.features)
- const platformSpecificFeaturesNotInRemoteConfig = platformSpecificFeatures.filter((featureName) => !remoteFeatureNames.includes(featureName))
- const enabledFeatures = remoteFeatureNames.filter((featureName) => {
- const feature = data.features[featureName]
- // Check that the platform supports minSupportedVersion checks and that the feature has a minSupportedVersion
- if (feature.minSupportedVersion && platformVersion) {
- if (!isSupportedVersion(feature.minSupportedVersion, platformVersion)) {
- return false
+export function computeEnabledFeatures(data, topLevelHostname, platformVersion, platformSpecificFeatures = []) {
+ const remoteFeatureNames = Object.keys(data.features);
+ const platformSpecificFeaturesNotInRemoteConfig = platformSpecificFeatures.filter(
+ (featureName) => !remoteFeatureNames.includes(featureName),
+ );
+ const enabledFeatures = remoteFeatureNames
+ .filter((featureName) => {
+ const feature = data.features[featureName];
+ // Check that the platform supports minSupportedVersion checks and that the feature has a minSupportedVersion
+ if (feature.minSupportedVersion && platformVersion) {
+ if (!isSupportedVersion(feature.minSupportedVersion, platformVersion)) {
+ return false;
+ }
}
- }
- return feature.state === 'enabled' && !isUnprotectedDomain(topLevelHostname, feature.exceptions)
- }).concat(platformSpecificFeaturesNotInRemoteConfig) // only disable platform specific features if it's explicitly disabled in remote config
- return enabledFeatures
+ return feature.state === 'enabled' && !isUnprotectedDomain(topLevelHostname, feature.exceptions);
+ })
+ .concat(platformSpecificFeaturesNotInRemoteConfig); // only disable platform specific features if it's explicitly disabled in remote config
+ return enabledFeatures;
}
/**
@@ -703,46 +707,49 @@ export function computeEnabledFeatures (data, topLevelHostname, platformVersion,
* @param {string[]} enabledFeatures
* @returns {Record}
*/
-export function parseFeatureSettings (data, enabledFeatures) {
+export function parseFeatureSettings(data, enabledFeatures) {
/** @type {Record} */
- const featureSettings = {}
- const remoteFeatureNames = Object.keys(data.features)
+ const featureSettings = {};
+ const remoteFeatureNames = Object.keys(data.features);
remoteFeatureNames.forEach((featureName) => {
if (!enabledFeatures.includes(featureName)) {
- return
+ return;
}
- featureSettings[featureName] = data.features[featureName].settings
- })
- return featureSettings
+ featureSettings[featureName] = data.features[featureName].settings;
+ });
+ return featureSettings;
}
-export function isGloballyDisabled (args) {
- return args.site.allowlisted || args.site.isBroken
+export function isGloballyDisabled(args) {
+ return args.site.allowlisted || args.site.isBroken;
}
-export const windowsSpecificFeatures = ['windowsPermissionUsage']
+export const windowsSpecificFeatures = ['windowsPermissionUsage'];
-export function isWindowsSpecificFeature (featureName) {
- return windowsSpecificFeatures.includes(featureName)
+export function isWindowsSpecificFeature(featureName) {
+ return windowsSpecificFeatures.includes(featureName);
}
-export function createCustomEvent (eventName, eventDetail) {
+export function createCustomEvent(eventName, eventDetail) {
// By default, Firefox protects the event detail Object from the page,
// leading to "Permission denied to access property" errors.
// See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts
if (hasMozProxies) {
- eventDetail = cloneInto(eventDetail, window)
+ eventDetail = cloneInto(eventDetail, window);
}
// @ts-expect-error - possibly null
- return new OriginalCustomEvent(eventName, eventDetail)
+ return new OriginalCustomEvent(eventName, eventDetail);
}
/** @deprecated */
-export function legacySendMessage (messageType, options) {
+export function legacySendMessage(messageType, options) {
// FF & Chrome
- return originalWindowDispatchEvent && originalWindowDispatchEvent(createCustomEvent('sendMessageProxy' + messageSecret, { detail: { messageType, options } }))
+ return (
+ originalWindowDispatchEvent &&
+ originalWindowDispatchEvent(createCustomEvent('sendMessageProxy' + messageSecret, { detail: { messageType, options } }))
+ );
// TBD other platforms
}
@@ -753,29 +760,29 @@ export function legacySendMessage (messageType, options) {
* @param {number} [delay=500] - The initial delay to be used to create the exponential backoff.
* @returns {Promise}
*/
-export function withExponentialBackoff (fn, maxAttempts = 4, delay = 500) {
+export function withExponentialBackoff(fn, maxAttempts = 4, delay = 500) {
return new Promise((resolve, reject) => {
- let attempts = 0
+ let attempts = 0;
const tryFn = () => {
- attempts += 1
- const error = new Error('Element not found')
+ attempts += 1;
+ const error = new Error('Element not found');
try {
- const element = fn()
+ const element = fn();
if (element) {
- resolve(element)
+ resolve(element);
} else if (attempts < maxAttempts) {
- setTimeout(tryFn, delay * Math.pow(2, attempts))
+ setTimeout(tryFn, delay * Math.pow(2, attempts));
} else {
- reject(error)
+ reject(error);
}
} catch {
if (attempts < maxAttempts) {
- setTimeout(tryFn, delay * Math.pow(2, attempts))
+ setTimeout(tryFn, delay * Math.pow(2, attempts));
} else {
- reject(error)
+ reject(error);
}
}
- }
- tryFn()
- })
+ };
+ tryFn();
+ });
}
diff --git a/injected/src/wrapper-utils.js b/injected/src/wrapper-utils.js
index dec969641..0040e928d 100644
--- a/injected/src/wrapper-utils.js
+++ b/injected/src/wrapper-utils.js
@@ -1,14 +1,21 @@
/* global mozProxies, cloneInto, exportFunction */
-import { functionToString, getOwnPropertyDescriptor, getOwnPropertyDescriptors, objectDefineProperty, objectEntries, objectKeys } from './captured-globals.js'
+import {
+ functionToString,
+ getOwnPropertyDescriptor,
+ getOwnPropertyDescriptors,
+ objectDefineProperty,
+ objectEntries,
+ objectKeys,
+} from './captured-globals.js';
-const globalObj = typeof window === 'undefined' ? globalThis : window
+const globalObj = typeof window === 'undefined' ? globalThis : window;
// Tests don't define this variable so fallback to behave like chrome
-export const hasMozProxies = typeof mozProxies !== 'undefined' ? mozProxies : false
+export const hasMozProxies = typeof mozProxies !== 'undefined' ? mozProxies : false;
// special property that is set on classes used to shim standard interfaces
-export const ddgShimMark = Symbol('ddgShimMark')
+export const ddgShimMark = Symbol('ddgShimMark');
/**
* Like Object.defineProperty, but with support for Firefox's mozProxies.
@@ -16,28 +23,25 @@ export const ddgShimMark = Symbol('ddgShimMark')
* @param {string} propertyName
* @param {import('./wrapper-utils').StrictPropertyDescriptor} descriptor - requires all descriptor options to be defined because we can't validate correctness based on TS types
*/
-export function defineProperty (object, propertyName, descriptor) {
+export function defineProperty(object, propertyName, descriptor) {
if (hasMozProxies) {
- const usedObj = object.wrappedJSObject || object
- const UsedObjectInterface = globalObj.wrappedJSObject.Object
+ const usedObj = object.wrappedJSObject || object;
+ const UsedObjectInterface = globalObj.wrappedJSObject.Object;
const definedDescriptor = new UsedObjectInterface();
['configurable', 'enumerable', 'value', 'writable'].forEach((propertyName) => {
if (propertyName in descriptor) {
- definedDescriptor[propertyName] = cloneInto(
- descriptor[propertyName],
- definedDescriptor,
- { cloneFunctions: true }
- )
+ definedDescriptor[propertyName] = cloneInto(descriptor[propertyName], definedDescriptor, { cloneFunctions: true });
}
});
['get', 'set'].forEach((methodName) => {
- if (methodName in descriptor && typeof descriptor[methodName] !== 'undefined') { // Firefox returns undefined for missing getters/setters
- exportFunction(descriptor[methodName], definedDescriptor, { defineAs: methodName })
+ if (methodName in descriptor && typeof descriptor[methodName] !== 'undefined') {
+ // Firefox returns undefined for missing getters/setters
+ exportFunction(descriptor[methodName], definedDescriptor, { defineAs: methodName });
}
- })
- UsedObjectInterface.defineProperty(usedObj, propertyName, definedDescriptor)
+ });
+ UsedObjectInterface.defineProperty(usedObj, propertyName, definedDescriptor);
} else {
- objectDefineProperty(object, propertyName, descriptor)
+ objectDefineProperty(object, propertyName, descriptor);
}
}
@@ -49,12 +53,12 @@ export function defineProperty (object, propertyName, descriptor) {
* @param {*} origFn
* @param {string} [mockValue] - when provided, .toString() will return this value
*/
-export function wrapToString (newFn, origFn, mockValue) {
+export function wrapToString(newFn, origFn, mockValue) {
if (typeof newFn !== 'function' || typeof origFn !== 'function') {
- return newFn
+ return newFn;
}
- return new Proxy(newFn, { get: toStringGetTrap(origFn, mockValue) })
+ return new Proxy(newFn, { get: toStringGetTrap(origFn, mockValue) });
}
/**
@@ -64,45 +68,45 @@ export function wrapToString (newFn, origFn, mockValue) {
* @param {string} [mockValue] - when provided, .toString() will return this value
* @returns { (target: any, prop: string, receiver: any) => any }
*/
-export function toStringGetTrap (targetFn, mockValue) {
+export function toStringGetTrap(targetFn, mockValue) {
// We wrap two levels deep to handle toString.toString() calls
- return function get (target, prop, receiver) {
+ return function get(target, prop, receiver) {
if (prop === 'toString') {
- const origToString = Reflect.get(targetFn, 'toString', targetFn)
+ const origToString = Reflect.get(targetFn, 'toString', targetFn);
const toStringProxy = new Proxy(origToString, {
- apply (target, thisArg, argumentsList) {
+ apply(target, thisArg, argumentsList) {
// only mock toString() when called on the proxy itself. If the method is applied to some other object, it should behave as a normal toString()
if (thisArg === receiver) {
if (mockValue) {
- return mockValue
+ return mockValue;
}
- return Reflect.apply(target, targetFn, argumentsList)
+ return Reflect.apply(target, targetFn, argumentsList);
} else {
- return Reflect.apply(target, thisArg, argumentsList)
+ return Reflect.apply(target, thisArg, argumentsList);
}
},
- get (target, prop, receiver) {
+ get(target, prop, receiver) {
// handle toString.toString() result
if (prop === 'toString') {
- const origToStringToString = Reflect.get(origToString, 'toString', origToString)
+ const origToStringToString = Reflect.get(origToString, 'toString', origToString);
const toStringToStringProxy = new Proxy(origToStringToString, {
- apply (target, thisArg, argumentsList) {
+ apply(target, thisArg, argumentsList) {
if (thisArg === toStringProxy) {
- return Reflect.apply(target, origToString, argumentsList)
+ return Reflect.apply(target, origToString, argumentsList);
} else {
- return Reflect.apply(target, thisArg, argumentsList)
+ return Reflect.apply(target, thisArg, argumentsList);
}
- }
- })
- return toStringToStringProxy
+ },
+ });
+ return toStringToStringProxy;
}
- return Reflect.get(target, prop, receiver)
- }
- })
- return toStringProxy
+ return Reflect.get(target, prop, receiver);
+ },
+ });
+ return toStringProxy;
}
- return Reflect.get(target, prop, receiver)
- }
+ return Reflect.get(target, prop, receiver);
+ };
}
/**
@@ -111,24 +115,24 @@ export function toStringGetTrap (targetFn, mockValue) {
* @param {*} realTarget
* @returns {Proxy} a proxy for the function
*/
-export function wrapFunction (functionValue, realTarget) {
+export function wrapFunction(functionValue, realTarget) {
return new Proxy(realTarget, {
- get (target, prop, receiver) {
+ get(target, prop, receiver) {
if (prop === 'toString') {
- const method = Reflect.get(target, prop, receiver).bind(target)
+ const method = Reflect.get(target, prop, receiver).bind(target);
Object.defineProperty(method, 'toString', {
value: functionToString.bind(functionToString),
- enumerable: false
- })
- return method
+ enumerable: false,
+ });
+ return method;
}
- return Reflect.get(target, prop, receiver)
+ return Reflect.get(target, prop, receiver);
},
- apply (target, thisArg, argumentsList) {
+ apply(target, thisArg, argumentsList) {
// This is where we call our real function
- return Reflect.apply(functionValue, thisArg, argumentsList)
- }
- })
+ return Reflect.apply(functionValue, thisArg, argumentsList);
+ },
+ });
}
/**
@@ -139,34 +143,35 @@ export function wrapFunction (functionValue, realTarget) {
* @param {typeof Object.defineProperty} definePropertyFn - function to use for defining the property
* @returns {PropertyDescriptor|undefined} original property descriptor, or undefined if it's not found
*/
-export function wrapProperty (object, propertyName, descriptor, definePropertyFn) {
+export function wrapProperty(object, propertyName, descriptor, definePropertyFn) {
if (!object) {
- return
+ return;
}
if (hasMozProxies) {
- object = object.wrappedJSObject || object
+ object = object.wrappedJSObject || object;
}
/** @type {StrictPropertyDescriptor} */
// @ts-expect-error - we check for undefined below
- const origDescriptor = getOwnPropertyDescriptor(object, propertyName)
+ const origDescriptor = getOwnPropertyDescriptor(object, propertyName);
if (!origDescriptor) {
// this happens if the property is not implemented in the browser
- return
+ return;
}
- if (('value' in origDescriptor && 'value' in descriptor) ||
+ if (
+ ('value' in origDescriptor && 'value' in descriptor) ||
('get' in origDescriptor && 'get' in descriptor) ||
('set' in origDescriptor && 'set' in descriptor)
) {
definePropertyFn(object, propertyName, {
...origDescriptor,
- ...descriptor
- })
- return origDescriptor
+ ...descriptor,
+ });
+ return origDescriptor;
} else {
// if the property is defined with get/set it must be wrapped with a get/set. If it's defined with a `value`, it must be wrapped with a `value`
- throw new Error(`Property descriptor for ${propertyName} may only include the following keys: ${objectKeys(origDescriptor)}`)
+ throw new Error(`Property descriptor for ${propertyName} may only include the following keys: ${objectKeys(origDescriptor)}`);
}
}
@@ -178,38 +183,38 @@ export function wrapProperty (object, propertyName, descriptor, definePropertyFn
* @param {DefinePropertyFn} definePropertyFn - function to use for defining the property
* @returns {PropertyDescriptor|undefined} original property descriptor, or undefined if it's not found
*/
-export function wrapMethod (object, propertyName, wrapperFn, definePropertyFn) {
+export function wrapMethod(object, propertyName, wrapperFn, definePropertyFn) {
if (!object) {
- return
+ return;
}
if (hasMozProxies) {
- object = object.wrappedJSObject || object
+ object = object.wrappedJSObject || object;
}
/** @type {StrictPropertyDescriptor} */
// @ts-expect-error - we check for undefined below
- const origDescriptor = getOwnPropertyDescriptor(object, propertyName)
+ const origDescriptor = getOwnPropertyDescriptor(object, propertyName);
if (!origDescriptor) {
// this happens if the property is not implemented in the browser
- return
+ return;
}
// @ts-expect-error - we check for undefined below
- const origFn = origDescriptor.value
+ const origFn = origDescriptor.value;
if (!origFn || typeof origFn !== 'function') {
// method properties are expected to be defined with a `value`
- throw new Error(`Property ${propertyName} does not look like a method`)
+ throw new Error(`Property ${propertyName} does not look like a method`);
}
const newFn = wrapToString(function () {
- return wrapperFn.call(this, origFn, ...arguments)
- }, origFn)
+ return wrapperFn.call(this, origFn, ...arguments);
+ }, origFn);
definePropertyFn(object, propertyName, {
...origDescriptor,
- value: newFn
- })
- return origDescriptor
+ value: newFn,
+ });
+ return origDescriptor;
}
/**
@@ -219,18 +224,13 @@ export function wrapMethod (object, propertyName, wrapperFn, definePropertyFn) {
* @param {DefineInterfaceOptions} options - options for defining the interface
* @param {DefinePropertyFn} definePropertyFn - function to use for defining the property
*/
-export function shimInterface (
- interfaceName,
- ImplClass,
- options,
- definePropertyFn
-) {
+export function shimInterface(interfaceName, ImplClass, options, definePropertyFn) {
if (import.meta.injectName === 'integration') {
- if (!globalThis.origInterfaceDescriptors) globalThis.origInterfaceDescriptors = {}
- const descriptor = Object.getOwnPropertyDescriptor(globalThis, interfaceName)
- globalThis.origInterfaceDescriptors[interfaceName] = descriptor
+ if (!globalThis.origInterfaceDescriptors) globalThis.origInterfaceDescriptors = {};
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, interfaceName);
+ globalThis.origInterfaceDescriptors[interfaceName] = descriptor;
- globalThis.ddgShimMark = ddgShimMark
+ globalThis.ddgShimMark = ddgShimMark;
}
/** @type {DefineInterfaceOptions} */
@@ -238,35 +238,35 @@ export function shimInterface (
allowConstructorCall: false,
disallowConstructor: false,
constructorErrorMessage: 'Illegal constructor',
- wrapToString: true
- }
+ wrapToString: true,
+ };
const fullOptions = {
interfaceDescriptorOptions: { writable: true, enumerable: false, configurable: true, value: ImplClass },
...defaultOptions,
- ...options
- }
+ ...options,
+ };
// In some cases we can get away without a full proxy, but in many cases below we need it.
// For example, we can't redefine `prototype` property on ES6 classes.
// Se we just always wrap the class to make the code more maintaibnable
/** @type {ProxyHandler} */
- const proxyHandler = {}
+ const proxyHandler = {};
// handle the case where the constructor is called without new
if (fullOptions.allowConstructorCall) {
// make the constructor function callable without new
proxyHandler.apply = function (target, thisArg, argumentsList) {
- return Reflect.construct(target, argumentsList, target)
- }
+ return Reflect.construct(target, argumentsList, target);
+ };
}
// make the constructor function throw when called without new
if (fullOptions.disallowConstructor) {
proxyHandler.construct = function () {
- throw new TypeError(fullOptions.constructorErrorMessage)
- }
+ throw new TypeError(fullOptions.constructorErrorMessage);
+ };
}
if (fullOptions.wrapToString) {
@@ -274,29 +274,29 @@ export function shimInterface (
for (const [prop, descriptor] of objectEntries(getOwnPropertyDescriptors(ImplClass.prototype))) {
if (prop !== 'constructor' && descriptor.writable && typeof descriptor.value === 'function') {
ImplClass.prototype[prop] = new Proxy(descriptor.value, {
- get: toStringGetTrap(descriptor.value, `function ${prop}() { [native code] }`)
- })
+ get: toStringGetTrap(descriptor.value, `function ${prop}() { [native code] }`),
+ });
}
}
// wrap toString on the constructor function itself
Object.assign(proxyHandler, {
- get: toStringGetTrap(ImplClass, `function ${interfaceName}() { [native code] }`)
- })
+ get: toStringGetTrap(ImplClass, `function ${interfaceName}() { [native code] }`),
+ });
}
// Note that instanceof should still work, since the `.prototype` object is proxied too:
// Interface() instanceof Interface === true
// ImplClass() instanceof Interface === true
- const Interface = new Proxy(ImplClass, proxyHandler)
+ const Interface = new Proxy(ImplClass, proxyHandler);
// Make sure that Interface().constructor === Interface (not ImplClass)
if (ImplClass.prototype?.constructor === ImplClass) {
/** @type {StrictDataDescriptor} */
// @ts-expect-error - As long as ImplClass is a normal class, it should have the prototype property
- const descriptor = getOwnPropertyDescriptor(ImplClass.prototype, 'constructor')
+ const descriptor = getOwnPropertyDescriptor(ImplClass.prototype, 'constructor');
if (descriptor.writable) {
- ImplClass.prototype.constructor = Interface
+ ImplClass.prototype.constructor = Interface;
}
}
@@ -307,8 +307,8 @@ export function shimInterface (
value: true,
configurable: false,
enumerable: false,
- writable: false
- })
+ writable: false,
+ });
}
// mock the name property
@@ -316,15 +316,11 @@ export function shimInterface (
value: interfaceName,
configurable: true,
enumerable: false,
- writable: false
- })
+ writable: false,
+ });
// interfaces are exposed directly on the global object, not on its prototype
- definePropertyFn(
- globalThis,
- interfaceName,
- { ...fullOptions.interfaceDescriptorOptions, value: Interface }
- )
+ definePropertyFn(globalThis, interfaceName, { ...fullOptions.interfaceDescriptorOptions, value: Interface });
}
/**
@@ -339,52 +335,54 @@ export function shimInterface (
* @param {boolean} readOnly - whether the property should be read-only
* @param {DefinePropertyFn} definePropertyFn - function to use for defining the property
*/
-export function shimProperty (baseObject, propertyName, implInstance, readOnly, definePropertyFn) {
+export function shimProperty(baseObject, propertyName, implInstance, readOnly, definePropertyFn) {
// @ts-expect-error - implInstance is a class instance
- const ImplClass = implInstance.constructor
+ const ImplClass = implInstance.constructor;
if (import.meta.injectName === 'integration') {
- if (!globalThis.origPropDescriptors) globalThis.origPropDescriptors = []
- const descriptor = Object.getOwnPropertyDescriptor(baseObject, propertyName)
- globalThis.origPropDescriptors.push([baseObject, propertyName, descriptor])
+ if (!globalThis.origPropDescriptors) globalThis.origPropDescriptors = [];
+ const descriptor = Object.getOwnPropertyDescriptor(baseObject, propertyName);
+ globalThis.origPropDescriptors.push([baseObject, propertyName, descriptor]);
- globalThis.ddgShimMark = ddgShimMark
+ globalThis.ddgShimMark = ddgShimMark;
if (ImplClass[ddgShimMark] !== true) {
- throw new TypeError('implInstance must be an instance of a shimmed class')
+ throw new TypeError('implInstance must be an instance of a shimmed class');
}
}
// mask toString() and toString.toString() on the instance
const proxiedInstance = new Proxy(implInstance, {
- get: toStringGetTrap(implInstance, `[object ${ImplClass.name}]`)
- })
+ get: toStringGetTrap(implInstance, `[object ${ImplClass.name}]`),
+ });
/** @type {StrictPropertyDescriptor} */
- let descriptor
+ let descriptor;
// Note that we only cover most common cases: a getter for "readonly" properties, and a value descriptor for writable properties.
// But there could be other cases, e.g. a property with both a getter and a setter. These could be defined with a raw defineProperty() call.
// Important: make sure to cover each new shim with a test that verifies that all descriptors match the standard API.
if (readOnly) {
- const getter = function get () { return proxiedInstance }
+ const getter = function get() {
+ return proxiedInstance;
+ };
const proxiedGetter = new Proxy(getter, {
- get: toStringGetTrap(getter, `function get ${propertyName}() { [native code] }`)
- })
+ get: toStringGetTrap(getter, `function get ${propertyName}() { [native code] }`),
+ });
descriptor = {
configurable: true,
enumerable: true,
- get: proxiedGetter
- }
+ get: proxiedGetter,
+ };
} else {
descriptor = {
configurable: true,
enumerable: true,
writable: true,
- value: proxiedInstance
- }
+ value: proxiedInstance,
+ };
}
- definePropertyFn(baseObject, propertyName, descriptor)
+ definePropertyFn(baseObject, propertyName, descriptor);
}
/**
diff --git a/injected/unit-test/broker-protection-extract.js b/injected/unit-test/broker-protection-extract.js
index b316aa639..02eb005f1 100644
--- a/injected/unit-test/broker-protection-extract.js
+++ b/injected/unit-test/broker-protection-extract.js
@@ -1,8 +1,5 @@
-import {
- aggregateFields,
- createProfile
-} from '../src/features/broker-protection/actions/extract.js'
-import { cleanArray } from '../src/features/broker-protection/utils.js'
+import { aggregateFields, createProfile } from '../src/features/broker-protection/actions/extract.js';
+import { cleanArray } from '../src/features/broker-protection/utils.js';
describe('create profiles from extracted data', () => {
describe('cleanArray', () => {
@@ -20,25 +17,25 @@ describe('create profiles from extracted data', () => {
{ input: [null], expected: [] },
{ input: [[[]]], expected: [] },
{ input: [null, '', 0, ' '], expected: [0] },
- { input: [null, '000'], expected: ['000'] }
- ]
+ { input: [null, '000'], expected: ['000'] },
+ ];
for (const item of items) {
- const actual = cleanArray(item.input)
- expect(actual).toEqual(item.expected)
+ const actual = cleanArray(item.input);
+ expect(actual).toEqual(item.expected);
}
- })
- })
+ });
+ });
it('handles combined, single strings', () => {
const selectors = {
name: {
selector: '.name',
- beforeText: ','
+ beforeText: ',',
},
age: {
selector: '.name',
- afterText: ','
- }
- }
+ afterText: ',',
+ },
+ };
const elementExamples = [
{ text: 'John smith, 39', expected: { name: 'John smith', age: '39' } },
@@ -60,17 +57,17 @@ describe('create profiles from extracted data', () => {
// examples where age is absent, so the result should be null
{ text: 'John smith', expected: { name: 'John smith', age: null } },
{ text: 'John smith , ', expected: { name: 'John smith', age: null } },
- { text: 'John smith \n,\n ', expected: { name: 'John smith', age: null } }
- ]
+ { text: 'John smith \n,\n ', expected: { name: 'John smith', age: null } },
+ ];
for (const elementExample of elementExamples) {
const elementFactory = () => {
- return [{ innerText: elementExample.text }]
- }
- const profile = createProfile(elementFactory, selectors)
- expect(profile).toEqual(elementExample.expected)
+ return [{ innerText: elementExample.text }];
+ };
+ const profile = createProfile(elementFactory, selectors);
+ expect(profile).toEqual(elementExample.expected);
}
- })
+ });
it('handles multiple strings', () => {
const elementExamples = [
{
@@ -78,75 +75,69 @@ describe('create profiles from extracted data', () => {
alternativeNamesList: {
selector: 'example',
afterText: 'Also Known as:',
- separator: ','
- }
+ separator: ',',
+ },
},
elements: [{ innerText: 'Also Known as: John Smith, Jon Smith' }],
expected: {
- alternativeNamesList: ['John Smith', 'Jon Smith']
- }
+ alternativeNamesList: ['John Smith', 'Jon Smith'],
+ },
},
{
selectors: {
alternativeNamesList: {
selector: 'example',
findElements: true,
- afterText: 'Also Known as:'
- }
+ afterText: 'Also Known as:',
+ },
},
- elements: [
- { innerText: 'Also Known as: John Smith' },
- { innerText: 'Jon Smith' }
- ],
+ elements: [{ innerText: 'Also Known as: John Smith' }, { innerText: 'Jon Smith' }],
expected: {
- alternativeNamesList: ['John Smith', 'Jon Smith']
- }
+ alternativeNamesList: ['John Smith', 'Jon Smith'],
+ },
},
{
selectors: {
alternativeNamesList: {
selector: 'example',
findElements: true,
- beforeText: ', '
- }
+ beforeText: ', ',
+ },
},
- elements: [
- { innerText: 'John Smith, 89' },
- { innerText: 'Jon Smith, 78' }
- ],
+ elements: [{ innerText: 'John Smith, 89' }, { innerText: 'Jon Smith, 78' }],
expected: {
- alternativeNamesList: ['John Smith', 'Jon Smith']
- }
- }
- ]
+ alternativeNamesList: ['John Smith', 'Jon Smith'],
+ },
+ },
+ ];
for (const elementExample of elementExamples) {
- const elementFactory = () => elementExample.elements
- const profile = createProfile(elementFactory, elementExample.selectors)
- expect(profile).toEqual(elementExample.expected)
+ const elementFactory = () => elementExample.elements;
+ const profile = createProfile(elementFactory, elementExample.selectors);
+ expect(profile).toEqual(elementExample.expected);
}
- })
+ });
it('should omit invalid addresses', () => {
const elementExamples = [
{
selectors: {
addressCityState: {
- selector: 'example'
- }
+ selector: 'example',
+ },
},
elements: [{ innerText: 'anything, here' }],
expected: {
- addressCityState: []
- }
- }
- ]
+ addressCityState: [],
+ },
+ },
+ ];
for (const elementExample of elementExamples) {
- const elementFactory = () => elementExample.elements
- const profile = createProfile(elementFactory, elementExample.selectors)
- expect(profile).toEqual(elementExample.expected)
+ const elementFactory = () => elementExample.elements;
+ const profile = createProfile(elementFactory, elementExample.selectors);
+ expect(profile).toEqual(elementExample.expected);
}
- })
+ });
it('should omit duplicate addresses when aggregated', () => {
const elementExamples = [
@@ -154,47 +145,47 @@ describe('create profiles from extracted data', () => {
selectors: {
addressCityState: {
selector: 'example',
- findElements: true
- }
+ findElements: true,
+ },
},
elements: [{ innerText: 'Dallas, TX' }, { innerText: 'Dallas, TX' }],
expected: {
- addresses: [{ city: 'Dallas', state: 'TX' }]
- }
- }
- ]
+ addresses: [{ city: 'Dallas', state: 'TX' }],
+ },
+ },
+ ];
for (const elementExample of elementExamples) {
- const elementFactory = () => elementExample.elements
- const profile = createProfile(elementFactory, elementExample.selectors)
- const aggregated = aggregateFields(profile)
- expect(aggregated.addresses).toEqual(elementExample.expected.addresses)
+ const elementFactory = () => elementExample.elements;
+ const profile = createProfile(elementFactory, elementExample.selectors);
+ const aggregated = aggregateFields(profile);
+ expect(aggregated.addresses).toEqual(elementExample.expected.addresses);
}
- })
+ });
it('should handle addressCityStateList', () => {
const example = {
selectors: {
addressCityStateList: {
- selector: 'example'
- }
+ selector: 'example',
+ },
},
elements: [{ innerText: 'Dallas, TX • The Colony, TX • Carrollton, TX • +1 more' }],
expected: {
addresses: [
{ city: 'Carrollton', state: 'TX' },
{ city: 'Dallas', state: 'TX' },
- { city: 'The Colony', state: 'TX' }
- ]
- }
- }
+ { city: 'The Colony', state: 'TX' },
+ ],
+ },
+ };
- const elementFactory = () => example.elements
- const profile = createProfile(elementFactory, /** @type {any} */(example.selectors))
- const aggregated = aggregateFields(profile)
+ const elementFactory = () => example.elements;
+ const profile = createProfile(elementFactory, /** @type {any} */ (example.selectors));
+ const aggregated = aggregateFields(profile);
- expect(aggregated.addresses).toEqual(example.expected.addresses)
- })
+ expect(aggregated.addresses).toEqual(example.expected.addresses);
+ });
it('should include addresses from `addressFullList` - https://app.asana.com/0/0/1206856260863051/f', () => {
const elementExamples = [
@@ -202,32 +193,35 @@ describe('create profiles from extracted data', () => {
selectors: {
addressFullList: {
selector: 'example',
- findElements: true
- }
+ findElements: true,
+ },
},
elements: [{ innerText: '123 fake street,\nDallas, TX 75215' }, { innerText: '123 fake street,\nMiami, FL 75215' }],
expected: {
- addresses: [{ city: 'Miami', state: 'FL' }, { city: 'Dallas', state: 'TX' }]
- }
- }
- ]
+ addresses: [
+ { city: 'Miami', state: 'FL' },
+ { city: 'Dallas', state: 'TX' },
+ ],
+ },
+ },
+ ];
for (const elementExample of elementExamples) {
- const elementFactory = () => elementExample.elements
- const profile = createProfile(elementFactory, /** @type {any} */(elementExample.selectors))
- const aggregated = aggregateFields(profile)
- expect(aggregated.addresses).toEqual(elementExample.expected.addresses)
+ const elementFactory = () => elementExample.elements;
+ const profile = createProfile(elementFactory, /** @type {any} */ (elementExample.selectors));
+ const aggregated = aggregateFields(profile);
+ expect(aggregated.addresses).toEqual(elementExample.expected.addresses);
}
- })
+ });
it('should exclude common prefixes/suffixes https://app.asana.com/0/0/1206808591178551/f', () => {
const selectors = {
relativesList: {
selector: 'example',
findElements: true,
- afterText: 'AKA:'
- }
- }
+ afterText: 'AKA:',
+ },
+ };
const elementFactory = (key) => {
return {
relativesList: [
@@ -244,11 +238,11 @@ describe('create profiles from extracted data', () => {
{ innerText: 'Jimmy Smith, 39 + 1 more' },
{ innerText: 'Jimmy Smith, 39 +3 more like this' },
{ innerText: 'Jill Johnson, 39 +4 more' },
- { innerText: 'Jack Johnson, 39 - ' }
- ]
- }[key]
- }
- const scraped = createProfile(elementFactory, /** @type {any} */(selectors))
+ { innerText: 'Jack Johnson, 39 - ' },
+ ],
+ }[key];
+ };
+ const scraped = createProfile(elementFactory, /** @type {any} */ (selectors));
expect(scraped).toEqual({
relativesList: [
'Jane Smith',
@@ -264,48 +258,45 @@ describe('create profiles from extracted data', () => {
'Jimmy Smith',
'Jimmy Smith',
'Jill Johnson',
- 'Jack Johnson'
- ]
- })
- })
+ 'Jack Johnson',
+ ],
+ });
+ });
it('(1) Addresses: general validation [validation] https://app.asana.com/0/0/1206808587680141/f', () => {
const selectors = {
name: {
- selector: 'example'
+ selector: 'example',
},
age: {
- selector: 'example'
+ selector: 'example',
},
addressCityState: {
selector: 'example',
- findElements: true
- }
- }
+ findElements: true,
+ },
+ };
const elementFactory = (key) => {
return {
name: [{ innerText: 'Shane Osbourne' }],
age: [{ innerText: '39' }],
- addressCityState: [
- { innerText: 'Dallas, TX' },
- { innerText: 'anything, here' }
- ]
- }[key]
- }
- const expected = [{ city: 'Dallas', state: 'TX' }]
+ addressCityState: [{ innerText: 'Dallas, TX' }, { innerText: 'anything, here' }],
+ }[key];
+ };
+ const expected = [{ city: 'Dallas', state: 'TX' }];
- const scraped = createProfile(elementFactory, /** @type {any} */(selectors))
- const actual = aggregateFields(scraped)
- expect(actual.addresses).toEqual(expected)
- })
+ const scraped = createProfile(elementFactory, /** @type {any} */ (selectors));
+ const actual = aggregateFields(scraped);
+ expect(actual.addresses).toEqual(expected);
+ });
it('should sort relatives by name alphabetically', () => {
const selectors = {
relativesList: {
selector: 'example',
- findElements: true
- }
- }
+ findElements: true,
+ },
+ };
const elementFactory = (key) => {
return {
relativesList: [
@@ -313,29 +304,23 @@ describe('create profiles from extracted data', () => {
{ innerText: 'John Smith' },
{ innerText: 'Jimmy Smith' },
{ innerText: 'Jill Johnson' },
- { innerText: 'Jack Johnson' }
- ]
- }[key]
- }
- const scraped = createProfile(elementFactory, /** @type {any} */(selectors))
- const actual = aggregateFields(scraped)
+ { innerText: 'Jack Johnson' },
+ ],
+ }[key];
+ };
+ const scraped = createProfile(elementFactory, /** @type {any} */ (selectors));
+ const actual = aggregateFields(scraped);
- expect(actual.relatives).toEqual([
- 'Dale Johnson',
- 'Jack Johnson',
- 'Jill Johnson',
- 'Jimmy Smith',
- 'John Smith'
- ])
- })
+ expect(actual.relatives).toEqual(['Dale Johnson', 'Jack Johnson', 'Jill Johnson', 'Jimmy Smith', 'John Smith']);
+ });
it('should sort phone numbers numerically', () => {
const selectors = {
phoneList: {
selector: 'example',
- findElements: true
- }
- }
+ findElements: true,
+ },
+ };
const elementFactory = (key) => {
return {
phoneList: [
@@ -343,47 +328,36 @@ describe('create profiles from extracted data', () => {
{ innerText: '123-456-7894' },
{ innerText: '123-456-7892' },
{ innerText: '123-456-7891' },
- { innerText: '123-456-7890' }
- ]
- }[key]
- }
- const scraped = createProfile(elementFactory, /** @type {any} */(selectors))
- const actual = aggregateFields(scraped)
+ { innerText: '123-456-7890' },
+ ],
+ }[key];
+ };
+ const scraped = createProfile(elementFactory, /** @type {any} */ (selectors));
+ const actual = aggregateFields(scraped);
- expect(actual.phoneNumbers).toEqual([
- '1234567890',
- '1234567891',
- '1234567892',
- '1234567894',
- '1234567895'
- ])
- })
+ expect(actual.phoneNumbers).toEqual(['1234567890', '1234567891', '1234567892', '1234567894', '1234567895']);
+ });
it('should sort alternative names alphabetically', () => {
const selectors = {
alternativeNamesList: {
selector: 'example',
- findElements: true
- }
- }
+ findElements: true,
+ },
+ };
const elementFactory = (key) => {
return {
alternativeNamesList: [
{ innerText: 'Jerry Doug' },
{ innerText: 'Marvin Smith' },
{ innerText: 'Roger Star' },
- { innerText: 'Fred Firth' }
- ]
- }[key]
- }
- const scraped = createProfile(elementFactory, /** @type {any} */(selectors))
- const actual = aggregateFields(scraped)
+ { innerText: 'Fred Firth' },
+ ],
+ }[key];
+ };
+ const scraped = createProfile(elementFactory, /** @type {any} */ (selectors));
+ const actual = aggregateFields(scraped);
- expect(actual.alternativeNames).toEqual([
- 'Fred Firth',
- 'Jerry Doug',
- 'Marvin Smith',
- 'Roger Star'
- ])
- })
-})
+ expect(actual.alternativeNames).toEqual(['Fred Firth', 'Jerry Doug', 'Marvin Smith', 'Roger Star']);
+ });
+});
diff --git a/injected/unit-test/broker-protection-extractors.js b/injected/unit-test/broker-protection-extractors.js
index 12f01faae..5fb47026b 100644
--- a/injected/unit-test/broker-protection-extractors.js
+++ b/injected/unit-test/broker-protection-extractors.js
@@ -1,19 +1,21 @@
-import fc from 'fast-check'
-import { cleanArray } from '../src/features/broker-protection/utils.js'
-import { PhoneExtractor } from '../src/features/broker-protection/extractors/phone.js'
-import { ProfileUrlExtractor } from '../src/features/broker-protection/extractors/profile-url.js'
+import fc from 'fast-check';
+import { cleanArray } from '../src/features/broker-protection/utils.js';
+import { PhoneExtractor } from '../src/features/broker-protection/extractors/phone.js';
+import { ProfileUrlExtractor } from '../src/features/broker-protection/extractors/profile-url.js';
describe('individual extractors', () => {
describe('PhoneExtractor', () => {
it('should extract digits only', () => {
- fc.assert(fc.property(fc.array(fc.string()), (s) => {
- const cleanInput = cleanArray(s)
- const numbers = new PhoneExtractor().extract(cleanInput, {})
- const cleanOutput = cleanArray(numbers)
- return cleanOutput.every(num => num.match(/^\d+$/))
- }))
- })
- })
+ fc.assert(
+ fc.property(fc.array(fc.string()), (s) => {
+ const cleanInput = cleanArray(s);
+ const numbers = new PhoneExtractor().extract(cleanInput, {});
+ const cleanOutput = cleanArray(numbers);
+ return cleanOutput.every((num) => num.match(/^\d+$/));
+ }),
+ );
+ });
+ });
describe('ProfileUrlExtractor', () => {
/**
* @typedef {import("../src/features/broker-protection/actions/extract.js").IdentifierType} IdentifierType
@@ -24,35 +26,35 @@ describe('individual extractors', () => {
// eslint-disable-next-line no-template-curly-in-string
identifier: 'https://duckduckgo.com/my/profile/${firstName}-${lastName}/${id}',
profileUrl: 'https://duckduckgo.com/my/profile/john-smith/223',
- expected: 'https://duckduckgo.com/my/profile/john-smith/223'
+ expected: 'https://duckduckgo.com/my/profile/john-smith/223',
},
{
identifierType: /** @type {IdentifierType} */ ('param'),
identifier: 'pid',
profileUrl: 'https://duckduckgo.com/my/profile?id=test',
- expected: 'https://duckduckgo.com/my/profile?id=test'
+ expected: 'https://duckduckgo.com/my/profile?id=test',
},
{
identifierType: /** @type {IdentifierType} */ ('param'),
// eslint-disable-next-line no-template-curly-in-string
identifier: 'https://duckduckgo.com/my/profile/${firstName}-${lastName}/${id}',
profileUrl: 'https://duckduckgo.com/my/profile/john-smith/223',
- expected: 'https://duckduckgo.com/my/profile/john-smith/223'
+ expected: 'https://duckduckgo.com/my/profile/john-smith/223',
},
{
identifierType: /** @type {IdentifierType} */ ('param'),
identifier: 'id',
profileUrl: 'https://duckduckgo.com/my/profile?id=test',
- expected: 'test'
- }
- ]
+ expected: 'test',
+ },
+ ];
testCases.forEach(({ identifierType, identifier, profileUrl, expected }) => {
it(`should return the correct identifier when identifierType is "${identifierType}" and identifier is "${identifier}"`, () => {
- const profile = new ProfileUrlExtractor().extract([profileUrl], { identifierType, identifier })
+ const profile = new ProfileUrlExtractor().extract([profileUrl], { identifierType, identifier });
- expect(profile?.identifier).toEqual(expected)
- })
- })
- })
-})
+ expect(profile?.identifier).toEqual(expected);
+ });
+ });
+ });
+});
diff --git a/injected/unit-test/broker-protection.js b/injected/unit-test/broker-protection.js
index 5c05b2268..147bb77f5 100644
--- a/injected/unit-test/broker-protection.js
+++ b/injected/unit-test/broker-protection.js
@@ -1,141 +1,148 @@
-import fc from 'fast-check'
-import { isSameAge } from '../src/features/broker-protection/comparisons/is-same-age.js'
-import { getNicknames, getFullNames, isSameName, getNames } from '../src/features/broker-protection/comparisons/is-same-name.js'
-import {
- stringToList,
- extractValue
-} from '../src/features/broker-protection/actions/extract.js'
-import {
- addressMatch
-} from '../src/features/broker-protection/comparisons/address.js'
-import { replaceTemplatedUrl } from '../src/features/broker-protection/actions/build-url.js'
-import { processTemplateStringWithUserData } from '../src/features/broker-protection/actions/build-url-transforms.js'
-import { names } from '../src/features/broker-protection/comparisons/constants.js'
-import { generateRandomInt, hashObject, sortAddressesByStateAndCity } from '../src/features/broker-protection/utils.js'
-import { generatePhoneNumber, generateZipCode, generateStreetAddress } from '../src/features/broker-protection/actions/generators.js'
-import { CityStateExtractor } from '../src/features/broker-protection/extractors/address.js'
-import { ProfileHashTransformer } from '../src/features/broker-protection/extractors/profile-url.js'
+import fc from 'fast-check';
+import { isSameAge } from '../src/features/broker-protection/comparisons/is-same-age.js';
+import { getNicknames, getFullNames, isSameName, getNames } from '../src/features/broker-protection/comparisons/is-same-name.js';
+import { stringToList, extractValue } from '../src/features/broker-protection/actions/extract.js';
+import { addressMatch } from '../src/features/broker-protection/comparisons/address.js';
+import { replaceTemplatedUrl } from '../src/features/broker-protection/actions/build-url.js';
+import { processTemplateStringWithUserData } from '../src/features/broker-protection/actions/build-url-transforms.js';
+import { names } from '../src/features/broker-protection/comparisons/constants.js';
+import { generateRandomInt, hashObject, sortAddressesByStateAndCity } from '../src/features/broker-protection/utils.js';
+import { generatePhoneNumber, generateZipCode, generateStreetAddress } from '../src/features/broker-protection/actions/generators.js';
+import { CityStateExtractor } from '../src/features/broker-protection/extractors/address.js';
+import { ProfileHashTransformer } from '../src/features/broker-protection/extractors/profile-url.js';
describe('Actions', () => {
describe('extract', () => {
describe('isSameAge', () => {
it('evaluate as the same as if the age is within 2 years', () => {
- expect(isSameAge(40, 41)).toBe(true)
- })
+ expect(isSameAge(40, 41)).toBe(true);
+ });
it('evaluate as the same as if the age is within 2 years and given as a string', () => {
- expect(isSameAge(40, '41')).toBe(true)
- })
+ expect(isSameAge(40, '41')).toBe(true);
+ });
it('does not evaluate as the same as if the age is not within 2 years', () => {
- expect(isSameAge(40, 44)).toBe(false)
- })
+ expect(isSameAge(40, 44)).toBe(false);
+ });
it('does not evaluate as the same age if one is not a number', () => {
- expect(isSameAge(40, 'John Smith')).toBe(false)
- })
- })
+ expect(isSameAge(40, 'John Smith')).toBe(false);
+ });
+ });
describe('getNames', () => {
it('should return an empty set if the name is an empty string', () => {
- expect(Array.from(getNames(' '))).toEqual([])
- expect(Array.from(getNames(''))).toEqual([])
- })
+ expect(Array.from(getNames(' '))).toEqual([]);
+ expect(Array.from(getNames(''))).toEqual([]);
+ });
it('should return just name if there are no nicknames or full names', () => {
- expect(Array.from(getNames('J-Breeze')))
- })
+ expect(Array.from(getNames('J-Breeze')));
+ });
it('should return the name along with nicknames and full names if present', () => {
- expect(Array.from(getNames('Greg')).sort()).toEqual(['greg', 'gregory'])
- expect(Array.from(getNames('Gregory')).sort()).toEqual(['greg', 'gregory'])
- })
- })
+ expect(Array.from(getNames('Greg')).sort()).toEqual(['greg', 'gregory']);
+ expect(Array.from(getNames('Gregory')).sort()).toEqual(['greg', 'gregory']);
+ });
+ });
describe('getNicknames', () => {
it('should return nicknames for a name in our nickname list', () => {
- expect(Array.from(getNicknames('Jon', names.nicknames))).toEqual(['john', 'johnny', 'jonny', 'jonnie'])
- })
+ expect(Array.from(getNicknames('Jon', names.nicknames))).toEqual(['john', 'johnny', 'jonny', 'jonnie']);
+ });
it('should return an empty set if the name has no nicknames', () => {
- expect(Array.from(getNicknames('J-Breeze', names.nicknames))).toEqual([])
- })
- })
+ expect(Array.from(getNicknames('J-Breeze', names.nicknames))).toEqual([]);
+ });
+ });
describe('getFullNames', () => {
it('should return a full name given a nickname in our list', () => {
- expect(Array.from(getFullNames('Greg', names.nicknames))).toEqual(['gregory'])
- })
+ expect(Array.from(getFullNames('Greg', names.nicknames))).toEqual(['gregory']);
+ });
it('should return as many full names as are applicable for the nickname', () => {
- expect(Array.from(getFullNames('Kate', names.nicknames))).toEqual(['katelin', 'katelyn', 'katherine', 'kathryn', 'katia', 'katy'])
- })
+ expect(Array.from(getFullNames('Kate', names.nicknames))).toEqual([
+ 'katelin',
+ 'katelyn',
+ 'katherine',
+ 'kathryn',
+ 'katia',
+ 'katy',
+ ]);
+ });
it('should return an empty set if the nickname has no full names', () => {
- expect(Array.from(getFullNames('J-Breeze, names.nicknames', names.nicknames))).toEqual([])
- })
- })
+ expect(Array.from(getFullNames('J-Breeze, names.nicknames', names.nicknames))).toEqual([]);
+ });
+ });
describe('extractValue', () => {
it('should convert newlines to spaces in names', () => {
- expect(extractValue('name', { selector: 'example' }, ['John\nSmith'])).toEqual('John Smith')
- expect(extractValue('name', { selector: 'example' }, ['John\nT\nSmith'])).toEqual('John T Smith')
- })
- })
+ expect(extractValue('name', { selector: 'example' }, ['John\nSmith'])).toEqual('John Smith');
+ expect(extractValue('name', { selector: 'example' }, ['John\nT\nSmith'])).toEqual('John T Smith');
+ });
+ });
describe('isSameName', () => {
const userName = {
firstName: 'Jon',
middleName: 'Andrew',
lastName: 'Smith',
- suffix: null
- }
+ suffix: null,
+ };
it('should match if exact match', () => {
- expect(isSameName('Jon Smith', userName.firstName, userName.middleName, userName.lastName)).toBe(true)
- })
+ expect(isSameName('Jon Smith', userName.firstName, userName.middleName, userName.lastName)).toBe(true);
+ });
it('should match if nickname is given', () => {
- expect(isSameName('Jonathan Smith', userName.firstName, userName.middleName, userName.lastName)).toBe(true)
- })
+ expect(isSameName('Jonathan Smith', userName.firstName, userName.middleName, userName.lastName)).toBe(true);
+ });
it('should match middle name', () => {
- expect(isSameName('Jon Andrew Smith', userName.firstName, userName.middleName, userName.lastName)).toBe(true)
- })
+ expect(isSameName('Jon Andrew Smith', userName.firstName, userName.middleName, userName.lastName)).toBe(true);
+ });
it('should match if middle name is missing from user data but included in scraped data', () => {
- expect(isSameName('Jon A Smith', userName.firstName, null, userName.lastName)).toBe(true)
- expect(isSameName('Jon Andrew Smith', userName.firstName, null, userName.lastName)).toBe(true)
- })
+ expect(isSameName('Jon A Smith', userName.firstName, null, userName.lastName)).toBe(true);
+ expect(isSameName('Jon Andrew Smith', userName.firstName, null, userName.lastName)).toBe(true);
+ });
it('property testing isSameName -> boolean', () => {
- fc.assert(fc.property(
- fc.string(),
- fc.string(),
- fc.option(fc.string()),
- fc.string(),
- fc.option(fc.string()),
- (fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix) => {
- const result = isSameName(fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix)
- expect(typeof result).toBe('boolean')
- }
- ))
- })
+ fc.assert(
+ fc.property(
+ fc.string(),
+ fc.string(),
+ fc.option(fc.string()),
+ fc.string(),
+ fc.option(fc.string()),
+ (fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix) => {
+ const result = isSameName(fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix);
+ expect(typeof result).toBe('boolean');
+ },
+ ),
+ );
+ });
it('property testing isSameName -> boolean (seed 1)', () => {
// Got TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
// when doing if (nicknames[name])
- fc.assert(fc.property(
- fc.string(),
- fc.string(),
- fc.option(fc.string()),
- fc.string(),
- fc.option(fc.string()),
- (fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix) => {
- const result = isSameName(fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix)
- expect(typeof result).toBe('boolean')
- }
- ), { seed: 203542789, path: '70:1:0:0:1:85:86:85:86:86', endOnFailure: true })
- })
- })
+ fc.assert(
+ fc.property(
+ fc.string(),
+ fc.string(),
+ fc.option(fc.string()),
+ fc.string(),
+ fc.option(fc.string()),
+ (fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix) => {
+ const result = isSameName(fullNameExtracted, userFirstName, userMiddleName, userLastName, userSuffix);
+ expect(typeof result).toBe('boolean');
+ },
+ ),
+ { seed: 203542789, path: '70:1:0:0:1:85:86:85:86:86', endOnFailure: true },
+ );
+ });
+ });
describe('ProfileHashTransformer', () => {
/**
@@ -144,72 +151,64 @@ describe('Actions', () => {
it('Should return the profile unchanged if profileUrl is not present', async () => {
const profile = {
firstName: 'John',
- lastName: 'Doe'
- }
+ lastName: 'Doe',
+ };
- const generatedProfile = await new ProfileHashTransformer().transform(profile, {})
- expect(generatedProfile).toEqual(profile)
- })
+ const generatedProfile = await new ProfileHashTransformer().transform(profile, {});
+ expect(generatedProfile).toEqual(profile);
+ });
it('Should return the profile unchanged if identifierType is not set to hash', async () => {
const profile = {
firstName: 'John',
- lastName: 'Doe'
- }
+ lastName: 'Doe',
+ };
const params = {
profileUrl: {
- identifierType: /** @type {IdentifierType} */ ('param')
- }
- }
+ identifierType: /** @type {IdentifierType} */ ('param'),
+ },
+ };
- const generatedProfile = await new ProfileHashTransformer().transform(profile, params)
- expect(generatedProfile).toEqual(profile)
- })
+ const generatedProfile = await new ProfileHashTransformer().transform(profile, params);
+ expect(generatedProfile).toEqual(profile);
+ });
it('Should return a profile with a hash in the identifier if the identifierType is set to hash', async () => {
const profile = {
firstName: 'John',
- lastName: 'Doe'
- }
+ lastName: 'Doe',
+ };
const params = {
profileUrl: {
- identifierType: /** @type {IdentifierType} */ ('hash')
- }
- }
+ identifierType: /** @type {IdentifierType} */ ('hash'),
+ },
+ };
- const generatedProfile =
- await new ProfileHashTransformer().transform(profile, params)
- expect(generatedProfile.identifier).toMatch(/^[0-9a-f]{40}$/)
- })
- })
+ const generatedProfile = await new ProfileHashTransformer().transform(profile, params);
+ expect(generatedProfile.identifier).toMatch(/^[0-9a-f]{40}$/);
+ });
+ });
describe('get correct city state combos from list', () => {
- const cityStateLists = [
- 'Chicago IL, River Forest IL, Forest Park IL, Oak Park IL'
- ]
- const separator = ','
+ const cityStateLists = ['Chicago IL, River Forest IL, Forest Park IL, Oak Park IL'];
+ const separator = ',';
it('should match when city/state is the same', () => {
for (const cityStateList of cityStateLists) {
- const list = stringToList(cityStateList, separator)
- expect(list).toEqual([
- 'Chicago IL',
- 'River Forest IL',
- 'Forest Park IL',
- 'Oak Park IL'
- ])
- const result = new CityStateExtractor().extract(list, {})
+ const list = stringToList(cityStateList, separator);
+ expect(list).toEqual(['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL']);
+ const result = new CityStateExtractor().extract(list, {});
expect(result).toEqual([
{ city: 'Chicago', state: 'IL' },
{ city: 'River Forest', state: 'IL' },
{ city: 'Forest Park', state: 'IL' },
- { city: 'Oak Park', state: 'IL' }
- ])
+ { city: 'Oak Park', state: 'IL' },
+ ]);
}
- })
- })
+ });
+ });
describe('get correct city state combo from a string', () => {
it('should successfully extract the city/state when a zip code is included', () => {
@@ -218,68 +217,89 @@ describe('Actions', () => {
'Chicago IL 60611',
'River Forest IL 60305-1243',
'Forest Park IL, 60130-1234',
- 'Oak Park IL, 60302'
- ]
+ 'Oak Park IL, 60302',
+ ];
- const result = new CityStateExtractor().extract(cityStateZipList, {})
+ const result = new CityStateExtractor().extract(cityStateZipList, {});
expect(result).toEqual([
{ city: 'Chicago', state: 'IL' },
{ city: 'Chicago', state: 'IL' },
{ city: 'River Forest', state: 'IL' },
{ city: 'Forest Park', state: 'IL' },
- { city: 'Oak Park', state: 'IL' }
- ])
- })
- })
+ { city: 'Oak Park', state: 'IL' },
+ ]);
+ });
+ });
describe('get correct city state combos from malformedlist', () => {
- const malformedCityStateList = [
- 'Chicago IL, River Forest IL, Fores...'
- ]
- const separator = ','
+ const malformedCityStateList = ['Chicago IL, River Forest IL, Fores...'];
+ const separator = ',';
it('shouldshow partial address', () => {
for (const cityStateList of malformedCityStateList) {
- const list = stringToList(cityStateList, separator)
- expect(list).toEqual([
- 'Chicago IL',
- 'River Forest IL',
- 'Fores...'
- ])
- const result = new CityStateExtractor().extract(list, {})
+ const list = stringToList(cityStateList, separator);
+ expect(list).toEqual(['Chicago IL', 'River Forest IL', 'Fores...']);
+ const result = new CityStateExtractor().extract(list, {});
expect(result).toEqual([
{ city: 'Chicago', state: 'IL' },
- { city: 'River Forest', state: 'IL' }
- ])
+ { city: 'River Forest', state: 'IL' },
+ ]);
}
- })
- })
+ });
+ });
describe('get correct city state combos with separator', () => {
const cityStateList = [
- { listString: 'Chicago IL\nRiver Forest IL\nForest Park IL\nOak Park IL', separator: '\n', list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'] },
- { listString: 'Chicago, IL\nRiver Forest, IL\nForest Park, IL\nOak Park, IL', separator: '\n', list: ['Chicago, IL', 'River Forest, IL', 'Forest Park, IL', 'Oak Park, IL'] },
- { listString: 'Chicago IL | River Forest IL | Forest Park IL | Oak Park IL', separator: '|', list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'] },
- { listString: 'Chicago, IL | River Forest, IL | Forest Park, IL | Oak Park, IL', separator: '|', list: ['Chicago, IL', 'River Forest, IL', 'Forest Park, IL', 'Oak Park, IL'] },
- { listString: 'Chicago, IL • River Forest, IL • Forest Park, IL • Oak Park, IL', separator: '•', list: ['Chicago, IL', 'River Forest, IL', 'Forest Park, IL', 'Oak Park, IL'] },
- { listString: 'Chicago IL • River Forest IL • Forest Park IL • Oak Park IL', separator: '•', list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'] },
- { listString: 'Chicago IL · River Forest IL · Forest Park IL · Oak Park IL', list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'] }
- ]
+ {
+ listString: 'Chicago IL\nRiver Forest IL\nForest Park IL\nOak Park IL',
+ separator: '\n',
+ list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'],
+ },
+ {
+ listString: 'Chicago, IL\nRiver Forest, IL\nForest Park, IL\nOak Park, IL',
+ separator: '\n',
+ list: ['Chicago, IL', 'River Forest, IL', 'Forest Park, IL', 'Oak Park, IL'],
+ },
+ {
+ listString: 'Chicago IL | River Forest IL | Forest Park IL | Oak Park IL',
+ separator: '|',
+ list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'],
+ },
+ {
+ listString: 'Chicago, IL | River Forest, IL | Forest Park, IL | Oak Park, IL',
+ separator: '|',
+ list: ['Chicago, IL', 'River Forest, IL', 'Forest Park, IL', 'Oak Park, IL'],
+ },
+ {
+ listString: 'Chicago, IL • River Forest, IL • Forest Park, IL • Oak Park, IL',
+ separator: '•',
+ list: ['Chicago, IL', 'River Forest, IL', 'Forest Park, IL', 'Oak Park, IL'],
+ },
+ {
+ listString: 'Chicago IL • River Forest IL • Forest Park IL • Oak Park IL',
+ separator: '•',
+ list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'],
+ },
+ {
+ listString: 'Chicago IL · River Forest IL · Forest Park IL · Oak Park IL',
+ list: ['Chicago IL', 'River Forest IL', 'Forest Park IL', 'Oak Park IL'],
+ },
+ ];
it('should get correct city state with separator', () => {
for (const item of cityStateList) {
- const list = stringToList(item.listString, item.separator)
- expect(list).toEqual(item.list)
+ const list = stringToList(item.listString, item.separator);
+ expect(list).toEqual(item.list);
- const result = new CityStateExtractor().extract(list, { })
+ const result = new CityStateExtractor().extract(list, {});
expect(result).toEqual([
{ city: 'Chicago', state: 'IL' },
{ city: 'River Forest', state: 'IL' },
{ city: 'Forest Park', state: 'IL' },
- { city: 'Oak Park', state: 'IL' }
- ])
+ { city: 'Oak Park', state: 'IL' },
+ ]);
}
- })
- })
+ });
+ });
describe('Address Matching', () => {
const userData = {
@@ -287,8 +307,8 @@ describe('Actions', () => {
{
firstName: 'John',
middleName: null,
- lastName: 'Smith'
- }
+ lastName: 'Smith',
+ },
],
userAge: '40',
addresses: [
@@ -296,21 +316,21 @@ describe('Actions', () => {
addressLine1: '123 Fake St',
city: 'Chicago',
state: 'IL',
- zip: '60602'
- }
- ]
- }
+ zip: '60602',
+ },
+ ],
+ };
describe('matchAddressFromAddressListCityState', () => {
it('should match when city/state is present', () => {
- expect(addressMatch(userData.addresses, [{ city: 'chicago', state: 'il' }])).toBe(true)
- })
+ expect(addressMatch(userData.addresses, [{ city: 'chicago', state: 'il' }])).toBe(true);
+ });
it('should not match when city/state is not present', () => {
- expect(addressMatch(userData.addresses, [{ city: 'los angeles', state: 'ca' }])).toBe(false)
- })
- })
- })
- })
+ expect(addressMatch(userData.addresses, [{ city: 'los angeles', state: 'ca' }])).toBe(false);
+ });
+ });
+ });
+ });
describe('buildUrl', () => {
const userData = {
@@ -318,108 +338,138 @@ describe('Actions', () => {
lastName: 'Smith',
city: 'Chicago',
state: 'IL',
- age: '24'
- }
+ age: '24',
+ };
const userData2 = {
firstName: 'John',
lastName: 'Smith',
city: 'West Montego',
state: 'NY',
- age: '24'
- }
+ age: '24',
+ };
it('should build url without params', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- url: 'https://example.com/optout'
- }, userData)
- expect(result).toEqual({ url: 'https://example.com/optout' })
- })
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ url: 'https://example.com/optout',
+ },
+ userData,
+ );
+ expect(result).toEqual({ url: 'https://example.com/optout' });
+ });
it('should build url when given valid data from path segments', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/${firstName}-${lastName}/a/b/c/search?state=${state}&city=${city|hyphenated}&fage=${age}'
- }, userData)
- expect(result).toEqual({ url: 'https://example.com/profile/John-Smith/a/b/c/search?state=il&city=Chicago&fage=24' })
- })
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/${firstName}-${lastName}/a/b/c/search?state=${state}&city=${city|hyphenated}&fage=${age}',
+ },
+ userData,
+ );
+ expect(result).toEqual({ url: 'https://example.com/profile/John-Smith/a/b/c/search?state=il&city=Chicago&fage=24' });
+ });
it('should handle url encodings', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- url: 'https://example.com/name/$%7BfirstName%7Cdowncase%7D-$%7BlastName%7Cdowncase%7D/$%7Bcity%7Cdowncase%7D-$%7Bstate%7CstateFull%7Cdowncase%7D?age=$%7Bage%7D'
- }, userData)
- expect(result).toEqual({ url: 'https://example.com/name/john-smith/chicago-illinois?age=24' })
- })
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ url: 'https://example.com/name/$%7BfirstName%7Cdowncase%7D-$%7BlastName%7Cdowncase%7D/$%7Bcity%7Cdowncase%7D-$%7Bstate%7CstateFull%7Cdowncase%7D?age=$%7Bage%7D',
+ },
+ userData,
+ );
+ expect(result).toEqual({ url: 'https://example.com/name/john-smith/chicago-illinois?age=24' });
+ });
it('should build url when given valid data from path segments with modifiers path and url', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/${firstName|downcase}-${lastName|downcase}/a/b/c/search?state=${state|downcase}&city=${city|downcase}&fage=${age}'
- }, userData)
- expect(result).toEqual({ url: 'https://example.com/profile/john-smith/a/b/c/search?state=il&city=chicago&fage=24' })
- })
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/${firstName|downcase}-${lastName|downcase}/a/b/c/search?state=${state|downcase}&city=${city|downcase}&fage=${age}',
+ },
+ userData,
+ );
+ expect(result).toEqual({ url: 'https://example.com/profile/john-smith/a/b/c/search?state=il&city=chicago&fage=24' });
+ });
it('should build url when given valid data from url-search param segments', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/a/b/c/search?name=${firstName}-${lastName}&other=foobar'
- }, userData)
- expect(result).toEqual({ url: 'https://example.com/profile/a/b/c/search?name=John-Smith&other=foobar' })
- })
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/a/b/c/search?name=${firstName}-${lastName}&other=foobar',
+ },
+ userData,
+ );
+ expect(result).toEqual({ url: 'https://example.com/profile/a/b/c/search?name=John-Smith&other=foobar' });
+ });
it('should build url when given valid data', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated}&fage=${age}'
- }, userData)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated}&fage=${age}',
+ },
+ userData,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=il&city=Chicago&fage=24' })
- })
+ expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=il&city=Chicago&fage=24' });
+ });
it('should build hyphenated url when given hyphenated city', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated}&fage=${age}'
- }, userData2)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated}&fage=${age}',
+ },
+ userData2,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=West-Montego&fage=24' })
- })
+ expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=West-Montego&fage=24' });
+ });
it('should build downcased hyphenated url when given a downcased hyphenated city', () => {
- const result = replaceTemplatedUrl({
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|downcase|hyphenated}&fage=${age}'
- }, userData2)
+ const result = replaceTemplatedUrl(
+ {
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|downcase|hyphenated}&fage=${age}',
+ },
+ userData2,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=west-montego&fage=24' })
- })
+ expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=west-montego&fage=24' });
+ });
it('should build downcased hyphenated url when given a downcased hyphenated city in a different order', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated|downcase}&fage=${age}'
- }, userData2)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated|downcase}&fage=${age}',
+ },
+ userData2,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=west-montego&fage=24' })
- })
+ expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=west-montego&fage=24' });
+ });
it('should build downcased snakecase url when given a downcased snakecase city', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|snakecase|downcase}&fage=${age}'
- }, userData2)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|snakecase|downcase}&fage=${age}',
+ },
+ userData2,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=west_montego&fage=24' })
- })
+ expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=ny&city=west_montego&fage=24' });
+ });
it('should support value substitution via defaultIfEmpty:', () => {
const testCases = [
@@ -427,59 +477,73 @@ describe('Actions', () => {
// eslint-disable-next-line no-template-curly-in-string
input: 'https://example.com/a/${middleName|defaultIfEmpty:~}/b',
expected: 'https://example.com/a/~/b',
- data: userData2
+ data: userData2,
},
{
// eslint-disable-next-line no-template-curly-in-string
input: 'https://example.com/a/${middleName|downcase|defaultIfEmpty:anything}/b',
expected: 'https://example.com/a/anything/b',
- data: userData2
+ data: userData2,
},
{
// eslint-disable-next-line no-template-curly-in-string
input: 'https://example.com/a/${middleName|downcase|defaultIfEmpty:anything}/b',
expected: 'https://example.com/a/kittie/b',
- data: { ...userData2, middleName: 'Kittie' }
- }
- ]
+ data: { ...userData2, middleName: 'Kittie' },
+ },
+ ];
for (const testCase of testCases) {
- const result = replaceTemplatedUrl({
- id: 0,
- url: testCase.input
- }, testCase.data)
- expect(result).toEqual({ url: testCase.expected })
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ url: testCase.input,
+ },
+ testCase.data,
+ );
+ expect(result).toEqual({ url: testCase.expected });
}
- })
+ });
it('should build hyphenated url when given hyphenated state', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state|stateFull|hyphenated}&city=${city}&fage=${age}'
- }, userData2)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state|stateFull|hyphenated}&city=${city}&fage=${age}',
+ },
+ userData2,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=New-York&city=West+Montego&fage=24' })
- })
+ expect(result).toEqual({
+ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=New-York&city=West+Montego&fage=24',
+ });
+ });
it('should build url when given valid data and age range', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- // eslint-disable-next-line no-template-curly-in-string
- url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}',
- ageRange: ['18-30', '31-40', '41-50']
- }, userData)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ // eslint-disable-next-line no-template-curly-in-string
+ url: 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}',
+ ageRange: ['18-30', '31-40', '41-50'],
+ },
+ userData,
+ );
- expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=il&city=Chicago&fage=18-30' })
- })
+ expect(result).toEqual({ url: 'https://example.com/profile/search?fname=John&lname=Smith&state=il&city=Chicago&fage=18-30' });
+ });
it('should error when given an invalid action', () => {
- const result = replaceTemplatedUrl({
- id: 0,
- url: null
- }, userData)
+ const result = replaceTemplatedUrl(
+ {
+ id: 0,
+ url: null,
+ },
+ userData,
+ );
- expect(result).toEqual({ error: 'Error: No url provided.' })
- })
+ expect(result).toEqual({ error: 'Error: No url provided.' });
+ });
it('should accept any inputs and not crash', () => {
fc.assert(
@@ -487,61 +551,60 @@ describe('Actions', () => {
fc.oneof(
fc.anything(),
fc.record({
- url: fc.anything()
- })
- ),
- fc.oneof(
- fc.anything(),
- fc.dictionary(fc.string(), fc.oneof(fc.string(), fc.integer(), fc.boolean()))
+ url: fc.anything(),
+ }),
),
+ fc.oneof(fc.anything(), fc.dictionary(fc.string(), fc.oneof(fc.string(), fc.integer(), fc.boolean()))),
(action, userData) => {
- const result = replaceTemplatedUrl(action, userData)
- expect('url' in result || 'error' in result)
+ const result = replaceTemplatedUrl(action, userData);
+ expect('url' in result || 'error' in result);
if ('error' in result) {
- expect(typeof result.error).toEqual('string')
+ expect(typeof result.error).toEqual('string');
}
if ('url' in result) {
- const url = new URL(result.url)
- expect(url).toBeDefined()
+ const url = new URL(result.url);
+ expect(url).toBeDefined();
}
- }
- )
- )
- })
+ },
+ ),
+ );
+ });
it('should template the url with random inputs and produce a valid URL', () => {
fc.assert(
fc.property(
fc.record({
- // eslint-disable-next-line no-template-curly-in-string
- url: fc.constant('https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated}&fage=${age}')
+ url: fc.constant(
+ // eslint-disable-next-line no-template-curly-in-string
+ 'https://example.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city|hyphenated}&fage=${age}',
+ ),
}),
fc.record({
firstName: fc.string(),
lastName: fc.string(),
city: fc.string(),
state: fc.string(),
- age: fc.oneof(fc.string(), fc.integer())
+ age: fc.oneof(fc.string(), fc.integer()),
}),
(action, userData) => {
- const result = replaceTemplatedUrl(action, userData)
- expect('url' in result || 'error' in result)
+ const result = replaceTemplatedUrl(action, userData);
+ expect('url' in result || 'error' in result);
if ('url' in result) {
- const url = new URL(result.url)
- expect(url).toBeDefined()
+ const url = new URL(result.url);
+ expect(url).toBeDefined();
}
- }
- )
- )
- })
+ },
+ ),
+ );
+ });
it('should test the regex replacer with random values', () => {
- const variable = fc.string().map(randomMiddle => {
- return '${' + randomMiddle + '}'
- })
+ const variable = fc.string().map((randomMiddle) => {
+ return '${' + randomMiddle + '}';
+ });
- const padded = variable.map(randomMiddle => {
- return '--' + randomMiddle + '--'
- })
+ const padded = variable.map((randomMiddle) => {
+ return '--' + randomMiddle + '--';
+ });
fc.assert(
fc.property(
@@ -549,132 +612,128 @@ describe('Actions', () => {
fc.object(),
fc.dictionary(fc.string(), fc.oneof(fc.string(), fc.integer())),
(input, action, userData) => {
- const output = processTemplateStringWithUserData(input, /** @type {any} */(action), userData)
- expect(typeof output).toEqual('string')
- }
- )
- )
- })
- })
-})
+ const output = processTemplateStringWithUserData(input, /** @type {any} */ (action), userData);
+ expect(typeof output).toEqual('string');
+ },
+ ),
+ );
+ });
+ });
+});
describe('generators', () => {
describe('generateRandomPhoneNumber', () => {
it('generates a string of integers of an appropriate size', () => {
- const phoneNumber = generatePhoneNumber()
+ const phoneNumber = generatePhoneNumber();
- expect(typeof phoneNumber).toEqual('string')
- expect(phoneNumber.length).toBe(10)
- expect(phoneNumber).toMatch(/^\d{10}$/)
- })
- })
+ expect(typeof phoneNumber).toEqual('string');
+ expect(phoneNumber.length).toBe(10);
+ expect(phoneNumber).toMatch(/^\d{10}$/);
+ });
+ });
describe('generateZipCode', () => {
it('generates a string of integers of an appropriate size', () => {
- const zipCode = generateZipCode()
+ const zipCode = generateZipCode();
- expect(typeof zipCode).toEqual('string')
- expect(zipCode.length).toBe(5)
- expect(zipCode).toMatch(/^\d{5}$/)
- })
- })
+ expect(typeof zipCode).toEqual('string');
+ expect(zipCode.length).toBe(5);
+ expect(zipCode).toMatch(/^\d{5}$/);
+ });
+ });
describe('generateStreetAddress', () => {
it('generates a string of integers of an appropriate size', () => {
Array.from({ length: 30 }).forEach(() => {
- const streetAddress = generateStreetAddress()
- expect(typeof streetAddress).toEqual('string')
- expect(streetAddress).toMatch(/^\d+ [A-Za-z]+(?: [A-Za-z]+)?$/)
- })
- })
- })
-})
+ const streetAddress = generateStreetAddress();
+ expect(typeof streetAddress).toEqual('string');
+ expect(streetAddress).toMatch(/^\d+ [A-Za-z]+(?: [A-Za-z]+)?$/);
+ });
+ });
+ });
+});
describe('utils', () => {
describe('generateRandomInt', () => {
it('generates an integers between the min and max values', () => {
fc.assert(
fc.property(fc.integer(), fc.integer(), (a, b) => {
- const min = Math.min(a, b)
- const max = Math.max(a, b)
+ const min = Math.min(a, b);
+ const max = Math.max(a, b);
- const result = generateRandomInt(min, max)
+ const result = generateRandomInt(min, max);
- return (
- Number.isInteger(result) &&
- result >= min &&
- result <= max
- )
- })
- )
- })
- })
+ return Number.isInteger(result) && result >= min && result <= max;
+ }),
+ );
+ });
+ });
describe('generateIdFromProfile', () => {
it('generates a hash from a profile', async () => {
const profile = {
firstName: 'John',
- lastName: 'Doe'
- }
+ lastName: 'Doe',
+ };
- const result = await hashObject(profile)
+ const result = await hashObject(profile);
- expect(typeof result).toEqual('string')
- expect(result.length).toBe(40)
- expect(result).toMatch(/^[0-9a-f]{40}$/)
- })
+ expect(typeof result).toEqual('string');
+ expect(result.length).toBe(40);
+ expect(result).toMatch(/^[0-9a-f]{40}$/);
+ });
it('generates a stable hash from a profile', async () => {
const profile = {
firstName: 'John',
- lastName: 'Doe'
- }
+ lastName: 'Doe',
+ };
- const originalResult = await hashObject(profile)
+ const originalResult = await hashObject(profile);
- profile.middleName = 'David'
+ profile.middleName = 'David';
- const updatedResult = await hashObject(profile)
- expect(originalResult).not.toEqual(updatedResult)
+ const updatedResult = await hashObject(profile);
+ expect(originalResult).not.toEqual(updatedResult);
- delete profile.middleName
+ delete profile.middleName;
- const finalResult = await hashObject(profile)
- expect(finalResult).toEqual(originalResult)
- })
- })
+ const finalResult = await hashObject(profile);
+ expect(finalResult).toEqual(originalResult);
+ });
+ });
describe('sortAddressesByStateAndCity', () => {
it('sorts addresses by state and city', () => {
const addresses = [
{
city: 'Houston',
- state: 'TX'
+ state: 'TX',
},
{
city: 'Ontario',
- state: 'CA'
+ state: 'CA',
},
{
city: 'Dallas',
- state: 'TX'
- }
- ]
+ state: 'TX',
+ },
+ ];
- const result = sortAddressesByStateAndCity(addresses)
+ const result = sortAddressesByStateAndCity(addresses);
expect(result).toEqual([
{
city: 'Ontario',
- state: 'CA'
+ state: 'CA',
},
{
city: 'Dallas',
- state: 'TX'
+ state: 'TX',
},
{
city: 'Houston',
- state: 'TX'
- }
- ])
- })
- })
-})
+ state: 'TX',
+ },
+ ]);
+ });
+ });
+});
diff --git a/injected/unit-test/canvas.js b/injected/unit-test/canvas.js
index 18da139f3..420a7f831 100644
--- a/injected/unit-test/canvas.js
+++ b/injected/unit-test/canvas.js
@@ -1,58 +1,60 @@
// eslint-disable-next-line no-redeclare
-import ImageData from '@canvas/image-data'
-import { modifyPixelData } from '../src/canvas.js'
+import ImageData from '@canvas/image-data';
+import { modifyPixelData } from '../src/canvas.js';
-function calculateCheckSum (imageData) {
- return imageData.data.reduce((t, v) => { return t + v }, 0)
+function calculateCheckSum(imageData) {
+ return imageData.data.reduce((t, v) => {
+ return t + v;
+ }, 0);
}
// Produce some fake Image data, the values aren't really that important
-function computeSampleImageData () {
- const height = 100
- const width = 100
- const inVal = []
+function computeSampleImageData() {
+ const height = 100;
+ const width = 100;
+ const inVal = [];
// Construct some fake pixel data
for (let i = 0; i < width; i++) {
for (let j = 0; j < height; j++) {
// RGBA vals
for (let k = 0; k < 4; k++) {
- inVal.push(i + j)
+ inVal.push(i + j);
}
}
}
- return new ImageData(new Uint8ClampedArray(inVal), height, width)
+ return new ImageData(new Uint8ClampedArray(inVal), height, width);
}
describe('Canvas', () => {
it('Modifying a canvas should make a difference', () => {
- const imageData = computeSampleImageData()
- const inCS = calculateCheckSum(imageData)
- modifyPixelData(imageData, 'example.com', 'randomkey', 100)
- const outCS = calculateCheckSum(imageData)
- expect(inCS).not.toEqual(outCS)
- })
+ const imageData = computeSampleImageData();
+ const inCS = calculateCheckSum(imageData);
+ modifyPixelData(imageData, 'example.com', 'randomkey', 100);
+ const outCS = calculateCheckSum(imageData);
+ expect(inCS).not.toEqual(outCS);
+ });
it('Ensure image data from a different domain is unique', () => {
- const imageData = computeSampleImageData()
- modifyPixelData(imageData, 'example.com', 'randomkey', 100)
- const example = calculateCheckSum(imageData)
+ const imageData = computeSampleImageData();
+ modifyPixelData(imageData, 'example.com', 'randomkey', 100);
+ const example = calculateCheckSum(imageData);
- const imageData2 = computeSampleImageData()
- modifyPixelData(imageData2, 'test.com', 'randomkey', 100)
- const test = calculateCheckSum(imageData2)
+ const imageData2 = computeSampleImageData();
+ modifyPixelData(imageData2, 'test.com', 'randomkey', 100);
+ const test = calculateCheckSum(imageData2);
- expect(example).not.toEqual(test)
- })
+ expect(example).not.toEqual(test);
+ });
it('Ensure when key rotates the data output is unique', () => {
- const imageData = computeSampleImageData()
- modifyPixelData(imageData, 'example.com', 'randomkey', 100)
- const one = calculateCheckSum(imageData)
+ const imageData = computeSampleImageData();
+ modifyPixelData(imageData, 'example.com', 'randomkey', 100);
+ const one = calculateCheckSum(imageData);
- const imageData2 = computeSampleImageData()
- modifyPixelData(imageData2, 'example.com', 'randomkey2', 100)
- const two = calculateCheckSum(imageData2)
+ const imageData2 = computeSampleImageData();
+ modifyPixelData(imageData2, 'example.com', 'randomkey2', 100);
+ const two = calculateCheckSum(imageData2);
- expect(one).not.toEqual(two)
- })
-})
+ expect(one).not.toEqual(two);
+ });
+});
diff --git a/injected/unit-test/content-feature.js b/injected/unit-test/content-feature.js
index d471bf5de..f9e28737e 100644
--- a/injected/unit-test/content-feature.js
+++ b/injected/unit-test/content-feature.js
@@ -1,21 +1,21 @@
-import ContentFeature from '../src/content-feature.js'
+import ContentFeature from '../src/content-feature.js';
describe('ContentFeature class', () => {
it('Should trigger getFeatureSettingEnabled for the correct domain', () => {
- let didRun = false
+ let didRun = false;
class MyTestFeature extends ContentFeature {
- init () {
- expect(this.getFeatureSetting('test')).toBe('enabled3')
- expect(this.getFeatureSetting('otherTest')).toBe('enabled')
- expect(this.getFeatureSetting('otherOtherTest')).toBe('ding')
- expect(this.getFeatureSetting('arrayTest')).toBe('enabledArray')
- didRun = true
+ init() {
+ expect(this.getFeatureSetting('test')).toBe('enabled3');
+ expect(this.getFeatureSetting('otherTest')).toBe('enabled');
+ expect(this.getFeatureSetting('otherOtherTest')).toBe('ding');
+ expect(this.getFeatureSetting('arrayTest')).toBe('enabledArray');
+ didRun = true;
}
}
- const me = new MyTestFeature('test')
+ const me = new MyTestFeature('test');
me.callInit({
site: {
- domain: 'beep.example.com'
+ domain: 'beep.example.com',
},
featureSettings: {
test: {
@@ -28,159 +28,151 @@ describe('ContentFeature class', () => {
domain: 'example.com',
patchSettings: [
{ op: 'replace', path: '/test', value: 'enabled2' },
- { op: 'replace', path: '/otherTest', value: 'enabled' }
- ]
+ { op: 'replace', path: '/otherTest', value: 'enabled' },
+ ],
},
{
domain: 'beep.example.com',
- patchSettings: [
- { op: 'replace', path: '/test', value: 'enabled3' }
- ]
+ patchSettings: [{ op: 'replace', path: '/test', value: 'enabled3' }],
},
{
domain: ['meep.com', 'example.com'],
- patchSettings: [
- { op: 'replace', path: '/arrayTest', value: 'enabledArray' }
- ]
- }
- ]
- }
- }
- })
- expect(didRun).withContext('Should run').toBeTrue()
- })
+ patchSettings: [{ op: 'replace', path: '/arrayTest', value: 'enabledArray' }],
+ },
+ ],
+ },
+ },
+ });
+ expect(didRun).withContext('Should run').toBeTrue();
+ });
describe('addDebugFlag', () => {
class MyTestFeature extends ContentFeature {
// eslint-disable-next-line
// @ts-ignore partial mock
messaging = {
-
- notify (name, data) {}
- }
+ notify(name, data) {},
+ };
}
- let feature
+ let feature;
beforeEach(() => {
- feature = new MyTestFeature('someFeatureName')
- })
+ feature = new MyTestFeature('someFeatureName');
+ });
it('should not send duplicate flags', () => {
// send some flag
- feature.addDebugFlag('someflag')
+ feature.addDebugFlag('someflag');
// send it again
- const spyNotify = spyOn(feature.messaging, 'notify')
- feature.addDebugFlag('someflag')
- expect(spyNotify).not.toHaveBeenCalled()
- })
+ const spyNotify = spyOn(feature.messaging, 'notify');
+ feature.addDebugFlag('someflag');
+ expect(spyNotify).not.toHaveBeenCalled();
+ });
it('should send an empty suffix by default', () => {
- const spyNotify = spyOn(feature.messaging, 'notify')
- feature.addDebugFlag()
- expect(spyNotify).toHaveBeenCalledWith(
- 'addDebugFlag',
- {
- flag: 'someFeatureName'
- }
- )
- })
- })
+ const spyNotify = spyOn(feature.messaging, 'notify');
+ feature.addDebugFlag();
+ expect(spyNotify).toHaveBeenCalledWith('addDebugFlag', {
+ flag: 'someFeatureName',
+ });
+ });
+ });
describe('defineProperty', () => {
class MyTestFeature extends ContentFeature {
- addDebugFlag () {
- this.debugFlagAdded = true
+ addDebugFlag() {
+ this.debugFlagAdded = true;
}
}
- let feature
+ let feature;
beforeEach(() => {
- feature = new MyTestFeature('someFeatureName')
- })
+ feature = new MyTestFeature('someFeatureName');
+ });
it('should add debug flag to value descriptors', () => {
- const object = {}
+ const object = {};
feature.defineProperty(object, 'someProp', {
value: () => 'someValue',
writable: true,
enumerable: true,
- configurable: true
- })
- expect(feature.debugFlagAdded).toBeUndefined()
- expect(object.someProp()).toBe('someValue')
- const newDesc = Object.getOwnPropertyDescriptor(object, 'someProp')
- expect(newDesc).toBeDefined()
+ configurable: true,
+ });
+ expect(feature.debugFlagAdded).toBeUndefined();
+ expect(object.someProp()).toBe('someValue');
+ const newDesc = Object.getOwnPropertyDescriptor(object, 'someProp');
+ expect(newDesc).toBeDefined();
// @ts-expect-error - this must be defined
- newDesc.value = null
+ newDesc.value = null;
expect(newDesc).toEqual({
value: null,
writable: true,
enumerable: true,
- configurable: true
- })
- expect(feature.debugFlagAdded).toBeTrue()
- })
+ configurable: true,
+ });
+ expect(feature.debugFlagAdded).toBeTrue();
+ });
it('should add debug flag to get descriptors', () => {
- const object = {}
+ const object = {};
feature.defineProperty(object, 'someProp', {
get: () => 'someValue',
enumerable: true,
- configurable: true
- })
- expect(feature.debugFlagAdded).toBeUndefined()
- expect(object.someProp).toBe('someValue')
- const newDesc = Object.getOwnPropertyDescriptor(object, 'someProp')
- expect(newDesc).toBeDefined()
+ configurable: true,
+ });
+ expect(feature.debugFlagAdded).toBeUndefined();
+ expect(object.someProp).toBe('someValue');
+ const newDesc = Object.getOwnPropertyDescriptor(object, 'someProp');
+ expect(newDesc).toBeDefined();
// @ts-expect-error - this must be defined
- newDesc.get = null
+ newDesc.get = null;
expect(newDesc).toEqual({
// @ts-expect-error get is overridden
get: null,
set: undefined,
enumerable: true,
- configurable: true
- })
- expect(feature.debugFlagAdded).toBeTrue()
- })
+ configurable: true,
+ });
+ expect(feature.debugFlagAdded).toBeTrue();
+ });
it('should add debug flag to set descriptors', () => {
- const object = {}
+ const object = {};
feature.defineProperty(object, 'someProp', {
set: () => {},
enumerable: true,
- configurable: true
- })
- expect(feature.debugFlagAdded).toBeUndefined()
- expect(object.someProp = 'someValue').toBe('someValue')
- const newDesc = Object.getOwnPropertyDescriptor(object, 'someProp')
- expect(newDesc).toBeDefined()
+ configurable: true,
+ });
+ expect(feature.debugFlagAdded).toBeUndefined();
+ expect((object.someProp = 'someValue')).toBe('someValue');
+ const newDesc = Object.getOwnPropertyDescriptor(object, 'someProp');
+ expect(newDesc).toBeDefined();
// @ts-expect-error - this must be defined
- newDesc.set = null
+ newDesc.set = null;
expect(newDesc).toEqual({
get: undefined,
// @ts-expect-error set is overridden
set: null,
enumerable: true,
- configurable: true
- })
- expect(feature.debugFlagAdded).toBeTrue()
- })
+ configurable: true,
+ });
+ expect(feature.debugFlagAdded).toBeTrue();
+ });
it('should not change toString()', () => {
- const object = {}
- const fn = () => 'someValue'
+ const object = {};
+ const fn = () => 'someValue';
feature.defineProperty(object, 'someProp', {
value: fn,
writable: true,
enumerable: true,
- configurable: true
- })
- expect(object.someProp()).toBe('someValue')
- expect(object.someProp.toString()).toBe(fn.toString())
- expect(Object.prototype.toString.apply(object.someProp)).toBe(Object.prototype.toString.apply(fn))
- expect(`${object.someProp}`).toBe(`${fn}`)
- expect(object.someProp.toString.toString()).toBe(fn.toString.toString())
+ configurable: true,
+ });
+ expect(object.someProp()).toBe('someValue');
+ expect(object.someProp.toString()).toBe(fn.toString());
+ expect(Object.prototype.toString.apply(object.someProp)).toBe(Object.prototype.toString.apply(fn));
+ expect(`${object.someProp}`).toBe(`${fn}`);
+ expect(object.someProp.toString.toString()).toBe(fn.toString.toString());
// we don't expect it to wrap toString() more than 2 levels deep
- expect(object.someProp.toString.toString.toString()).not.toBe(fn.toString.toString.toString())
- })
- })
-})
+ expect(object.someProp.toString.toString.toString()).not.toBe(fn.toString.toString.toString());
+ });
+ });
+});
diff --git a/injected/unit-test/cookie.js b/injected/unit-test/cookie.js
index c53706062..4cd5bb480 100644
--- a/injected/unit-test/cookie.js
+++ b/injected/unit-test/cookie.js
@@ -1,60 +1,64 @@
-import { Cookie } from '../src/cookie.js'
+import { Cookie } from '../src/cookie.js';
describe('Cookie', () => {
describe('constructor', () => {
it('should parse a weird but valid cookie', () => {
- const cki = new Cookie('foo=bar=bar&foo=foo&John=Doe&Doe=John; Max-Age=1000; Domain=.example.com; Path=/; HttpOnly; Secure')
- expect(cki.name).toEqual('foo')
- expect(cki.value).toEqual('bar=bar&foo=foo&John=Doe&Doe=John')
- expect(cki.maxAge).toEqual('1000')
+ const cki = new Cookie('foo=bar=bar&foo=foo&John=Doe&Doe=John; Max-Age=1000; Domain=.example.com; Path=/; HttpOnly; Secure');
+ expect(cki.name).toEqual('foo');
+ expect(cki.value).toEqual('bar=bar&foo=foo&John=Doe&Doe=John');
+ expect(cki.maxAge).toEqual('1000');
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
- expect(cki.domain).toEqual('.example.com')
- })
- })
+ expect(cki.domain).toEqual('.example.com');
+ });
+ });
describe('.getExpiry', () => {
it('cookie expires in the past', () => {
- const cki = new Cookie('jsdata=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=good.third-party.site ;path=/privacy-protections/storage-blocking/iframe.html')
- expect(cki.getExpiry()).toBeLessThan(0)
- })
+ const cki = new Cookie(
+ 'jsdata=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=good.third-party.site ;path=/privacy-protections/storage-blocking/iframe.html',
+ );
+ expect(cki.getExpiry()).toBeLessThan(0);
+ });
it('cookie expires in the future', () => {
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
- const expectedExpiry = (new Date('Wed, 21 Aug 2030 20:00:00 UTC') - new Date()) / 1000
- const cki = new Cookie('jsdata=783; expires= Wed, 21 Aug 2030 20:00:00 UTC; Secure; SameSite=Lax')
- expect(cki.getExpiry()).toBeCloseTo(expectedExpiry, 0)
- })
+ const expectedExpiry = (new Date('Wed, 21 Aug 2030 20:00:00 UTC') - new Date()) / 1000;
+ const cki = new Cookie('jsdata=783; expires= Wed, 21 Aug 2030 20:00:00 UTC; Secure; SameSite=Lax');
+ expect(cki.getExpiry()).toBeCloseTo(expectedExpiry, 0);
+ });
it('cookie with max-age', () => {
- const cki = new Cookie('jsdata=783; expires= Wed, 21 Aug 2030 20:00:00 UTC; Secure; SameSite=Lax; max-age=100')
- expect(cki.getExpiry()).toBe(100)
- })
+ const cki = new Cookie('jsdata=783; expires= Wed, 21 Aug 2030 20:00:00 UTC; Secure; SameSite=Lax; max-age=100');
+ expect(cki.getExpiry()).toBe(100);
+ });
it('session cookie', () => {
- const cki = new Cookie('jsdata=783')
- expect(cki.getExpiry()).toBeNaN()
- })
+ const cki = new Cookie('jsdata=783');
+ expect(cki.getExpiry()).toBeNaN();
+ });
it('cookie with invalid date expiry', () => {
- const cki = new Cookie('jsdata=783; expires= Wed, 40 Aug 2030 20:00:00 UTC; Secure; SameSite=Lax')
- expect(cki.getExpiry()).toBeNaN()
- })
+ const cki = new Cookie('jsdata=783; expires= Wed, 40 Aug 2030 20:00:00 UTC; Secure; SameSite=Lax');
+ expect(cki.getExpiry()).toBeNaN();
+ });
it('cookie with invalid max-age expiry', () => {
- const cki = new Cookie('jsdata=783; Secure; SameSite=Lax; max-age=number')
- expect(cki.getExpiry()).toBeNaN()
- })
- })
+ const cki = new Cookie('jsdata=783; Secure; SameSite=Lax; max-age=number');
+ expect(cki.getExpiry()).toBeNaN();
+ });
+ });
describe('maxAge setter', () => {
it('modifies cookie expiry', () => {
- const cki = new Cookie('jsdata=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=good.third-party.site ;path=/privacy-protections/storage-blocking/iframe.html')
- expect(cki.getExpiry()).toBeLessThan(0)
- cki.maxAge = 100
- expect(cki.getExpiry()).toBe(100)
- expect(cki.toString().indexOf('max-age=100')).not.toBe(-1)
- })
+ const cki = new Cookie(
+ 'jsdata=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=good.third-party.site ;path=/privacy-protections/storage-blocking/iframe.html',
+ );
+ expect(cki.getExpiry()).toBeLessThan(0);
+ cki.maxAge = 100;
+ expect(cki.getExpiry()).toBe(100);
+ expect(cki.toString().indexOf('max-age=100')).not.toBe(-1);
+ });
it('updates existing max-age', () => {
- const cki = new Cookie('jsdata=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=good.third-party.site ;max-age=50')
- expect(cki.getExpiry()).toBe(50)
- cki.maxAge = 100
- expect(cki.getExpiry()).toBe(100)
- expect(cki.toString().indexOf('max-age=100')).not.toBe(-1)
- })
- })
-})
+ const cki = new Cookie('jsdata=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=good.third-party.site ;max-age=50');
+ expect(cki.getExpiry()).toBe(50);
+ cki.maxAge = 100;
+ expect(cki.getExpiry()).toBe(100);
+ expect(cki.toString().indexOf('max-age=100')).not.toBe(-1);
+ });
+ });
+});
diff --git a/injected/unit-test/dom-utils.js b/injected/unit-test/dom-utils.js
index fb86f184f..dd82de096 100644
--- a/injected/unit-test/dom-utils.js
+++ b/injected/unit-test/dom-utils.js
@@ -1,29 +1,36 @@
-import { html } from '../src/dom-utils.js'
+import { html } from '../src/dom-utils.js';
describe('dom-utils.js - escapedTemplate', () => {
const tests = [
{ title: 'single', input: () => html`Foo
`, expected: 'Foo
' },
- { title: 'siblings', input: () => html`Foo
Bar
`, expected: 'Foo
Bar
' },
+ {
+ title: 'siblings',
+ input: () =>
+ html`Foo
+ Bar
`,
+ expected: `Foo
+ Bar
`,
+ },
{ title: 'nested', input: () => html``, expected: '' },
{
title: 'loop',
input: () => {
- const items = [{ value: 'foo' }, { value: 'bar' }]
+ const items = [{ value: 'foo' }, { value: 'bar' }];
return html`Heading
- ${items.map(item => html`- ${item.value}
`)};
-
`
+ ${items.map((item) => html`${item.value}`)};
+ `;
},
expected: `Heading
`
- }
- ]
+ `,
+ },
+ ];
for (const test of tests) {
it(`should generate ${test.title}`, () => {
- const actual = test.input().toString()
- expect(actual).toEqual(test.expected)
- })
+ const actual = test.input().toString();
+ expect(actual).toEqual(test.expected);
+ });
}
-})
+});
diff --git a/injected/unit-test/features.js b/injected/unit-test/features.js
index 34c881850..7bf937725 100644
--- a/injected/unit-test/features.js
+++ b/injected/unit-test/features.js
@@ -1,4 +1,4 @@
-import { platformSupport } from '../src/features.js'
+import { platformSupport } from '../src/features.js';
describe('Features definition', () => {
it('calls `webCompat` before `fingerPrintingScreenSize` https://app.asana.com/0/1177771139624306/1204944717262422/f', () => {
@@ -16,7 +16,7 @@ describe('Features definition', () => {
'fingerprintingTemporaryStorage',
'navigatorInterface',
'elementHiding',
- 'exceptionHandler'
- ])
- })
-})
+ 'exceptionHandler',
+ ]);
+ });
+});
diff --git a/injected/unit-test/fixtures/feature-includes.js b/injected/unit-test/fixtures/feature-includes.js
index f55e92a94..3a7555888 100644
--- a/injected/unit-test/fixtures/feature-includes.js
+++ b/injected/unit-test/fixtures/feature-includes.js
@@ -1,2 +1,2 @@
-import platformFeatures from 'ddg:platformFeatures'
-console.log(platformFeatures)
+import platformFeatures from 'ddg:platformFeatures';
+console.log(platformFeatures);
diff --git a/injected/unit-test/helpers/pollyfil-for-process-globals.js b/injected/unit-test/helpers/pollyfil-for-process-globals.js
index d8dab953c..95b773101 100644
--- a/injected/unit-test/helpers/pollyfil-for-process-globals.js
+++ b/injected/unit-test/helpers/pollyfil-for-process-globals.js
@@ -1,4 +1,4 @@
-export default function setup () {
+export default function setup() {
// Pollyfill for globalThis methods needed in processConfig
globalThis.document = {
referrer: 'http://localhost:8080',
@@ -6,15 +6,15 @@ export default function setup () {
href: 'http://localhost:8080',
// @ts-expect-error - ancestorOrigins is not defined in the type definition
ancestorOrigins: {
- length: 0
- }
- }
- }
+ length: 0,
+ },
+ },
+ };
globalThis.location = {
href: 'http://localhost:8080',
// @ts-expect-error - ancestorOrigins is not defined in the type definition
ancestorOrigins: {
- length: 0
- }
- }
+ length: 0,
+ },
+ };
}
diff --git a/injected/unit-test/messaging.js b/injected/unit-test/messaging.js
index 14059045e..ba6f6d482 100644
--- a/injected/unit-test/messaging.js
+++ b/injected/unit-test/messaging.js
@@ -2,237 +2,247 @@ import {
Messaging,
MessagingContext,
TestTransportConfig,
- RequestMessage, NotificationMessage, Subscription, MessageResponse, SubscriptionEvent
-} from '@duckduckgo/messaging'
-import { AndroidMessagingConfig } from '@duckduckgo/messaging/lib/android.js'
+ RequestMessage,
+ NotificationMessage,
+ Subscription,
+ MessageResponse,
+ SubscriptionEvent,
+} from '@duckduckgo/messaging';
+import { AndroidMessagingConfig } from '@duckduckgo/messaging/lib/android.js';
describe('Messaging Transports', () => {
it('calls transport with a RequestMessage', () => {
- const { messaging, transport } = createMessaging()
+ const { messaging, transport } = createMessaging();
- const spy = spyOn(transport, 'request')
+ const spy = spyOn(transport, 'request');
- messaging.request('helloWorld', { foo: 'bar' })
+ messaging.request('helloWorld', { foo: 'bar' });
// grab the auto-generated `id` field
- const [requestMessage] = spy.calls.first()?.args ?? []
- expect(typeof requestMessage.id).toBe('string')
- expect(requestMessage.id.length).toBeGreaterThan(0)
-
- expect(spy).toHaveBeenCalledWith(new RequestMessage({
- context: 'contentScopeScripts',
- featureName: 'hello-world',
- id: requestMessage.id,
- method: 'helloWorld',
- params: { foo: 'bar' }
- }))
- })
+ const [requestMessage] = spy.calls.first()?.args ?? [];
+ expect(typeof requestMessage.id).toBe('string');
+ expect(requestMessage.id.length).toBeGreaterThan(0);
+
+ expect(spy).toHaveBeenCalledWith(
+ new RequestMessage({
+ context: 'contentScopeScripts',
+ featureName: 'hello-world',
+ id: requestMessage.id,
+ method: 'helloWorld',
+ params: { foo: 'bar' },
+ }),
+ );
+ });
it('calls transport with a NotificationMessage', () => {
- const { messaging, transport } = createMessaging()
+ const { messaging, transport } = createMessaging();
- const spy = spyOn(transport, 'notify')
+ const spy = spyOn(transport, 'notify');
- messaging.notify('helloWorld', { foo: 'bar' })
+ messaging.notify('helloWorld', { foo: 'bar' });
- expect(spy).toHaveBeenCalledWith(new NotificationMessage({
- context: 'contentScopeScripts',
- featureName: 'hello-world',
- method: 'helloWorld',
- params: { foo: 'bar' }
- }))
- })
+ expect(spy).toHaveBeenCalledWith(
+ new NotificationMessage({
+ context: 'contentScopeScripts',
+ featureName: 'hello-world',
+ method: 'helloWorld',
+ params: { foo: 'bar' },
+ }),
+ );
+ });
it('calls transport with a Subscription', () => {
- const { messaging, transport } = createMessaging()
+ const { messaging, transport } = createMessaging();
- const spy = spyOn(transport, 'subscribe')
- const callback = jasmine.createSpy()
+ const spy = spyOn(transport, 'subscribe');
+ const callback = jasmine.createSpy();
- messaging.subscribe('helloWorld', callback)
+ messaging.subscribe('helloWorld', callback);
- expect(spy).toHaveBeenCalledWith(new Subscription({
- context: 'contentScopeScripts',
- featureName: 'hello-world',
- subscriptionName: 'helloWorld'
- }), callback)
- })
-})
+ expect(spy).toHaveBeenCalledWith(
+ new Subscription({
+ context: 'contentScopeScripts',
+ featureName: 'hello-world',
+ subscriptionName: 'helloWorld',
+ }),
+ callback,
+ );
+ });
+});
describe('Android', () => {
/**
* @param {Record} target
* @return {AndroidMessagingConfig}
*/
- function createConfig (target) {
+ function createConfig(target) {
const config = new AndroidMessagingConfig({
target,
messageSecret: 'abc',
javascriptInterface: 'AnyRandomValue',
messageCallback: 'callback_abc_def',
- debug: false
- })
- return config
+ debug: false,
+ });
+ return config;
}
/**
* @param {string} featureName
* @param {AndroidMessagingConfig} config
*/
- function createContext (featureName, config) {
+ function createContext(featureName, config) {
const messageContextA = new MessagingContext({
context: 'contentScopeScripts',
featureName,
- env: 'development'
- })
- const messaging = new Messaging(messageContextA, config)
- return { messaging }
+ env: 'development',
+ });
+ const messaging = new Messaging(messageContextA, config);
+ return { messaging };
}
it('sends notification to 1 feature', () => {
- const spy = jasmine.createSpy()
+ const spy = jasmine.createSpy();
const target = {
AnyRandomValue: {
- process: spy
- }
- }
- const config = createConfig(target)
- const { messaging } = createContext('featureA', config)
- messaging.notify('helloWorld')
- const payload = '{"context":"contentScopeScripts","featureName":"featureA","method":"helloWorld","params":{}}'
- const secret = 'abc'
- expect(spy).toHaveBeenCalledWith(payload, secret)
- })
+ process: spy,
+ },
+ };
+ const config = createConfig(target);
+ const { messaging } = createContext('featureA', config);
+ messaging.notify('helloWorld');
+ const payload = '{"context":"contentScopeScripts","featureName":"featureA","method":"helloWorld","params":{}}';
+ const secret = 'abc';
+ expect(spy).toHaveBeenCalledWith(payload, secret);
+ });
it('sends notification to 2 separate features', () => {
- const spy = jasmine.createSpy()
+ const spy = jasmine.createSpy();
const target = {
AnyRandomValue: {
- process: spy
- }
- }
- const config = createConfig(target)
- const { messaging } = createContext('featureA', config)
- const { messaging: bMessaging } = createContext('featureB', config)
- messaging.notify('helloWorld')
- bMessaging.notify('helloWorld')
- const expected1 = '{"context":"contentScopeScripts","featureName":"featureA","method":"helloWorld","params":{}}'
- const expected2 = '{"context":"contentScopeScripts","featureName":"featureA","method":"helloWorld","params":{}}'
- expect(spy).toHaveBeenCalledTimes(2)
- expect(spy).toHaveBeenCalledWith(expected1, 'abc')
- expect(spy).toHaveBeenCalledWith(expected2, 'abc')
- })
+ process: spy,
+ },
+ };
+ const config = createConfig(target);
+ const { messaging } = createContext('featureA', config);
+ const { messaging: bMessaging } = createContext('featureB', config);
+ messaging.notify('helloWorld');
+ bMessaging.notify('helloWorld');
+ const expected1 = '{"context":"contentScopeScripts","featureName":"featureA","method":"helloWorld","params":{}}';
+ const expected2 = '{"context":"contentScopeScripts","featureName":"featureA","method":"helloWorld","params":{}}';
+ expect(spy).toHaveBeenCalledTimes(2);
+ expect(spy).toHaveBeenCalledWith(expected1, 'abc');
+ expect(spy).toHaveBeenCalledWith(expected2, 'abc');
+ });
it('sends request and gets response', async () => {
- const spy = jasmine.createSpy()
+ const spy = jasmine.createSpy();
/** @type {MessageResponse} */
- let msg
+ let msg;
/** @type {string} */
- let token
+ let token;
const target = {
AnyRandomValue: {
process: (outgoing, _token) => {
- msg = JSON.parse(outgoing)
- token = _token
- spy(outgoing, _token)
- }
- }
- }
- const config = createConfig(target)
- const { messaging } = createContext('featureA', config)
- const request = messaging.request('helloWorld')
+ msg = JSON.parse(outgoing);
+ token = _token;
+ spy(outgoing, _token);
+ },
+ },
+ };
+ const config = createConfig(target);
+ const { messaging } = createContext('featureA', config);
+ const request = messaging.request('helloWorld');
// @ts-expect-error - unit-testing
- if (!msg) throw new Error('must have set msg by this point in the test')
+ if (!msg) throw new Error('must have set msg by this point in the test');
// simulate a valid response
const response = new MessageResponse({
id: msg.id,
context: 'contentScopeScripts',
featureName: msg.featureName,
- result: { foo: 'bar' }
- })
+ result: { foo: 'bar' },
+ });
// pretend to call back from native
- target[config.messageCallback](config.messageSecret, response)
+ target[config.messageCallback](config.messageSecret, response);
// wait for it to resolve
- const result = await request
+ const result = await request;
const outgoingMessage = {
context: 'contentScopeScripts',
featureName: 'featureA',
method: 'helloWorld',
id: msg.id,
- params: {}
- }
+ params: {},
+ };
// Android messages are sent as a JSON string
- const asJsonString = JSON.stringify(outgoingMessage)
- expect(spy).toHaveBeenCalledWith(asJsonString, 'abc')
+ const asJsonString = JSON.stringify(outgoingMessage);
+ expect(spy).toHaveBeenCalledWith(asJsonString, 'abc');
// ensure the result is correct
- expect(result).toEqual({ foo: 'bar' })
+ expect(result).toEqual({ foo: 'bar' });
// @ts-expect-error - unit-testing
- expect(token).toEqual(config.messageSecret)
- })
+ expect(token).toEqual(config.messageSecret);
+ });
it('allows subscriptions', (done) => {
- const spy = jasmine.createSpy()
+ const spy = jasmine.createSpy();
const globalTarget = {
AnyRandomValue: {
- process: spy
- }
- }
- const config = createConfig(globalTarget)
- const { messaging } = createContext('featureA', config)
+ process: spy,
+ },
+ };
+ const config = createConfig(globalTarget);
+ const { messaging } = createContext('featureA', config);
// create the message as the native side would
const subEvent1 = new SubscriptionEvent({
context: 'contentScopeScripts',
featureName: 'featureA',
subscriptionName: 'onUpdate',
- params: { foo: 'bar' }
- })
+ params: { foo: 'bar' },
+ });
// subscribe to 'onUpdate'
messaging.subscribe('onUpdate', (data) => {
- expect(data).toEqual(subEvent1.params)
- done()
- })
+ expect(data).toEqual(subEvent1.params);
+ done();
+ });
// simulate native calling this method
- globalTarget[config.messageCallback](config.messageSecret, subEvent1)
- })
-})
+ globalTarget[config.messageCallback](config.messageSecret, subEvent1);
+ });
+});
/**
* Creates a test transport and Messaging instance for testing
*/
-function createMessaging () {
+function createMessaging() {
/** @type {import("@duckduckgo/messaging").MessagingTransport} */
const transport = {
-
- notify (msg) {
+ notify(msg) {
// test
},
-
+
request: (_msg) => {
// test
- return Promise.resolve(null)
+ return Promise.resolve(null);
},
-
- subscribe (_msg) {
+
+ subscribe(_msg) {
// test
return () => {
// test teardown
- }
- }
- }
+ };
+ },
+ };
- const testTransportConfig = new TestTransportConfig(transport)
+ const testTransportConfig = new TestTransportConfig(transport);
const messagingContext = new MessagingContext({
context: 'contentScopeScripts',
featureName: 'hello-world',
- env: 'development'
- })
+ env: 'development',
+ });
- const messaging = new Messaging(messagingContext, testTransportConfig)
+ const messaging = new Messaging(messagingContext, testTransportConfig);
- return { transport, messaging }
+ return { transport, messaging };
}
diff --git a/injected/unit-test/timer-utils.js b/injected/unit-test/timer-utils.js
index d943efc5a..2985f0255 100644
--- a/injected/unit-test/timer-utils.js
+++ b/injected/unit-test/timer-utils.js
@@ -1,81 +1,92 @@
-import fc from 'fast-check'
-import { DEFAULT_RETRY_CONFIG, retry } from '../src/timer-utils.js'
+import fc from 'fast-check';
+import { DEFAULT_RETRY_CONFIG, retry } from '../src/timer-utils.js';
describe('retry function tests', () => {
// This represents the default arguments to the `retry` helper function
- const defaultProps = () => fc.integer({ min: 1, max: 10 })
- .map(n => ({ ...DEFAULT_RETRY_CONFIG, maxAttempts: n }))
+ const defaultProps = () => fc.integer({ min: 1, max: 10 }).map((n) => ({ ...DEFAULT_RETRY_CONFIG, maxAttempts: n }));
it('should catch errors if the retried function always throws', async () => {
await fc.assert(
fc.asyncProperty(defaultProps(), async (config) => {
- const errorFunction = jasmine.createSpy('errorFunction', () => {
- // A function that always throws an error
- throw new Error('Always fails')
- }).and.callThrough()
+ const errorFunction = jasmine
+ .createSpy('errorFunction', () => {
+ // A function that always throws an error
+ throw new Error('Always fails');
+ })
+ .and.callThrough();
- const { result, exceptions } = await retry(errorFunction, config)
+ const { result, exceptions } = await retry(errorFunction, config);
// result should be undefined since we always throw
- expect(result).toBe(undefined)
+ expect(result).toBe(undefined);
// should have been called config.maxAttempts times
- expect(errorFunction.calls.count()).toEqual(config.maxAttempts)
+ expect(errorFunction.calls.count()).toEqual(config.maxAttempts);
// Size of errors should be equal to maxAttempts because the function always fails
- expect(exceptions.length).toEqual(config.maxAttempts)
- }), { numRuns: 10 })
- })
+ expect(exceptions.length).toEqual(config.maxAttempts);
+ }),
+ { numRuns: 10 },
+ );
+ });
it('should assign lastResult correctly and stop retrying when success is obtained', async () => {
await fc.assert(
fc.asyncProperty(defaultProps(), async (config) => {
- let callCount = 0
+ let callCount = 0;
/**
* @type {jasmine.Spy<() => Promise<{ success: string } | { error: { message: string } }>>}
*/
- const successfulFunction = jasmine.createSpy('successfulFunction', () => {
- callCount += 1
- // The function fails for the first (n-1) times and succeeds on the last try
- if (callCount === config.maxAttempts) {
- return Promise.resolve({ success: 'Function succeeded' })
- } else {
- return Promise.resolve({ error: { message: 'something went wrong' } })
- }
- }).and.callThrough()
+ const successfulFunction = jasmine
+ .createSpy('successfulFunction', () => {
+ callCount += 1;
+ // The function fails for the first (n-1) times and succeeds on the last try
+ if (callCount === config.maxAttempts) {
+ return Promise.resolve({ success: 'Function succeeded' });
+ } else {
+ return Promise.resolve({ error: { message: 'something went wrong' } });
+ }
+ })
+ .and.callThrough();
- const { result, exceptions } = await retry(successfulFunction, config)
+ const { result, exceptions } = await retry(successfulFunction, config);
// Expect retry to eventually return a successful result
- expect(result).toEqual({ success: 'Function succeeded' })
+ expect(result).toEqual({ success: 'Function succeeded' });
// should have been called config.maxAttempts times
- expect(successfulFunction.calls.count()).toEqual(config.maxAttempts)
+ expect(successfulFunction.calls.count()).toEqual(config.maxAttempts);
// no exceptions were thrown, so this should be empty
- expect(exceptions).toEqual([])
- }), { numRuns: 10 })
- })
+ expect(exceptions).toEqual([]);
+ }),
+ { numRuns: 10 },
+ );
+ });
it('should return last result if a function is never successful', async () => {
await fc.assert(
fc.asyncProperty(defaultProps(), async (config) => {
/**
* @type {jasmine.Spy<() => Promise<{ success: string } | { error: { message: string } }>>}
*/
- const errorFunction = jasmine.createSpy('successfulFunction', () => {
- return Promise.resolve({ error: { message: 'something went wrong' } })
- }).and.callThrough()
+ const errorFunction = jasmine
+ .createSpy('successfulFunction', () => {
+ return Promise.resolve({ error: { message: 'something went wrong' } });
+ })
+ .and.callThrough();
- const { result, exceptions } = await retry(errorFunction, config)
+ const { result, exceptions } = await retry(errorFunction, config);
// this line just helps typescript understand that we're expecting 'result.error'
- if (result && !('error' in result)) throw new Error('unreachable')
+ if (result && !('error' in result)) throw new Error('unreachable');
// should be just the last error as a `result`
- expect(result?.error.message).toEqual('something went wrong')
+ expect(result?.error.message).toEqual('something went wrong');
// should be empty since nothing threw
- expect(exceptions).toEqual([])
- }), { numRuns: 10 })
- })
-})
+ expect(exceptions).toEqual([]);
+ }),
+ { numRuns: 10 },
+ );
+ });
+});
diff --git a/injected/unit-test/utils.js b/injected/unit-test/utils.js
index e01719269..bd650b1b0 100644
--- a/injected/unit-test/utils.js
+++ b/injected/unit-test/utils.js
@@ -1,19 +1,19 @@
-import { matchHostname, postDebugMessage, initStringExemptionLists, processConfig, satisfiesMinVersion } from '../src/utils.js'
-import polyfillProcessGlobals from './helpers/pollyfil-for-process-globals.js'
+import { matchHostname, postDebugMessage, initStringExemptionLists, processConfig, satisfiesMinVersion } from '../src/utils.js';
+import polyfillProcessGlobals from './helpers/pollyfil-for-process-globals.js';
-polyfillProcessGlobals()
+polyfillProcessGlobals();
describe('Helpers checks', () => {
describe('matchHostname', () => {
it('Expect results on matchHostnames', () => {
- expect(matchHostname('b.domain.com', 'domain.com')).toBeTrue()
- expect(matchHostname('domain.com', 'b.domain.com')).toBeFalse()
- expect(matchHostname('domain.com', 'domain.com')).toBeTrue()
- expect(matchHostname('a.b.c.e.f.domain.com', 'domain.com')).toBeTrue()
- expect(matchHostname('otherdomain.com', 'domain.com')).toBeFalse()
- expect(matchHostname('domain.com', 'otherdomain.com')).toBeFalse()
- })
- })
+ expect(matchHostname('b.domain.com', 'domain.com')).toBeTrue();
+ expect(matchHostname('domain.com', 'b.domain.com')).toBeFalse();
+ expect(matchHostname('domain.com', 'domain.com')).toBeTrue();
+ expect(matchHostname('a.b.c.e.f.domain.com', 'domain.com')).toBeTrue();
+ expect(matchHostname('otherdomain.com', 'domain.com')).toBeFalse();
+ expect(matchHostname('domain.com', 'otherdomain.com')).toBeFalse();
+ });
+ });
it('processes config in expected way for minSupportedVersion and numeric versions', () => {
const configIn = {
@@ -21,79 +21,79 @@ describe('Helpers checks', () => {
testFeature: {
state: 'enabled',
settings: {
- beep: 'boop'
+ beep: 'boop',
},
- exceptions: []
+ exceptions: [],
},
testFeatureTooBig: {
state: 'enabled',
minSupportedVersion: 100,
settings: {},
- exceptions: []
+ exceptions: [],
},
testFeatureSmall: {
state: 'enabled',
minSupportedVersion: 99,
settings: {},
- exceptions: []
+ exceptions: [],
},
testFeatureSmaller: {
state: 'enabled',
minSupportedVersion: 98,
settings: {
- barp: true
+ barp: true,
},
- exceptions: []
+ exceptions: [],
},
testFeatureOutlier: {
state: 'enabled',
minSupportedVersion: 97000,
settings: {},
- exceptions: []
- }
+ exceptions: [],
+ },
},
- unprotectedTemporary: []
- }
+ unprotectedTemporary: [],
+ };
const processedConfig = processConfig(
configIn,
[],
{
platform: {
- name: 'android'
+ name: 'android',
},
versionNumber: 99,
- sessionKey: 'testSessionKey'
+ sessionKey: 'testSessionKey',
},
- []
- )
+ [],
+ );
expect(processedConfig).toEqual({
site: {
domain: 'localhost',
isBroken: false,
allowlisted: false,
// testFeatureTooBig is not enabled because it's minSupportedVersion is 100
- enabledFeatures: ['testFeature', 'testFeatureSmall', 'testFeatureSmaller']
+ enabledFeatures: ['testFeature', 'testFeatureSmall', 'testFeatureSmaller'],
},
featureSettings: {
testFeature: {
- beep: 'boop'
+ beep: 'boop',
},
testFeatureSmall: {},
testFeatureSmaller: {
- barp: true
- }
+ barp: true,
+ },
},
platform: {
name: 'android',
- version: 99
+ version: 99,
},
versionNumber: 99,
sessionKey: 'testSessionKey',
// import.meta.trackerLookup is undefined because we've not overloaded it
trackerLookup: undefined,
- bundledConfig: configIn
- })
- })
+ bundledConfig: configIn,
+ });
+ });
it('processes config in expected way for minSupportedVersion and string versions', () => {
const configIn = {
@@ -101,78 +101,78 @@ describe('Helpers checks', () => {
testFeature: {
state: 'enabled',
settings: {
- beep: 'boop'
+ beep: 'boop',
},
- exceptions: []
+ exceptions: [],
},
testFeatureTooBig: {
state: 'enabled',
minSupportedVersion: '1.0.0',
settings: {},
- exceptions: []
+ exceptions: [],
},
testFeatureSmall: {
state: 'enabled',
minSupportedVersion: '0.9.9',
settings: {},
- exceptions: []
+ exceptions: [],
},
testFeatureSmaller: {
state: 'enabled',
minSupportedVersion: '0.9.8',
settings: {
- barp: true
+ barp: true,
},
- exceptions: []
+ exceptions: [],
},
testFeatureOutlier: {
state: 'enabled',
minSupportedVersion: '97.0.00',
settings: {},
- exceptions: []
- }
+ exceptions: [],
+ },
},
- unprotectedTemporary: []
- }
+ unprotectedTemporary: [],
+ };
const processedConfig = processConfig(
configIn,
[],
{
platform: {
- name: 'ios'
+ name: 'ios',
},
versionString: '0.9.9',
- sessionKey: 'testSessionKey'
+ sessionKey: 'testSessionKey',
},
- []
- )
+ [],
+ );
expect(processedConfig).toEqual({
site: {
domain: 'localhost',
isBroken: false,
allowlisted: false,
// testFeatureTooBig is not enabled because it's minSupportedVersion is 100
- enabledFeatures: ['testFeature', 'testFeatureSmall', 'testFeatureSmaller']
+ enabledFeatures: ['testFeature', 'testFeatureSmall', 'testFeatureSmaller'],
},
featureSettings: {
testFeature: {
- beep: 'boop'
+ beep: 'boop',
},
testFeatureSmall: {},
testFeatureSmaller: {
- barp: true
- }
+ barp: true,
+ },
},
platform: {
name: 'ios',
- version: '0.9.9'
+ version: '0.9.9',
},
versionString: '0.9.9',
sessionKey: 'testSessionKey',
trackerLookup: undefined,
- bundledConfig: configIn
- })
- })
+ bundledConfig: configIn,
+ });
+ });
describe('utils.satisfiesMinVersion', () => {
// Min version, Extension version, outcome
@@ -203,45 +203,45 @@ describe('Helpers checks', () => {
['102.12.12.1', '102.12.12.3', true],
['102.12.12.1', '102.12.12.1.1', true],
['102.12.12.1', '102.12.12.2.1', true],
- ['102.12.12.1', '102.12.12.1.1.1.1.1.1.1', true]
- ]
+ ['102.12.12.1', '102.12.12.1.1.1.1.1.1.1', true],
+ ];
for (const testCase of cases) {
- const [versionString, extensionVersionString, expectedOutcome] = testCase
+ const [versionString, extensionVersionString, expectedOutcome] = testCase;
it(`returns ${JSON.stringify(expectedOutcome)} for ${versionString} compared to ${extensionVersionString}`, () => {
- expect(satisfiesMinVersion(versionString, extensionVersionString)).toEqual(expectedOutcome)
- })
+ expect(satisfiesMinVersion(versionString, extensionVersionString)).toEqual(expectedOutcome);
+ });
}
- })
+ });
describe('utils.postDebugMessage', () => {
- const counters = new Map()
- globalThis.postMessage = message => {
- counters.set(message.action, (counters.get(message.action) || 0) + 1)
- }
- initStringExemptionLists({ debug: false })
+ const counters = new Map();
+ globalThis.postMessage = (message) => {
+ counters.set(message.action, (counters.get(message.action) || 0) + 1);
+ };
+ initStringExemptionLists({ debug: false });
for (let i = 0; i < 10; i++) {
- postDebugMessage('testa', { ding: 1 })
- postDebugMessage('testa', { ding: 2 })
- postDebugMessage('testb', { boop: true })
- postDebugMessage('testc', { boop: true }, true)
+ postDebugMessage('testa', { ding: 1 });
+ postDebugMessage('testa', { ding: 2 });
+ postDebugMessage('testb', { boop: true });
+ postDebugMessage('testc', { boop: true }, true);
}
it('does not trigger post messages without debug flag', () => {
- expect(counters.get('testa')).toEqual(undefined)
- expect(counters.get('testb')).toEqual(undefined)
- expect(counters.get('testc')).toEqual(10)
- })
+ expect(counters.get('testa')).toEqual(undefined);
+ expect(counters.get('testb')).toEqual(undefined);
+ expect(counters.get('testc')).toEqual(10);
+ });
- initStringExemptionLists({ debug: true })
+ initStringExemptionLists({ debug: true });
for (let i = 0; i < 6000; i++) {
- postDebugMessage('testd', { ding: 1 })
- postDebugMessage('testd', { ding: 2 })
- postDebugMessage('teste', { boop: true })
- postDebugMessage('testf', { boop: true }, true)
+ postDebugMessage('testd', { ding: 1 });
+ postDebugMessage('testd', { ding: 2 });
+ postDebugMessage('teste', { boop: true });
+ postDebugMessage('testf', { boop: true }, true);
}
it('posts messages', () => {
- expect(counters.get('testd')).toEqual(5000)
- expect(counters.get('teste')).toEqual(5000)
- expect(counters.get('testf')).toEqual(5000)
- })
- })
-})
+ expect(counters.get('testd')).toEqual(5000);
+ expect(counters.get('teste')).toEqual(5000);
+ expect(counters.get('testf')).toEqual(5000);
+ });
+ });
+});
diff --git a/injected/unit-test/verify-artifacts.js b/injected/unit-test/verify-artifacts.js
index ae9f6e7d0..1f1f6dbaa 100644
--- a/injected/unit-test/verify-artifacts.js
+++ b/injected/unit-test/verify-artifacts.js
@@ -1,17 +1,17 @@
-import { join, relative } from 'node:path'
-import { readFileSync, statSync } from 'node:fs'
-import { cwd } from '../../scripts/script-utils.js'
+import { join, relative } from 'node:path';
+import { readFileSync, statSync } from 'node:fs';
+import { cwd } from '../../scripts/script-utils.js';
// path helpers
-const ROOT = join(cwd(import.meta.url), '..', '..')
-console.log(ROOT)
-const BUILD = join(ROOT, 'build')
-const APPLE_BUILD = join(ROOT, 'Sources/ContentScopeScripts/dist')
-console.log(APPLE_BUILD)
-let CSS_OUTPUT_SIZE = 760_000
-const CSS_OUTPUT_SIZE_CHROME = CSS_OUTPUT_SIZE * 1.45 // 45% larger for Chrome MV2 due to base64 encoding
+const ROOT = join(cwd(import.meta.url), '..', '..');
+console.log(ROOT);
+const BUILD = join(ROOT, 'build');
+const APPLE_BUILD = join(ROOT, 'Sources/ContentScopeScripts/dist');
+console.log(APPLE_BUILD);
+let CSS_OUTPUT_SIZE = 760_000;
+const CSS_OUTPUT_SIZE_CHROME = CSS_OUTPUT_SIZE * 1.45; // 45% larger for Chrome MV2 due to base64 encoding
if (process.platform === 'win32') {
- CSS_OUTPUT_SIZE = CSS_OUTPUT_SIZE * 1.1 // 10% larger for Windows due to line endings
+ CSS_OUTPUT_SIZE = CSS_OUTPUT_SIZE * 1.1; // 10% larger for Windows due to line endings
}
const checks = {
@@ -19,77 +19,75 @@ const checks = {
file: join(BUILD, 'android/contentScope.js'),
tests: [
{ kind: 'maxFileSize', value: CSS_OUTPUT_SIZE },
- { kind: 'containsString', text: 'output.trackerLookup = {', includes: true }
- ]
+ { kind: 'containsString', text: 'output.trackerLookup = {', includes: true },
+ ],
},
chrome: {
file: join(BUILD, 'chrome/inject.js'),
tests: [
{ kind: 'maxFileSize', value: CSS_OUTPUT_SIZE_CHROME },
- { kind: 'containsString', text: '$TRACKER_LOOKUP$', includes: true }
- ]
+ { kind: 'containsString', text: '$TRACKER_LOOKUP$', includes: true },
+ ],
},
'chrome-mv3': {
file: join(BUILD, 'chrome-mv3/inject.js'),
tests: [
{ kind: 'maxFileSize', value: CSS_OUTPUT_SIZE },
{ kind: 'containsString', text: 'cloneInto(', includes: false },
- { kind: 'containsString', text: '$TRACKER_LOOKUP$', includes: true }
- ]
+ { kind: 'containsString', text: '$TRACKER_LOOKUP$', includes: true },
+ ],
},
firefox: {
file: join(BUILD, 'firefox/inject.js'),
tests: [
{ kind: 'maxFileSize', value: CSS_OUTPUT_SIZE },
{ kind: 'containsString', text: 'cloneInto(', includes: true },
- { kind: 'containsString', text: '$TRACKER_LOOKUP$', includes: true }
- ]
+ { kind: 'containsString', text: '$TRACKER_LOOKUP$', includes: true },
+ ],
},
integration: {
file: join(BUILD, 'integration/contentScope.js'),
- tests: [
- { kind: 'containsString', text: 'const trackerLookup = {', includes: true }
- ]
+ tests: [{ kind: 'containsString', text: 'const trackerLookup = {', includes: true }],
},
windows: {
file: join(BUILD, 'windows/contentScope.js'),
tests: [
{ kind: 'maxFileSize', value: CSS_OUTPUT_SIZE },
- { kind: 'containsString', text: 'output.trackerLookup = {', includes: true }
- ]
+ { kind: 'containsString', text: 'output.trackerLookup = {', includes: true },
+ ],
},
apple: {
file: join(APPLE_BUILD, 'contentScope.js'),
tests: [
{ kind: 'maxFileSize', value: CSS_OUTPUT_SIZE },
{ kind: 'containsString', text: 'output.trackerLookup = {', includes: true },
- { kind: 'containsString', text: '#bundledConfig', includes: false }
- ]
- }
-}
+ { kind: 'containsString', text: '#bundledConfig', includes: false },
+ ],
+ },
+};
describe('checks', () => {
for (const [platformName, platformChecks] of Object.entries(checks)) {
for (const check of platformChecks.tests) {
- const localPath = relative(ROOT, platformChecks.file)
+ const localPath = relative(ROOT, platformChecks.file);
if (check.kind === 'maxFileSize') {
it(`${platformName}: '${localPath}' is smaller than ${check.value}`, () => {
- const stats = statSync(platformChecks.file)
- expect(stats.size).toBeLessThan(check.value)
- })
+ const stats = statSync(platformChecks.file);
+ expect(stats.size).toBeLessThan(check.value);
+ });
}
if (check.kind === 'containsString') {
it(`${platformName}: '${localPath}' contains ${check.text}`, () => {
- const fileContents = readFileSync(platformChecks.file).toString()
+ const fileContents = readFileSync(platformChecks.file).toString();
// @ts-expect-error - can't infer that value is a number without adding types
- const includes = fileContents.includes(check.text)
+ const includes = fileContents.includes(check.text);
if (check.includes) {
- expect(includes).toBeTrue()
+ expect(includes).toBeTrue();
} else {
- expect(includes).toBeFalse()
+ expect(includes).toBeFalse();
}
- })
+ });
}
}
}
-})
+});
diff --git a/injected/unit-test/wrapper-utils.js b/injected/unit-test/wrapper-utils.js
index 6752756a5..c3b22ee52 100644
--- a/injected/unit-test/wrapper-utils.js
+++ b/injected/unit-test/wrapper-utils.js
@@ -1,194 +1,268 @@
-import { shimInterface, shimProperty } from '../src/wrapper-utils.js'
+import { shimInterface, shimProperty } from '../src/wrapper-utils.js';
describe('Shim API', () => {
// MediaSession is just an example, to make it close to reality
/** @type {typeof MediaSession} */
- let MyMediaSession
+ let MyMediaSession;
- let definePropertyFn
- let navigatorPrototype
- let navigator
+ let definePropertyFn;
+ let navigatorPrototype;
+ let navigator;
beforeEach(() => {
- expect(globalThis.MediaSession).toBeUndefined()
+ expect(globalThis.MediaSession).toBeUndefined();
MyMediaSession = class {
- metadata = null
+ metadata = null;
/** @type {MediaSession['playbackState']} */
- playbackState = 'none'
+ playbackState = 'none';
- setActionHandler () {
- return 123
+ setActionHandler() {
+ return 123;
}
- setCameraActive () {}
- setMicrophoneActive () {}
- setPositionState () {}
- }
- definePropertyFn = spyOn(Object, 'defineProperty').and.callThrough()
+ setCameraActive() {}
+ setMicrophoneActive() {}
+ setPositionState() {}
+ };
+ definePropertyFn = spyOn(Object, 'defineProperty').and.callThrough();
- navigatorPrototype = {}
- function NavigatorConstructor () {}
- NavigatorConstructor.prototype = navigatorPrototype
- navigator = new NavigatorConstructor()
- })
+ navigatorPrototype = {};
+ function NavigatorConstructor() {}
+ NavigatorConstructor.prototype = navigatorPrototype;
+ navigator = new NavigatorConstructor();
+ });
afterEach(() => {
// @ts-expect-error globalThis is read-only
- delete globalThis.MediaSession
- })
+ delete globalThis.MediaSession;
+ });
describe('shimInterface()', () => {
it('should (re)define the global', () => {
- shimInterface('MediaSession', MyMediaSession, {
- wrapToString: true,
- disallowConstructor: false,
- allowConstructorCall: false
- }, definePropertyFn)
- expect(definePropertyFn).toHaveBeenCalledTimes(2)
- const NewMediaSession = globalThis.MediaSession
- expect(NewMediaSession).toBeDefined()
- const newPropertyDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'MediaSession')
-
- expect({ ...newPropertyDescriptor, value: null }).withContext('property descriptors should match').toEqual({ value: null, writable: true, enumerable: false, configurable: true })
- expect(NewMediaSession.name).toBe('MediaSession')
-
- expect((new MyMediaSession()) instanceof NewMediaSession).withContext('instances should pass the instanceof check').toBeTrue()
- expect((new NewMediaSession()) instanceof NewMediaSession).withContext('instances should pass the instanceof check').toBeTrue()
- })
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ wrapToString: true,
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ },
+ definePropertyFn,
+ );
+ expect(definePropertyFn).toHaveBeenCalledTimes(2);
+ const NewMediaSession = globalThis.MediaSession;
+ expect(NewMediaSession).toBeDefined();
+ const newPropertyDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'MediaSession');
+
+ expect({ ...newPropertyDescriptor, value: null })
+ .withContext('property descriptors should match')
+ .toEqual({ value: null, writable: true, enumerable: false, configurable: true });
+ expect(NewMediaSession.name).toBe('MediaSession');
+
+ expect(new MyMediaSession() instanceof NewMediaSession)
+ .withContext('instances should pass the instanceof check')
+ .toBeTrue();
+ expect(new NewMediaSession() instanceof NewMediaSession)
+ .withContext('instances should pass the instanceof check')
+ .toBeTrue();
+ });
it('should support disallowConstructor', () => {
- shimInterface('MediaSession', MyMediaSession, {
- disallowConstructor: false,
- allowConstructorCall: false,
- wrapToString: true
- }, definePropertyFn)
- expect(() => new globalThis.MediaSession()).not.toThrow()
-
- shimInterface('MediaSession', MyMediaSession, {
- disallowConstructor: true,
- allowConstructorCall: false,
- wrapToString: true
- }, definePropertyFn)
- expect(() => new globalThis.MediaSession()).toThrowError(TypeError)
-
- shimInterface('MediaSession', MyMediaSession, {
- disallowConstructor: true,
- constructorErrorMessage: 'friendly message',
- allowConstructorCall: false,
- wrapToString: true
- }, definePropertyFn)
- expect(() => new globalThis.MediaSession()).toThrowMatching((err) => err instanceof TypeError && err.message === 'friendly message')
- })
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ wrapToString: true,
+ },
+ definePropertyFn,
+ );
+ expect(() => new globalThis.MediaSession()).not.toThrow();
+
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ disallowConstructor: true,
+ allowConstructorCall: false,
+ wrapToString: true,
+ },
+ definePropertyFn,
+ );
+ expect(() => new globalThis.MediaSession()).toThrowError(TypeError);
+
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ disallowConstructor: true,
+ constructorErrorMessage: 'friendly message',
+ allowConstructorCall: false,
+ wrapToString: true,
+ },
+ definePropertyFn,
+ );
+ expect(() => new globalThis.MediaSession()).toThrowMatching(
+ (err) => err instanceof TypeError && err.message === 'friendly message',
+ );
+ });
it('should support allowConstructorCall', () => {
- shimInterface('MediaSession', MyMediaSession, {
- allowConstructorCall: true,
- wrapToString: true,
- disallowConstructor: false
- }, definePropertyFn)
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ allowConstructorCall: true,
+ wrapToString: true,
+ disallowConstructor: false,
+ },
+ definePropertyFn,
+ );
// @ts-expect-error real MediaSession is not callable
- expect(() => globalThis.MediaSession()).not.toThrow()
+ expect(() => globalThis.MediaSession()).not.toThrow();
// @ts-expect-error real MediaSession is not callable
- expect(globalThis.MediaSession() instanceof globalThis.MediaSession).withContext('instances should pass the instanceof check').toBeTrue()
+ expect(globalThis.MediaSession() instanceof globalThis.MediaSession)
+ .withContext('instances should pass the instanceof check')
+ .toBeTrue();
- shimInterface('MediaSession', MyMediaSession, {
- allowConstructorCall: false,
- wrapToString: true,
- disallowConstructor: false
- }, definePropertyFn)
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ allowConstructorCall: false,
+ wrapToString: true,
+ disallowConstructor: false,
+ },
+ definePropertyFn,
+ );
// @ts-expect-error real MediaSession is not callable
- expect(() => globalThis.MediaSession()).toThrowError(TypeError)
- })
+ expect(() => globalThis.MediaSession()).toThrowError(TypeError);
+ });
it('should support wrapToString', () => {
- shimInterface('MediaSession', MyMediaSession, {
- wrapToString: false,
- disallowConstructor: false,
- allowConstructorCall: false
- }, definePropertyFn)
- expect(globalThis.MediaSession.toString()).not.toContain('class')
- expect(globalThis.MediaSession.toString.toString()).withContext('Shim\'s toString.toString() should not be masked').toBe(MyMediaSession.toString.toString())
- expect(globalThis.MediaSession.prototype.setActionHandler.toString()).withContext('Shim\'s method\'s .toString() should not be masked').toBe(MyMediaSession.prototype.setActionHandler.toString())
-
- shimInterface('MediaSession', MyMediaSession, {
- wrapToString: true,
- disallowConstructor: false,
- allowConstructorCall: false
- }, definePropertyFn)
-
- expect(globalThis.MediaSession.toString()).toBe('function MediaSession() { [native code] }')
- expect(globalThis.MediaSession.toString.toString()).toBe('function toString() { [native code] }')
- expect(globalThis.MediaSession.prototype.setActionHandler.toString()).toBe('function setActionHandler() { [native code] }')
- })
- })
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ wrapToString: false,
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ },
+ definePropertyFn,
+ );
+ expect(globalThis.MediaSession.toString()).not.toContain('class');
+ expect(globalThis.MediaSession.toString.toString())
+ .withContext("Shim's toString.toString() should not be masked")
+ .toBe(MyMediaSession.toString.toString());
+ expect(globalThis.MediaSession.prototype.setActionHandler.toString())
+ .withContext("Shim's method's .toString() should not be masked")
+ .toBe(MyMediaSession.prototype.setActionHandler.toString());
+
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ wrapToString: true,
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ },
+ definePropertyFn,
+ );
+
+ expect(globalThis.MediaSession.toString()).toBe('function MediaSession() { [native code] }');
+ expect(globalThis.MediaSession.toString.toString()).toBe('function toString() { [native code] }');
+ expect(globalThis.MediaSession.prototype.setActionHandler.toString()).toBe('function setActionHandler() { [native code] }');
+ });
+ });
describe('shimProperty()', () => {
it('should correctly shim the property', () => {
- shimInterface('MediaSession', MyMediaSession, {
- wrapToString: true,
- disallowConstructor: false,
- allowConstructorCall: false
- }, definePropertyFn)
- const instance = new MyMediaSession()
- shimProperty(navigatorPrototype, 'mediaSession', instance, false, definePropertyFn)
-
- const NewMediaSession = globalThis.MediaSession
- expect(navigator.mediaSession instanceof NewMediaSession).withContext('instances should pass the instanceof check').toBeTrue()
- expect(navigator.mediaSession.setActionHandler()).withContext('method should return expected value').toBe(123)
- expect(navigator.mediaSession.toString()).toBe('[object MediaSession]')
- expect(navigator.mediaSession.toString.toString()).toBe('function toString() { [native code] }')
- })
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ wrapToString: true,
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ },
+ definePropertyFn,
+ );
+ const instance = new MyMediaSession();
+ shimProperty(navigatorPrototype, 'mediaSession', instance, false, definePropertyFn);
+
+ const NewMediaSession = globalThis.MediaSession;
+ expect(navigator.mediaSession instanceof NewMediaSession)
+ .withContext('instances should pass the instanceof check')
+ .toBeTrue();
+ expect(navigator.mediaSession.setActionHandler()).withContext('method should return expected value').toBe(123);
+ expect(navigator.mediaSession.toString()).toBe('[object MediaSession]');
+ expect(navigator.mediaSession.toString.toString()).toBe('function toString() { [native code] }');
+ });
it('should support writable properties', () => {
- shimInterface('MediaSession', MyMediaSession, {
- wrapToString: true,
- disallowConstructor: false,
- allowConstructorCall: false
- }, definePropertyFn)
- const instance = new MyMediaSession()
- shimProperty(navigatorPrototype, 'mediaSession', instance, false, definePropertyFn)
-
- const descriptor = Object.getOwnPropertyDescriptor(navigatorPrototype, 'mediaSession')
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ wrapToString: true,
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ },
+ definePropertyFn,
+ );
+ const instance = new MyMediaSession();
+ shimProperty(navigatorPrototype, 'mediaSession', instance, false, definePropertyFn);
+
+ const descriptor = Object.getOwnPropertyDescriptor(navigatorPrototype, 'mediaSession');
// @ts-expect-error we know it's defined
- descriptor.value = null
- expect(descriptor).toEqual({
- value: null,
- configurable: true,
- enumerable: true,
- writable: true
- }, 'property should be writable')
- })
+ descriptor.value = null;
+ expect(descriptor).toEqual(
+ {
+ value: null,
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ },
+ 'property should be writable',
+ );
+ });
it('should support readonly properties', () => {
- shimInterface('MediaSession', MyMediaSession, {
- wrapToString: true,
- disallowConstructor: false,
- allowConstructorCall: false
- }, definePropertyFn)
- const instance = new MyMediaSession()
- shimProperty(navigatorPrototype, 'mediaSession', instance, true, definePropertyFn)
+ shimInterface(
+ 'MediaSession',
+ MyMediaSession,
+ {
+ wrapToString: true,
+ disallowConstructor: false,
+ allowConstructorCall: false,
+ },
+ definePropertyFn,
+ );
+ const instance = new MyMediaSession();
+ shimProperty(navigatorPrototype, 'mediaSession', instance, true, definePropertyFn);
/** @type {import('../src/wrapper-utils').StrictAccessorDescriptor} */
// @ts-expect-error we know it's defined
- const descriptor = Object.getOwnPropertyDescriptor(navigatorPrototype, 'mediaSession')
+ const descriptor = Object.getOwnPropertyDescriptor(navigatorPrototype, 'mediaSession');
- expect(descriptor.get).toBeDefined()
- expect(descriptor.set).toBeUndefined()
+ expect(descriptor.get).toBeDefined();
+ expect(descriptor.set).toBeUndefined();
- const getter = descriptor.get
+ const getter = descriptor.get;
// @ts-expect-error we know it's defined
- descriptor.get = null
+ descriptor.get = null;
expect(descriptor).toEqual({
// @ts-expect-error get is overridden
get: null,
// @ts-expect-error set is overridden
set: undefined,
configurable: true,
- enumerable: true
- })
+ enumerable: true,
+ });
- expect(getter.toString()).toBe('function get mediaSession() { [native code] }')
- })
- })
-})
+ expect(getter.toString()).toBe('function get mediaSession() { [native code] }');
+ });
+ });
+});
diff --git a/messaging/index.js b/messaging/index.js
index de1133965..8001f4939 100644
--- a/messaging/index.js
+++ b/messaging/index.js
@@ -20,11 +20,17 @@
*
* @module Messaging
*/
-import { WindowsMessagingConfig, WindowsMessagingTransport, WindowsInteropMethods, WindowsNotification, WindowsRequestMessage } from './lib/windows.js'
-import { WebkitMessagingConfig, WebkitMessagingTransport } from './lib/webkit.js'
-import { NotificationMessage, RequestMessage, Subscription, MessageResponse, MessageError, SubscriptionEvent } from './schema.js'
-import { AndroidMessagingConfig, AndroidMessagingTransport } from './lib/android.js'
-import { createTypedMessages } from './lib/typed-messages.js'
+import {
+ WindowsMessagingConfig,
+ WindowsMessagingTransport,
+ WindowsInteropMethods,
+ WindowsNotification,
+ WindowsRequestMessage,
+} from './lib/windows.js';
+import { WebkitMessagingConfig, WebkitMessagingTransport } from './lib/webkit.js';
+import { NotificationMessage, RequestMessage, Subscription, MessageResponse, MessageError, SubscriptionEvent } from './schema.js';
+import { AndroidMessagingConfig, AndroidMessagingTransport } from './lib/android.js';
+import { createTypedMessages } from './lib/typed-messages.js';
/**
* Common options/config that are *not* transport specific.
@@ -37,10 +43,10 @@ export class MessagingContext {
* @param {"production" | "development"} params.env
* @internal
*/
- constructor (params) {
- this.context = params.context
- this.featureName = params.featureName
- this.env = params.env
+ constructor(params) {
+ this.context = params.context;
+ this.featureName = params.featureName;
+ this.env = params.env;
}
}
@@ -56,9 +62,9 @@ export class Messaging {
* @param {MessagingContext} messagingContext
* @param {MessagingConfig} config
*/
- constructor (messagingContext, config) {
- this.messagingContext = messagingContext
- this.transport = getTransport(config, this.messagingContext)
+ constructor(messagingContext, config) {
+ this.messagingContext = messagingContext;
+ this.transport = getTransport(config, this.messagingContext);
}
/**
@@ -74,14 +80,14 @@ export class Messaging {
* @param {string} name
* @param {Record} [data]
*/
- notify (name, data = {}) {
+ notify(name, data = {}) {
const message = new NotificationMessage({
context: this.messagingContext.context,
featureName: this.messagingContext.featureName,
method: name,
- params: data
- })
- this.transport.notify(message)
+ params: data,
+ });
+ this.transport.notify(message);
}
/**
@@ -98,16 +104,16 @@ export class Messaging {
* @param {Record} [data]
* @return {Promise}
*/
- request (name, data = {}) {
- const id = globalThis?.crypto?.randomUUID?.() || name + '.response'
+ request(name, data = {}) {
+ const id = globalThis?.crypto?.randomUUID?.() || name + '.response';
const message = new RequestMessage({
context: this.messagingContext.context,
featureName: this.messagingContext.featureName,
method: name,
params: data,
- id
- })
- return this.transport.request(message)
+ id,
+ });
+ return this.transport.request(message);
}
/**
@@ -115,13 +121,13 @@ export class Messaging {
* @param {(value: unknown) => void} callback
* @return {() => void}
*/
- subscribe (name, callback) {
+ subscribe(name, callback) {
const msg = new Subscription({
context: this.messagingContext.context,
featureName: this.messagingContext.featureName,
- subscriptionName: name
- })
- return this.transport.subscribe(msg, callback)
+ subscriptionName: name,
+ });
+ return this.transport.subscribe(msg, callback);
}
}
@@ -133,9 +139,9 @@ export class MessagingTransport {
* @param {NotificationMessage} msg
* @returns {void}
*/
-
- notify (msg) {
- throw new Error("must implement 'notify'")
+
+ notify(msg) {
+ throw new Error("must implement 'notify'");
}
/**
@@ -143,9 +149,9 @@ export class MessagingTransport {
* @param {{signal?: AbortSignal}} [options]
* @return {Promise}
*/
-
- request (msg, options = {}) {
- throw new Error('must implement')
+
+ request(msg, options = {}) {
+ throw new Error('must implement');
}
/**
@@ -153,9 +159,9 @@ export class MessagingTransport {
* @param {(value: unknown) => void} callback
* @return {() => void}
*/
-
- subscribe (msg, callback) {
- throw new Error('must implement')
+
+ subscribe(msg, callback) {
+ throw new Error('must implement');
}
}
@@ -169,8 +175,8 @@ export class TestTransportConfig {
/**
* @param {MessagingTransport} impl
*/
- constructor (impl) {
- this.impl = impl
+ constructor(impl) {
+ this.impl = impl;
}
}
@@ -182,21 +188,21 @@ export class TestTransport {
* @param {TestTransportConfig} config
* @param {MessagingContext} messagingContext
*/
- constructor (config, messagingContext) {
- this.config = config
- this.messagingContext = messagingContext
+ constructor(config, messagingContext) {
+ this.config = config;
+ this.messagingContext = messagingContext;
}
- notify (msg) {
- return this.config.impl.notify(msg)
+ notify(msg) {
+ return this.config.impl.notify(msg);
}
- request (msg) {
- return this.config.impl.request(msg)
+ request(msg) {
+ return this.config.impl.request(msg);
}
- subscribe (msg, callback) {
- return this.config.impl.subscribe(msg, callback)
+ subscribe(msg, callback) {
+ return this.config.impl.subscribe(msg, callback);
}
}
@@ -205,20 +211,20 @@ export class TestTransport {
* @param {MessagingContext} messagingContext
* @returns {MessagingTransport}
*/
-function getTransport (config, messagingContext) {
+function getTransport(config, messagingContext) {
if (config instanceof WebkitMessagingConfig) {
- return new WebkitMessagingTransport(config, messagingContext)
+ return new WebkitMessagingTransport(config, messagingContext);
}
if (config instanceof WindowsMessagingConfig) {
- return new WindowsMessagingTransport(config, messagingContext)
+ return new WindowsMessagingTransport(config, messagingContext);
}
if (config instanceof AndroidMessagingConfig) {
- return new AndroidMessagingTransport(config, messagingContext)
+ return new AndroidMessagingTransport(config, messagingContext);
}
if (config instanceof TestTransportConfig) {
- return new TestTransport(config, messagingContext)
+ return new TestTransport(config, messagingContext);
}
- throw new Error('unreachable')
+ throw new Error('unreachable');
}
/**
@@ -229,9 +235,9 @@ export class MissingHandler extends Error {
* @param {string} message
* @param {string} handlerName
*/
- constructor (message, handlerName) {
- super(message)
- this.handlerName = handlerName
+ constructor(message, handlerName) {
+ super(message);
+ this.handlerName = handlerName;
}
}
@@ -254,5 +260,5 @@ export {
WindowsRequestMessage,
AndroidMessagingConfig,
AndroidMessagingTransport,
- createTypedMessages
-}
+ createTypedMessages,
+};
diff --git a/messaging/lib/android.js b/messaging/lib/android.js
index f63ff29c5..697be345f 100644
--- a/messaging/lib/android.js
+++ b/messaging/lib/android.js
@@ -6,8 +6,8 @@
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { MessagingTransport, MessageResponse, SubscriptionEvent } from '../index.js'
-import { isResponseFor, isSubscriptionEventFor } from '../schema.js'
+import { MessagingTransport, MessageResponse, SubscriptionEvent } from '../index.js';
+import { isResponseFor, isSubscriptionEventFor } from '../schema.js';
/**
* @typedef {import('../index.js').Subscription} Subscription
@@ -29,19 +29,19 @@ export class AndroidMessagingTransport {
* @param {MessagingContext} messagingContext
* @internal
*/
- constructor (config, messagingContext) {
- this.messagingContext = messagingContext
- this.config = config
+ constructor(config, messagingContext) {
+ this.messagingContext = messagingContext;
+ this.config = config;
}
/**
* @param {NotificationMessage} msg
*/
- notify (msg) {
+ notify(msg) {
try {
- this.config.sendMessageThrows?.(JSON.stringify(msg))
+ this.config.sendMessageThrows?.(JSON.stringify(msg));
} catch (e) {
- console.error('.notify failed', e)
+ console.error('.notify failed', e);
}
}
@@ -49,53 +49,53 @@ export class AndroidMessagingTransport {
* @param {RequestMessage} msg
* @return {Promise}
*/
- request (msg) {
+ request(msg) {
return new Promise((resolve, reject) => {
// subscribe early
- const unsub = this.config.subscribe(msg.id, handler)
+ const unsub = this.config.subscribe(msg.id, handler);
try {
- this.config.sendMessageThrows?.(JSON.stringify(msg))
+ this.config.sendMessageThrows?.(JSON.stringify(msg));
} catch (e) {
- unsub()
- reject(new Error('request failed to send: ' + e.message || 'unknown error'))
+ unsub();
+ reject(new Error('request failed to send: ' + e.message || 'unknown error'));
}
- function handler (data) {
+ function handler(data) {
if (isResponseFor(msg, data)) {
// success case, forward .result only
if (data.result) {
- resolve(data.result || {})
- return unsub()
+ resolve(data.result || {});
+ return unsub();
}
// error case, forward the error as a regular promise rejection
if (data.error) {
- reject(new Error(data.error.message))
- return unsub()
+ reject(new Error(data.error.message));
+ return unsub();
}
// getting here is undefined behavior
- unsub()
- throw new Error('unreachable: must have `result` or `error` key by this point')
+ unsub();
+ throw new Error('unreachable: must have `result` or `error` key by this point');
}
}
- })
+ });
}
/**
* @param {Subscription} msg
* @param {(value: unknown | undefined) => void} callback
*/
- subscribe (msg, callback) {
+ subscribe(msg, callback) {
const unsub = this.config.subscribe(msg.subscriptionName, (data) => {
if (isSubscriptionEventFor(msg, data)) {
- callback(data.params || {})
+ callback(data.params || {});
}
- })
+ });
return () => {
- unsub()
- }
+ unsub();
+ };
}
}
@@ -174,7 +174,7 @@ export class AndroidMessagingTransport {
*/
export class AndroidMessagingConfig {
/** @type {(json: string, secret: string) => void} */
- _capturedHandler
+ _capturedHandler;
/**
* @param {object} params
* @param {Record} params.target
@@ -186,28 +186,28 @@ export class AndroidMessagingConfig {
* @param {string} params.messageCallback - the name of the callback that the native
* side will use to send messages back to the javascript side
*/
- constructor (params) {
- this.target = params.target
- this.debug = params.debug
- this.javascriptInterface = params.javascriptInterface
- this.messageSecret = params.messageSecret
- this.messageCallback = params.messageCallback
+ constructor(params) {
+ this.target = params.target;
+ this.debug = params.debug;
+ this.javascriptInterface = params.javascriptInterface;
+ this.messageSecret = params.messageSecret;
+ this.messageCallback = params.messageCallback;
/**
* @type {Map void>}
* @internal
*/
- this.listeners = new globalThis.Map()
+ this.listeners = new globalThis.Map();
/**
* Capture the global handler and remove it from the global object.
*/
- this._captureGlobalHandler()
+ this._captureGlobalHandler();
/**
* Assign the incoming handler method to the global object.
*/
- this._assignHandlerMethod()
+ this._assignHandlerMethod();
}
/**
@@ -220,8 +220,8 @@ export class AndroidMessagingConfig {
* @throws
* @internal
*/
- sendMessageThrows (json) {
- this._capturedHandler(json, this.messageSecret)
+ sendMessageThrows(json) {
+ this._capturedHandler(json, this.messageSecret);
}
/**
@@ -237,11 +237,11 @@ export class AndroidMessagingConfig {
* @returns {() => void}
* @internal
*/
- subscribe (id, callback) {
- this.listeners.set(id, callback)
+ subscribe(id, callback) {
+ this.listeners.set(id, callback);
return () => {
- this.listeners.delete(id)
- }
+ this.listeners.delete(id);
+ };
}
/**
@@ -253,26 +253,26 @@ export class AndroidMessagingConfig {
* @param {MessageResponse | SubscriptionEvent} payload
* @internal
*/
- _dispatch (payload) {
+ _dispatch(payload) {
// do nothing if the response is empty
// this prevents the next `in` checks from throwing in test/debug scenarios
- if (!payload) return this._log('no response')
+ if (!payload) return this._log('no response');
// if the payload has an 'id' field, then it's a message response
if ('id' in payload) {
if (this.listeners.has(payload.id)) {
- this._tryCatch(() => this.listeners.get(payload.id)?.(payload))
+ this._tryCatch(() => this.listeners.get(payload.id)?.(payload));
} else {
- this._log('no listeners for ', payload)
+ this._log('no listeners for ', payload);
}
}
// if the payload has an 'subscriptionName' field, then it's a push event
if ('subscriptionName' in payload) {
if (this.listeners.has(payload.subscriptionName)) {
- this._tryCatch(() => this.listeners.get(payload.subscriptionName)?.(payload))
+ this._tryCatch(() => this.listeners.get(payload.subscriptionName)?.(payload));
} else {
- this._log('no subscription listeners for ', payload)
+ this._log('no subscription listeners for ', payload);
}
}
}
@@ -282,13 +282,13 @@ export class AndroidMessagingConfig {
* @param {(...args: any[]) => any} fn
* @param {string} [context]
*/
- _tryCatch (fn, context = 'none') {
+ _tryCatch(fn, context = 'none') {
try {
- return fn()
+ return fn();
} catch (e) {
if (this.debug) {
- console.error('AndroidMessagingConfig error:', context)
- console.error(e)
+ console.error('AndroidMessagingConfig error:', context);
+ console.error(e);
}
}
}
@@ -296,25 +296,25 @@ export class AndroidMessagingConfig {
/**
* @param {...any} args
*/
- _log (...args) {
+ _log(...args) {
if (this.debug) {
- console.log('AndroidMessagingConfig', ...args)
+ console.log('AndroidMessagingConfig', ...args);
}
}
/**
* Capture the global handler and remove it from the global object.
*/
- _captureGlobalHandler () {
- const { target, javascriptInterface } = this
+ _captureGlobalHandler() {
+ const { target, javascriptInterface } = this;
if (Object.prototype.hasOwnProperty.call(target, javascriptInterface)) {
- this._capturedHandler = target[javascriptInterface].process.bind(target[javascriptInterface])
- delete target[javascriptInterface]
+ this._capturedHandler = target[javascriptInterface].process.bind(target[javascriptInterface]);
+ delete target[javascriptInterface];
} else {
this._capturedHandler = () => {
- this._log('Android messaging interface not available', javascriptInterface)
- }
+ this._log('Android messaging interface not available', javascriptInterface);
+ };
}
}
@@ -322,18 +322,18 @@ export class AndroidMessagingConfig {
* Assign the incoming handler method to the global object.
* This is the method that Android will call to deliver messages.
*/
- _assignHandlerMethod () {
+ _assignHandlerMethod() {
/**
* @type {(secret: string, response: MessageResponse | SubscriptionEvent) => void}
*/
const responseHandler = (providedSecret, response) => {
if (providedSecret === this.messageSecret) {
- this._dispatch(response)
+ this._dispatch(response);
}
- }
+ };
Object.defineProperty(this.target, this.messageCallback, {
- value: responseHandler
- })
+ value: responseHandler,
+ });
}
}
diff --git a/messaging/lib/examples/android.example.js b/messaging/lib/examples/android.example.js
index 73a47c80e..8a28cff6e 100644
--- a/messaging/lib/examples/android.example.js
+++ b/messaging/lib/examples/android.example.js
@@ -1,11 +1,11 @@
-import { Messaging, MessagingContext } from '../../index.js'
-import { AndroidMessagingConfig } from '../android.js'
+import { Messaging, MessagingContext } from '../../index.js';
+import { AndroidMessagingConfig } from '../android.js';
/**
* This should match the string provided in the Android codebase
* @type {string}
*/
-const javascriptInterface = 'ContentScopeScripts'
+const javascriptInterface = 'ContentScopeScripts';
/**
* Create a *single* instance of AndroidMessagingConfig and share it.
@@ -15,8 +15,8 @@ const config = new AndroidMessagingConfig({
messageCallback: 'callback_123', // the method that android will execute with responses
target: globalThis, // where the global properties exist
javascriptInterface,
- debug: false
-})
+ debug: false,
+});
/**
* Context is per-feature;
@@ -24,27 +24,27 @@ const config = new AndroidMessagingConfig({
const messagingContext = new MessagingContext({
context: javascriptInterface,
featureName: 'hello-world',
- env: 'development'
-})
+ env: 'development',
+});
/**
* And then send notifications!
*/
-const messaging = new Messaging(messagingContext, config)
-messaging.notify('helloWorld')
+const messaging = new Messaging(messagingContext, config);
+messaging.notify('helloWorld');
/**
* Or request some data
*/
-messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error)
+messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error);
/**
* Or subscribe for push messages
*/
-const unsubscribe = messaging.subscribe('getData', (data) => console.log(data))
+const unsubscribe = messaging.subscribe('getData', (data) => console.log(data));
// later
-unsubscribe()
+unsubscribe();
/**
* Create messaging for 2 separate features
@@ -52,19 +52,19 @@ unsubscribe()
const messagingContext1 = new MessagingContext({
context: 'contentScopeScripts',
featureName: 'hello-world',
- env: 'development'
-})
+ env: 'development',
+});
/**
* Just change the feature name for a second feature
*/
-const messagingContext2 = { ...messagingContext1, featureName: 'duckPlayer' }
+const messagingContext2 = { ...messagingContext1, featureName: 'duckPlayer' };
/**
* Now, each feature has its own isolated messaging...
*/
-const messaging1 = new Messaging(messagingContext, config)
-messaging1.notify('helloWorld')
+const messaging1 = new Messaging(messagingContext, config);
+messaging1.notify('helloWorld');
-const messaging2 = new Messaging(messagingContext2, config)
-messaging2.notify('getUserValues')
+const messaging2 = new Messaging(messagingContext2, config);
+messaging2.notify('getUserValues');
diff --git a/messaging/lib/examples/payloads.js b/messaging/lib/examples/payloads.js
index 9273f61bd..7b1ac2273 100644
--- a/messaging/lib/examples/payloads.js
+++ b/messaging/lib/examples/payloads.js
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { NotificationMessage, RequestMessage, MessageResponse, SubscriptionEvent } from '../../index.js'
+import { NotificationMessage, RequestMessage, MessageResponse, SubscriptionEvent } from '../../index.js';
/**
* This is the payload sent for a notification
@@ -9,8 +9,8 @@ const notification = {
context: 'contentScopeScripts',
featureName: 'duckPlayerOverlays',
method: 'setUserValues',
- params: {}
-}
+ params: {},
+};
/**
* This is the payload sent for a Request.
@@ -21,8 +21,8 @@ const request = {
featureName: 'duckPlayerOverlays',
method: 'setUserValues',
params: {},
- id: 'setUserValues.response'
-}
+ id: 'setUserValues.response',
+};
/**
* This is the payload for a response
@@ -33,8 +33,8 @@ const messageResponse = {
featureName: 'duckPlayerOverlays',
result: {},
id: 'setUserValues.response',
- error: undefined
-}
+ error: undefined,
+};
/**
* This is the payload response for an error
@@ -45,8 +45,8 @@ const messageResponseError = {
featureName: 'duckPlayerOverlays',
result: undefined,
id: 'setUserValues.response',
- error: { message: '' }
-}
+ error: { message: '' },
+};
/**
* This is the payload response for a subscriptionEvent
@@ -56,5 +56,5 @@ const myEvent = {
context: 'contentScopeScripts',
featureName: 'duckPlayerOverlays',
subscriptionName: 'setUserValues',
- params: {}
-}
+ params: {},
+};
diff --git a/messaging/lib/examples/test.example.js b/messaging/lib/examples/test.example.js
index 7f2f74f98..2fedae5eb 100644
--- a/messaging/lib/examples/test.example.js
+++ b/messaging/lib/examples/test.example.js
@@ -1,4 +1,4 @@
-import { Messaging, MessagingContext, TestTransportConfig } from '../../index.js'
+import { Messaging, MessagingContext, TestTransportConfig } from '../../index.js';
/**
* Creates an ad-hoc messaging transport on the fly - useful for testing
@@ -8,46 +8,46 @@ import { Messaging, MessagingContext, TestTransportConfig } from '../../index.js
* - subscribe
*/
const config = new TestTransportConfig({
- notify (msg) {
- console.log(msg)
+ notify(msg) {
+ console.log(msg);
},
request: (msg) => {
if (msg.method === 'getUserValues') {
return Promise.resolve({
- foo: 'bar'
- })
+ foo: 'bar',
+ });
}
- return Promise.resolve(null)
+ return Promise.resolve(null);
},
- subscribe (msg) {
- console.log(msg)
+ subscribe(msg) {
+ console.log(msg);
return () => {
- console.log('teardown')
- }
- }
-})
+ console.log('teardown');
+ };
+ },
+});
const messagingContext = new MessagingContext({
context: 'contentScopeScripts',
featureName: 'hello-world',
- env: 'development'
-})
+ env: 'development',
+});
/**
* And then send notifications!
*/
-const messaging = new Messaging(messagingContext, config)
-messaging.notify('helloWorld')
+const messaging = new Messaging(messagingContext, config);
+messaging.notify('helloWorld');
/**
* Or request some data
*/
-messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error)
+messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error);
/**
* Or subscribe for push messages
*/
-const unsubscribe = messaging.subscribe('getData', (data) => console.log(data))
+const unsubscribe = messaging.subscribe('getData', (data) => console.log(data));
// later
-unsubscribe()
+unsubscribe();
diff --git a/messaging/lib/examples/webkit.example.js b/messaging/lib/examples/webkit.example.js
index f78e3119c..c0738326c 100644
--- a/messaging/lib/examples/webkit.example.js
+++ b/messaging/lib/examples/webkit.example.js
@@ -1,4 +1,4 @@
-import { Messaging, MessagingContext, WebkitMessagingConfig } from '../../index.js'
+import { Messaging, MessagingContext, WebkitMessagingConfig } from '../../index.js';
/**
* Configuration for WebkitMessaging
@@ -6,8 +6,8 @@ import { Messaging, MessagingContext, WebkitMessagingConfig } from '../../index.
const config = new WebkitMessagingConfig({
hasModernWebkitAPI: true,
secret: 'SECRET',
- webkitMessageHandlerNames: ['contentScopeScripts']
-})
+ webkitMessageHandlerNames: ['contentScopeScripts'],
+});
/**
* Context for messaging - this helps native platforms differentiate between senders
@@ -15,13 +15,13 @@ const config = new WebkitMessagingConfig({
const messagingContext = new MessagingContext({
context: 'contentScopeScripts',
featureName: 'hello-world',
- env: 'development'
-})
+ env: 'development',
+});
/**
* With config + context, now create an instance:
*/
-const messaging = new Messaging(messagingContext, config)
+const messaging = new Messaging(messagingContext, config);
/**
* send notifications (fire and forget)
@@ -32,6 +32,6 @@ messaging.notify('sendPixel');
* request data
*/
(async () => {
- const result = await messaging.request('helloWorld', { foo: 'bar' })
- console.log(result)
-})()
+ const result = await messaging.request('helloWorld', { foo: 'bar' });
+ console.log(result);
+})();
diff --git a/messaging/lib/examples/windows.example.js b/messaging/lib/examples/windows.example.js
index b3c22e9f7..7e3c2ef48 100644
--- a/messaging/lib/examples/windows.example.js
+++ b/messaging/lib/examples/windows.example.js
@@ -1,15 +1,15 @@
-import { WindowsMessagingConfig } from '../windows.js'
-import { Messaging, MessagingContext } from '../../index.js'
+import { WindowsMessagingConfig } from '../windows.js';
+import { Messaging, MessagingContext } from '../../index.js';
/**
* These 3 required methods that get assigned by the Native side.
*/
// @ts-expect-error - webview is not in @types/chrome
-const windowsInteropPostMessage = window.chrome.webview.postMessage
+const windowsInteropPostMessage = window.chrome.webview.postMessage;
// @ts-expect-error - webview is not in @types/chrome
-const windowsInteropAddEventListener = window.chrome.webview.addEventListener
+const windowsInteropAddEventListener = window.chrome.webview.addEventListener;
// @ts-expect-error - webview is not in @types/chrome
-const windowsInteropRemoveEventListener = window.chrome.webview.removeEventListener
+const windowsInteropRemoveEventListener = window.chrome.webview.removeEventListener;
/**
* With those methods available in the same lexical scope, we can then create
@@ -19,31 +19,31 @@ const config = new WindowsMessagingConfig({
methods: {
postMessage: windowsInteropPostMessage,
addEventListener: windowsInteropAddEventListener,
- removeEventListener: windowsInteropRemoveEventListener
- }
-})
+ removeEventListener: windowsInteropRemoveEventListener,
+ },
+});
const messagingContext = new MessagingContext({
context: 'contentScopeScripts',
featureName: 'hello-world',
- env: 'development'
-})
+ env: 'development',
+});
/**
* And then send notifications!
*/
-const messaging = new Messaging(messagingContext, config)
-messaging.notify('helloWorld')
+const messaging = new Messaging(messagingContext, config);
+messaging.notify('helloWorld');
/**
* Or request some data
*/
-messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error)
+messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error);
/**
* Or subscribe for push messages
*/
-const unsubscribe = messaging.subscribe('getData', (data) => console.log(data))
+const unsubscribe = messaging.subscribe('getData', (data) => console.log(data));
// later
-unsubscribe()
+unsubscribe();
diff --git a/messaging/lib/messaging.types.d.ts b/messaging/lib/messaging.types.d.ts
index 2f1d52de6..1d4f5d900 100644
--- a/messaging/lib/messaging.types.d.ts
+++ b/messaging/lib/messaging.types.d.ts
@@ -1,31 +1,28 @@
interface UnstableWebkit {
- messageHandlers: Record<
- string,
- {
- postMessage?: (...args: unknown[]) => void
- }
- >
+ messageHandlers: Record<
+ string,
+ {
+ postMessage?: (...args: unknown[]) => void;
+ }
+ >;
}
interface UnstableMockCall {
- payload:
- | import('../index.js').RequestMessage
- | import('../index.js').NotificationMessage
- | import('../index.js').Subscription;
- response?: Record;
+ payload: import('../index.js').RequestMessage | import('../index.js').NotificationMessage | import('../index.js').Subscription;
+ response?: Record;
}
interface Window {
- webkit: UnstableWebkit
- __playwright_01: {
- mockResponses: Record,
- subscriptionEvents: import('../index.js').SubscriptionEvent[],
- mocks: {
- outgoing: UnstableMockCall[],
- }
- }
+ webkit: UnstableWebkit;
+ __playwright_01: {
+ mockResponses: Record;
+ subscriptionEvents: import('../index.js').SubscriptionEvent[];
+ mocks: {
+ outgoing: UnstableMockCall[];
+ };
+ };
}
-declare let windowsInteropPostMessage: unknown
-declare let windowsInteropAddEventListener: unknown
-declare let windowsInteropRemoveEventListener: unknown
+declare let windowsInteropPostMessage: unknown;
+declare let windowsInteropAddEventListener: unknown;
+declare let windowsInteropRemoveEventListener: unknown;
diff --git a/messaging/lib/shared-types.ts b/messaging/lib/shared-types.ts
index a0291d3ab..6c1220f32 100644
--- a/messaging/lib/shared-types.ts
+++ b/messaging/lib/shared-types.ts
@@ -1,10 +1,10 @@
export interface MessageTypes {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- requests?: Record
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- notifications?: Record
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- subscriptions?: Record
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ requests?: Record;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ notifications?: Record;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ subscriptions?: Record;
}
/**
@@ -12,18 +12,25 @@ export interface MessageTypes {
* to any Messaging instance
*/
export interface MessagingBase {
- notify<
- Method extends T['notifications'] extends { method: string } ? T['notifications']['method'] : never,
- Msg = Extract,
- >(...args: Msg extends { params: infer Params } ? [Method, Params]: [Method]): void
- request<
- Method extends T['requests'] extends {method: string} ? T['requests']['method'] : never,
- Msg = Extract,
- Return = Msg extends { result: infer Result } ? Result : void
- >(...args: Msg extends { params: infer Params } ? [Method, Params]: [Method]): Promise
- subscribe<
- Method extends T['subscriptions'] extends { subscriptionEvent: string } ? T['subscriptions']['subscriptionEvent'] : never,
- Msg = Extract,
- Callback = Msg extends { params: infer Params } ? (params: Params) => void : (a: never) => void
- >(subscriptionEvent: Method, cb: Callback): () => void
+ notify<
+ Method extends T['notifications'] extends { method: string } ? T['notifications']['method'] : never,
+ Msg = Extract,
+ >(
+ ...args: Msg extends { params: infer Params } ? [Method, Params] : [Method]
+ ): void;
+ request<
+ Method extends T['requests'] extends { method: string } ? T['requests']['method'] : never,
+ Msg = Extract,
+ Return = Msg extends { result: infer Result } ? Result : void,
+ >(
+ ...args: Msg extends { params: infer Params } ? [Method, Params] : [Method]
+ ): Promise;
+ subscribe<
+ Method extends T['subscriptions'] extends { subscriptionEvent: string } ? T['subscriptions']['subscriptionEvent'] : never,
+ Msg = Extract,
+ Callback = Msg extends { params: infer Params } ? (params: Params) => void : (a: never) => void,
+ >(
+ subscriptionEvent: Method,
+ cb: Callback,
+ ): () => void;
}
diff --git a/messaging/lib/test-utils.mjs b/messaging/lib/test-utils.mjs
index 4093bd620..b60e57563 100644
--- a/messaging/lib/test-utils.mjs
+++ b/messaging/lib/test-utils.mjs
@@ -20,10 +20,10 @@ export function mockWindowsMessaging(params) {
mockResponses: params.responses,
subscriptionEvents: [],
mocks: {
- outgoing: []
- }
- }
- const listeners = []
+ outgoing: [],
+ },
+ };
+ const listeners = [];
// @ts-expect-error mocking is intentional
window.chrome = {};
// @ts-expect-error mocking is intentional
@@ -31,13 +31,12 @@ export function mockWindowsMessaging(params) {
/**
* @param {AnyWindowsMessage} input
*/
- postMessage (input) {
-
+ postMessage(input) {
// subscription events come through here also
if ('subscriptionName' in input) {
setTimeout(() => {
for (const listener of listeners) {
- listener({ origin: window.origin, data: input })
+ listener({ origin: window.origin, data: input });
}
}, 0);
return;
@@ -56,28 +55,30 @@ export function mockWindowsMessaging(params) {
msg = {
...msg,
id: input.Id,
- }
+ };
}
// record the call
- window.__playwright_01.mocks.outgoing.push(JSON.parse(JSON.stringify({
- payload: msg
- })))
+ window.__playwright_01.mocks.outgoing.push(
+ JSON.parse(
+ JSON.stringify({
+ payload: msg,
+ }),
+ ),
+ );
// if there's no 'id' field, we don't need to respond
if (!('id' in msg)) return;
-
// If we get here, it needed a response **and** we have a value for it
setTimeout(() => {
-
// if the mocked response is absent, bail with an error
if (!(msg.method in window.__playwright_01.mockResponses)) {
- throw new Error('response not found for ' + msg.method)
+ throw new Error('response not found for ' + msg.method);
}
// now access the response
- const response = window.__playwright_01.mockResponses[msg.method]
+ const response = window.__playwright_01.mockResponses[msg.method];
for (const listener of listeners) {
listener({
@@ -89,23 +90,22 @@ export function mockWindowsMessaging(params) {
featureName: msg.featureName,
id: msg.id,
},
- })
+ });
}
- }, 0)
+ }, 0);
},
- removeEventListener (_name, _listener) {
- const index = listeners.indexOf(_listener)
+ removeEventListener(_name, _listener) {
+ const index = listeners.indexOf(_listener);
if (index > -1) {
- listeners.splice(index, 1)
+ listeners.splice(index, 1);
}
},
- addEventListener (_name, listener) {
- listeners.push(listener)
- }
- }
+ addEventListener(_name, listener) {
+ listeners.push(listener);
+ },
+ };
}
-
/**
* Install a mock interface for windows messaging
* @param {{
@@ -118,22 +118,26 @@ export function mockWebkitMessaging(params) {
mockResponses: params.responses,
subscriptionEvents: [],
mocks: {
- outgoing: []
- }
- }
+ outgoing: [],
+ },
+ };
window.webkit = {
messageHandlers: {
[params.messagingContext.context]: {
/**
* @param {RequestMessage | NotificationMessage} msg
*/
- async postMessage (msg) {
- window.__playwright_01.mocks.outgoing.push(JSON.parse(JSON.stringify({
- payload: msg
- })))
+ async postMessage(msg) {
+ window.__playwright_01.mocks.outgoing.push(
+ JSON.parse(
+ JSON.stringify({
+ payload: msg,
+ }),
+ ),
+ );
// force a 'tick' to allow tests to reset mocks before reading responses
- await new Promise(resolve => setTimeout(resolve, 0));
+ await new Promise((resolve) => setTimeout(resolve, 0));
// if it's a notification, simulate the empty response and don't check for a response
if (!('id' in msg)) {
@@ -141,10 +145,10 @@ export function mockWebkitMessaging(params) {
}
if (!(msg.method in window.__playwright_01.mockResponses)) {
- throw new Error('response not found for ' + msg.method)
+ throw new Error('response not found for ' + msg.method);
}
- const response = window.__playwright_01.mockResponses[msg.method]
+ const response = window.__playwright_01.mockResponses[msg.method];
/** @type {Omit} */
const r = {
@@ -152,13 +156,13 @@ export function mockWebkitMessaging(params) {
context: msg.context,
featureName: msg.featureName,
id: msg.id,
- }
+ };
- return JSON.stringify(r)
- }
- }
- }
- }
+ return JSON.stringify(r);
+ },
+ },
+ },
+ };
}
/**
@@ -174,9 +178,9 @@ export function mockAndroidMessaging(params) {
mockResponses: params.responses,
subscriptionEvents: [],
mocks: {
- outgoing: []
- }
- }
+ outgoing: [],
+ },
+ };
window[params.messagingContext.context] = {
/**
* @param {string} jsonString
@@ -185,13 +189,16 @@ export function mockAndroidMessaging(params) {
*/
// eslint-disable-next-line require-await
process: async (jsonString, secret) => {
-
/** @type {RequestMessage | NotificationMessage} */
const msg = JSON.parse(jsonString);
- window.__playwright_01.mocks.outgoing.push(JSON.parse(JSON.stringify({
- payload: msg
- })))
+ window.__playwright_01.mocks.outgoing.push(
+ JSON.parse(
+ JSON.stringify({
+ payload: msg,
+ }),
+ ),
+ );
// if it's a notification, simulate the empty response and don't check for a response
if (!('id' in msg)) {
@@ -199,10 +206,10 @@ export function mockAndroidMessaging(params) {
}
if (!(msg.method in window.__playwright_01.mockResponses)) {
- throw new Error('response not found for ' + msg.method)
+ throw new Error('response not found for ' + msg.method);
}
- const response = window.__playwright_01.mockResponses[msg.method]
+ const response = window.__playwright_01.mockResponses[msg.method];
/** @type {Omit} */
const r = {
@@ -210,11 +217,11 @@ export function mockAndroidMessaging(params) {
context: msg.context,
featureName: msg.featureName,
id: msg.id,
- }
+ };
globalThis.messageCallback?.(secret, r);
- }
- }
+ },
+ };
}
/**
@@ -224,8 +231,8 @@ export function mockAndroidMessaging(params) {
export function mockResponses(params) {
window.__playwright_01.mockResponses = {
...window.__playwright_01.mockResponses,
- ...params.responses
- }
+ ...params.responses,
+ };
}
/**
@@ -234,14 +241,14 @@ export function mockResponses(params) {
* @param {number} params.count
*/
export function waitForCallCount(params) {
- const outgoing = window.__playwright_01.mocks.outgoing
+ const outgoing = window.__playwright_01.mocks.outgoing;
const filtered = outgoing.filter(({ payload }) => {
if ('method' in payload) {
return params.method === payload.method;
}
- return false
- })
- return filtered.length >= params.count
+ return false;
+ });
+ return filtered.length >= params.count;
}
/**
@@ -249,7 +256,7 @@ export function waitForCallCount(params) {
* @return {any}
*/
export function readOutgoingMessages() {
- return window.__playwright_01.mocks.outgoing
+ return window.__playwright_01.mocks.outgoing;
}
/**
@@ -275,7 +282,7 @@ export function wrapWindowsScripts(js, replacements) {
console.error(e)
}
})();
- `
+ `;
}
/**
@@ -303,21 +310,21 @@ export function simulateSubscriptionMessage(params) {
featureName: params.messagingContext.featureName,
subscriptionName: params.name,
params: params.payload,
- }
+ };
switch (params.injectName) {
- case "windows": {
- // @ts-expect-error DDG custom global
- const fn = window.chrome?.webview?.postMessage || window.windowsInteropPostMessage;
- fn(subscriptionEvent)
- break;
- }
- case "apple":
- case "apple-isolated": {
- if (!(params.name in window)) throw new Error('subscription fn not found for: ' + params.injectName);
- window[params.name](subscriptionEvent);
- break;
- }
- default: throw new Error('platform not supported yet: ' + params.injectName)
+ case 'windows': {
+ // @ts-expect-error DDG custom global
+ const fn = window.chrome?.webview?.postMessage || window.windowsInteropPostMessage;
+ fn(subscriptionEvent);
+ break;
+ }
+ case 'apple':
+ case 'apple-isolated': {
+ if (!(params.name in window)) throw new Error('subscription fn not found for: ' + params.injectName);
+ window[params.name](subscriptionEvent);
+ break;
+ }
+ default:
+ throw new Error('platform not supported yet: ' + params.injectName);
}
}
-
diff --git a/messaging/lib/typed-messages.js b/messaging/lib/typed-messages.js
index 6a42b7ef0..ed12a277e 100644
--- a/messaging/lib/typed-messages.js
+++ b/messaging/lib/typed-messages.js
@@ -11,7 +11,7 @@
* @param {import("@duckduckgo/messaging").Messaging} messaging
* @returns {BaseClass}
*/
-export function createTypedMessages (base, messaging) {
- const asAny = /** @type {any} */(messaging)
- return /** @type {BaseClass} */(asAny)
+export function createTypedMessages(base, messaging) {
+ const asAny = /** @type {any} */ (messaging);
+ return /** @type {BaseClass} */ (asAny);
}
diff --git a/messaging/lib/webkit.js b/messaging/lib/webkit.js
index 33adeafd7..ecb56b08b 100644
--- a/messaging/lib/webkit.js
+++ b/messaging/lib/webkit.js
@@ -7,8 +7,8 @@
* part of the message handling, see {@link WebkitMessagingTransport} for details.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { MessagingTransport, MissingHandler } from '../index.js'
-import { isResponseFor, isSubscriptionEventFor } from '../schema.js'
+import { MessagingTransport, MissingHandler } from '../index.js';
+import { isResponseFor, isSubscriptionEventFor } from '../schema.js';
/**
* @example
@@ -61,12 +61,12 @@ export class WebkitMessagingTransport {
* @param {WebkitMessagingConfig} config
* @param {import('../index.js').MessagingContext} messagingContext
*/
- constructor (config, messagingContext) {
- this.messagingContext = messagingContext
- this.config = config
- this.globals = captureGlobals()
+ constructor(config, messagingContext) {
+ this.messagingContext = messagingContext;
+ this.config = config;
+ this.globals = captureGlobals();
if (!this.config.hasModernWebkitAPI) {
- this.captureWebkitHandlers(this.config.webkitMessageHandlerNames)
+ this.captureWebkitHandlers(this.config.webkitMessageHandlerNames);
}
}
@@ -76,25 +76,25 @@ export class WebkitMessagingTransport {
* @param {*} data
* @internal
*/
- wkSend (handler, data = {}) {
+ wkSend(handler, data = {}) {
if (!(handler in this.globals.window.webkit.messageHandlers)) {
- throw new MissingHandler(`Missing webkit handler: '${handler}'`, handler)
+ throw new MissingHandler(`Missing webkit handler: '${handler}'`, handler);
}
if (!this.config.hasModernWebkitAPI) {
const outgoing = {
...data,
messageHandling: {
...data.messageHandling,
- secret: this.config.secret
- }
- }
+ secret: this.config.secret,
+ },
+ };
if (!(handler in this.globals.capturedWebkitHandlers)) {
- throw new MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler)
+ throw new MissingHandler(`cannot continue, method ${handler} not captured on macos < 11`, handler);
} else {
- return this.globals.capturedWebkitHandlers[handler](outgoing)
+ return this.globals.capturedWebkitHandlers[handler](outgoing);
}
}
- return this.globals.window.webkit.messageHandlers[handler].postMessage?.(data)
+ return this.globals.window.webkit.messageHandlers[handler].postMessage?.(data);
}
/**
@@ -104,44 +104,41 @@ export class WebkitMessagingTransport {
* @returns {Promise<*>}
* @internal
*/
- async wkSendAndWait (handler, data) {
+ async wkSendAndWait(handler, data) {
if (this.config.hasModernWebkitAPI) {
- const response = await this.wkSend(handler, data)
- return this.globals.JSONparse(response || '{}')
+ const response = await this.wkSend(handler, data);
+ return this.globals.JSONparse(response || '{}');
}
try {
- const randMethodName = this.createRandMethodName()
- const key = await this.createRandKey()
- const iv = this.createRandIv()
+ const randMethodName = this.createRandMethodName();
+ const key = await this.createRandKey();
+ const iv = this.createRandIv();
- const {
- ciphertext,
- tag
- } = await new this.globals.Promise((/** @type {any} */ resolve) => {
- this.generateRandomMethod(randMethodName, resolve)
+ const { ciphertext, tag } = await new this.globals.Promise((/** @type {any} */ resolve) => {
+ this.generateRandomMethod(randMethodName, resolve);
// @ts-expect-error - this is a carve-out for catalina that will be removed soon
data.messageHandling = new SecureMessagingParams({
methodName: randMethodName,
secret: this.config.secret,
key: this.globals.Arrayfrom(key),
- iv: this.globals.Arrayfrom(iv)
- })
- this.wkSend(handler, data)
- })
+ iv: this.globals.Arrayfrom(iv),
+ });
+ this.wkSend(handler, data);
+ });
- const cipher = new this.globals.Uint8Array([...ciphertext, ...tag])
- const decrypted = await this.decrypt(cipher, key, iv)
- return this.globals.JSONparse(decrypted || '{}')
+ const cipher = new this.globals.Uint8Array([...ciphertext, ...tag]);
+ const decrypted = await this.decrypt(cipher, key, iv);
+ return this.globals.JSONparse(decrypted || '{}');
} catch (e) {
// re-throw when the error is just a 'MissingHandler'
if (e instanceof MissingHandler) {
- throw e
+ throw e;
} else {
- console.error('decryption failed', e)
- console.error(e)
- return { error: e }
+ console.error('decryption failed', e);
+ console.error(e);
+ return { error: e };
}
}
}
@@ -149,27 +146,27 @@ export class WebkitMessagingTransport {
/**
* @param {import('../index.js').NotificationMessage} msg
*/
- notify (msg) {
- this.wkSend(msg.context, msg)
+ notify(msg) {
+ this.wkSend(msg.context, msg);
}
/**
* @param {import('../index.js').RequestMessage} msg
*/
- async request (msg) {
- const data = await this.wkSendAndWait(msg.context, msg)
+ async request(msg) {
+ const data = await this.wkSendAndWait(msg.context, msg);
if (isResponseFor(msg, data)) {
if (data.result) {
- return data.result || {}
+ return data.result || {};
}
// forward the error if one was given explicity
if (data.error) {
- throw new Error(data.error.message)
+ throw new Error(data.error.message);
}
}
- throw new Error('an unknown error occurred')
+ throw new Error('an unknown error occurred');
}
/**
@@ -179,7 +176,7 @@ export class WebkitMessagingTransport {
* @param {Function} callback
* @internal
*/
- generateRandomMethod (randomMethodName, callback) {
+ generateRandomMethod(randomMethodName, callback) {
this.globals.ObjectDefineProperty(this.globals.window, randomMethodName, {
enumerable: false,
// configurable, To allow for deletion later
@@ -189,27 +186,26 @@ export class WebkitMessagingTransport {
* @param {any[]} args
*/
value: (...args) => {
-
- callback(...args)
- delete this.globals.window[randomMethodName]
- }
- })
+ callback(...args);
+ delete this.globals.window[randomMethodName];
+ },
+ });
}
/**
* @internal
* @return {string}
*/
- randomString () {
- return '' + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0]
+ randomString() {
+ return '' + this.globals.getRandomValues(new this.globals.Uint32Array(1))[0];
}
/**
* @internal
* @return {string}
*/
- createRandMethodName () {
- return '_' + this.randomString()
+ createRandMethodName() {
+ return '_' + this.randomString();
}
/**
@@ -218,25 +214,25 @@ export class WebkitMessagingTransport {
*/
algoObj = {
name: 'AES-GCM',
- length: 256
- }
+ length: 256,
+ };
/**
* @returns {Promise}
* @internal
*/
- async createRandKey () {
- const key = await this.globals.generateKey(this.algoObj, true, ['encrypt', 'decrypt'])
- const exportedKey = await this.globals.exportKey('raw', key)
- return new this.globals.Uint8Array(exportedKey)
+ async createRandKey() {
+ const key = await this.globals.generateKey(this.algoObj, true, ['encrypt', 'decrypt']);
+ const exportedKey = await this.globals.exportKey('raw', key);
+ return new this.globals.Uint8Array(exportedKey);
}
/**
* @returns {Uint8Array}
* @internal
*/
- createRandIv () {
- return this.globals.getRandomValues(new this.globals.Uint8Array(12))
+ createRandIv() {
+ return this.globals.getRandomValues(new this.globals.Uint8Array(12));
}
/**
@@ -246,17 +242,17 @@ export class WebkitMessagingTransport {
* @returns {Promise}
* @internal
*/
- async decrypt (ciphertext, key, iv) {
- const cryptoKey = await this.globals.importKey('raw', key, 'AES-GCM', false, ['decrypt'])
+ async decrypt(ciphertext, key, iv) {
+ const cryptoKey = await this.globals.importKey('raw', key, 'AES-GCM', false, ['decrypt']);
const algo = {
name: 'AES-GCM',
- iv
- }
+ iv,
+ };
- const decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext)
+ const decrypted = await this.globals.decrypt(algo, cryptoKey, ciphertext);
- const dec = new this.globals.TextDecoder()
- return dec.decode(decrypted)
+ const dec = new this.globals.TextDecoder();
+ return dec.decode(decrypted);
}
/**
@@ -265,19 +261,19 @@ export class WebkitMessagingTransport {
*
* @param {string[]} handlerNames
*/
- captureWebkitHandlers (handlerNames) {
- const handlers = window.webkit.messageHandlers
- if (!handlers) throw new MissingHandler('window.webkit.messageHandlers was absent', 'all')
+ captureWebkitHandlers(handlerNames) {
+ const handlers = window.webkit.messageHandlers;
+ if (!handlers) throw new MissingHandler('window.webkit.messageHandlers was absent', 'all');
for (const webkitMessageHandlerName of handlerNames) {
if (typeof handlers[webkitMessageHandlerName]?.postMessage === 'function') {
/**
* `bind` is used here to ensure future calls to the captured
* `postMessage` have the correct `this` context
*/
- const original = handlers[webkitMessageHandlerName]
- const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original)
- this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound
- delete handlers[webkitMessageHandlerName].postMessage
+ const original = handlers[webkitMessageHandlerName];
+ const bound = handlers[webkitMessageHandlerName].postMessage?.bind(original);
+ this.globals.capturedWebkitHandlers[webkitMessageHandlerName] = bound;
+ delete handlers[webkitMessageHandlerName].postMessage;
}
}
}
@@ -286,10 +282,10 @@ export class WebkitMessagingTransport {
* @param {import('../index.js').Subscription} msg
* @param {(value: unknown) => void} callback
*/
- subscribe (msg, callback) {
+ subscribe(msg, callback) {
// for now, bail if there's already a handler setup for this subscription
if (msg.subscriptionName in this.globals.window) {
- throw new this.globals.Error(`A subscription with the name ${msg.subscriptionName} already exists`)
+ throw new this.globals.Error(`A subscription with the name ${msg.subscriptionName} already exists`);
}
this.globals.ObjectDefineProperty(this.globals.window, msg.subscriptionName, {
enumerable: false,
@@ -297,15 +293,15 @@ export class WebkitMessagingTransport {
writable: false,
value: (data) => {
if (data && isSubscriptionEventFor(msg, data)) {
- callback(data.params)
+ callback(data.params);
} else {
- console.warn('Received a message that did not match the subscription', data)
+ console.warn('Received a message that did not match the subscription', data);
}
- }
- })
+ },
+ });
return () => {
- this.globals.ReflectDeleteProperty(this.globals.window, msg.subscriptionName)
- }
+ this.globals.ReflectDeleteProperty(this.globals.window, msg.subscriptionName);
+ };
}
}
@@ -326,12 +322,12 @@ export class WebkitMessagingConfig {
* @param {string} params.secret
* @internal
*/
- constructor (params) {
+ constructor(params) {
/**
* Whether or not the current WebKit Platform supports secure messaging
* by default (eg: macOS 11+)
*/
- this.hasModernWebkitAPI = params.hasModernWebkitAPI
+ this.hasModernWebkitAPI = params.hasModernWebkitAPI;
/**
* A list of WebKit message handler names that a user script can send.
*
@@ -347,12 +343,12 @@ export class WebkitMessagingConfig {
* webkitMessageHandlerNames: ['foo']
* ```
*/
- this.webkitMessageHandlerNames = params.webkitMessageHandlerNames
+ this.webkitMessageHandlerNames = params.webkitMessageHandlerNames;
/**
* A string provided by native platforms to be sent with future outgoing
* messages.
*/
- this.secret = params.secret
+ this.secret = params.secret;
}
}
@@ -368,23 +364,23 @@ export class SecureMessagingParams {
* @param {number[]} params.key
* @param {number[]} params.iv
*/
- constructor (params) {
+ constructor(params) {
/**
* The method that's been appended to `window` to be called later
*/
- this.methodName = params.methodName
+ this.methodName = params.methodName;
/**
* The secret used to ensure message sender validity
*/
- this.secret = params.secret
+ this.secret = params.secret;
/**
* The CipherKey as number[]
*/
- this.key = params.key
+ this.key = params.key;
/**
* The Initial Vector as number[]
*/
- this.iv = params.iv
+ this.iv = params.iv;
}
}
@@ -392,7 +388,7 @@ export class SecureMessagingParams {
* Capture some globals used for messaging handling to prevent page
* scripts from tampering with this
*/
-function captureGlobals () {
+function captureGlobals() {
// Create base with null prototype
const globals = {
window,
@@ -411,15 +407,15 @@ function captureGlobals () {
ObjectDefineProperty: window.Object.defineProperty,
addEventListener: window.addEventListener.bind(window),
/** @type {Record} */
- capturedWebkitHandlers: {}
- }
+ capturedWebkitHandlers: {},
+ };
if (isSecureContext) {
// skip for HTTP content since window.crypto.subtle is unavailable
- globals.generateKey = window.crypto.subtle.generateKey.bind(window.crypto.subtle)
- globals.exportKey = window.crypto.subtle.exportKey.bind(window.crypto.subtle)
- globals.importKey = window.crypto.subtle.importKey.bind(window.crypto.subtle)
- globals.encrypt = window.crypto.subtle.encrypt.bind(window.crypto.subtle)
- globals.decrypt = window.crypto.subtle.decrypt.bind(window.crypto.subtle)
+ globals.generateKey = window.crypto.subtle.generateKey.bind(window.crypto.subtle);
+ globals.exportKey = window.crypto.subtle.exportKey.bind(window.crypto.subtle);
+ globals.importKey = window.crypto.subtle.importKey.bind(window.crypto.subtle);
+ globals.encrypt = window.crypto.subtle.encrypt.bind(window.crypto.subtle);
+ globals.decrypt = window.crypto.subtle.decrypt.bind(window.crypto.subtle);
}
- return globals
+ return globals;
}
diff --git a/messaging/lib/windows.js b/messaging/lib/windows.js
index 33d7d8944..5ab53ee8c 100644
--- a/messaging/lib/windows.js
+++ b/messaging/lib/windows.js
@@ -7,7 +7,7 @@
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { MessagingTransport, NotificationMessage, RequestMessage } from '../index.js'
+import { MessagingTransport, NotificationMessage, RequestMessage } from '../index.js';
/**
* An implementation of {@link MessagingTransport} for Windows
@@ -22,20 +22,20 @@ export class WindowsMessagingTransport {
* @param {import('../index.js').MessagingContext} messagingContext
* @internal
*/
- constructor (config, messagingContext) {
- this.messagingContext = messagingContext
- this.config = config
+ constructor(config, messagingContext) {
+ this.messagingContext = messagingContext;
+ this.config = config;
this.globals = {
window,
JSONparse: window.JSON.parse,
JSONstringify: window.JSON.stringify,
Promise: window.Promise,
Error: window.Error,
- String: window.String
- }
+ String: window.String,
+ };
for (const [methodName, fn] of Object.entries(this.config.methods)) {
if (typeof fn !== 'function') {
- throw new Error('cannot create WindowsMessagingTransport, missing the method: ' + methodName)
+ throw new Error('cannot create WindowsMessagingTransport, missing the method: ' + methodName);
}
}
}
@@ -43,10 +43,10 @@ export class WindowsMessagingTransport {
/**
* @param {import('../index.js').NotificationMessage} msg
*/
- notify (msg) {
- const data = this.globals.JSONparse(this.globals.JSONstringify(msg.params || {}))
- const notification = WindowsNotification.fromNotification(msg, data)
- this.config.methods.postMessage(notification)
+ notify(msg) {
+ const data = this.globals.JSONparse(this.globals.JSONstringify(msg.params || {}));
+ const notification = WindowsNotification.fromNotification(msg, data);
+ this.config.methods.postMessage(notification);
}
/**
@@ -54,74 +54,74 @@ export class WindowsMessagingTransport {
* @param {{signal?: AbortSignal}} opts
* @return {Promise}
*/
- request (msg, opts = {}) {
+ request(msg, opts = {}) {
// convert the message to window-specific naming
- const data = this.globals.JSONparse(this.globals.JSONstringify(msg.params || {}))
- const outgoing = WindowsRequestMessage.fromRequest(msg, data)
+ const data = this.globals.JSONparse(this.globals.JSONstringify(msg.params || {}));
+ const outgoing = WindowsRequestMessage.fromRequest(msg, data);
// send the message
- this.config.methods.postMessage(outgoing)
+ this.config.methods.postMessage(outgoing);
// compare incoming messages against the `msg.id`
const comparator = (eventData) => {
- return eventData.featureName === msg.featureName &&
- eventData.context === msg.context &&
- eventData.id === msg.id
- }
+ return eventData.featureName === msg.featureName && eventData.context === msg.context && eventData.id === msg.id;
+ };
/**
* @param data
* @return {data is import('../index.js').MessageResponse}
*/
- function isMessageResponse (data) {
- if ('result' in data) return true
- if ('error' in data) return true
- return false
+ function isMessageResponse(data) {
+ if ('result' in data) return true;
+ if ('error' in data) return true;
+ return false;
}
// now wait for a matching message
return new this.globals.Promise((resolve, reject) => {
try {
this._subscribe(comparator, opts, (value, unsubscribe) => {
- unsubscribe()
+ unsubscribe();
if (!isMessageResponse(value)) {
- console.warn('unknown response type', value)
- return reject(new this.globals.Error('unknown response'))
+ console.warn('unknown response type', value);
+ return reject(new this.globals.Error('unknown response'));
}
if (value.result) {
- return resolve(value.result)
+ return resolve(value.result);
}
- const message = this.globals.String(value.error?.message || 'unknown error')
- reject(new this.globals.Error(message))
- })
+ const message = this.globals.String(value.error?.message || 'unknown error');
+ reject(new this.globals.Error(message));
+ });
} catch (e) {
- reject(e)
+ reject(e);
}
- })
+ });
}
/**
* @param {import('../index.js').Subscription} msg
* @param {(value: unknown | undefined) => void} callback
*/
- subscribe (msg, callback) {
+ subscribe(msg, callback) {
// compare incoming messages against the `msg.subscriptionName`
const comparator = (eventData) => {
- return eventData.featureName === msg.featureName &&
+ return (
+ eventData.featureName === msg.featureName &&
eventData.context === msg.context &&
eventData.subscriptionName === msg.subscriptionName
- }
+ );
+ };
// only forward the 'params' from a SubscriptionEvent
const cb = (eventData) => {
- return callback(eventData.params)
- }
+ return callback(eventData.params);
+ };
// now listen for matching incoming messages.
- return this._subscribe(comparator, {}, cb)
+ return this._subscribe(comparator, {}, cb);
}
/**
@@ -133,14 +133,14 @@ export class WindowsMessagingTransport {
* @param {(value: Incoming, unsubscribe: (()=>void)) => void} callback
* @internal
*/
- _subscribe (comparator, options, callback) {
+ _subscribe(comparator, options, callback) {
// if already aborted, reject immediately
if (options?.signal?.aborted) {
- throw new DOMException('Aborted', 'AbortError')
+ throw new DOMException('Aborted', 'AbortError');
}
/** @type {(()=>void) | undefined} */
// eslint-disable-next-line prefer-const
- let teardown
+ let teardown;
/**
* @param {MessageEvent} event
@@ -148,41 +148,41 @@ export class WindowsMessagingTransport {
const idHandler = (event) => {
if (this.messagingContext.env === 'production') {
if (event.origin !== null && event.origin !== undefined) {
- console.warn('ignoring because evt.origin is not `null` or `undefined`')
- return
+ console.warn('ignoring because evt.origin is not `null` or `undefined`');
+ return;
}
}
if (!event.data) {
- console.warn('data absent from message')
- return
+ console.warn('data absent from message');
+ return;
}
if (comparator(event.data)) {
- if (!teardown) throw new Error('unreachable')
- callback(event.data, teardown)
+ if (!teardown) throw new Error('unreachable');
+ callback(event.data, teardown);
}
- }
+ };
// what to do if this promise is aborted
const abortHandler = () => {
- teardown?.()
- throw new DOMException('Aborted', 'AbortError')
- }
+ teardown?.();
+ throw new DOMException('Aborted', 'AbortError');
+ };
// console.log('DEBUG: handler setup', { config, comparator })
-
- this.config.methods.addEventListener('message', idHandler)
- options?.signal?.addEventListener('abort', abortHandler)
+
+ this.config.methods.addEventListener('message', idHandler);
+ options?.signal?.addEventListener('abort', abortHandler);
teardown = () => {
// console.log('DEBUG: handler teardown', { config, comparator })
-
- this.config.methods.removeEventListener('message', idHandler)
- options?.signal?.removeEventListener('abort', abortHandler)
- }
+
+ this.config.methods.removeEventListener('message', idHandler);
+ options?.signal?.removeEventListener('abort', abortHandler);
+ };
return () => {
- teardown?.()
- }
+ teardown?.();
+ };
}
}
@@ -211,15 +211,15 @@ export class WindowsMessagingConfig {
* @param {WindowsInteropMethods} params.methods
* @internal
*/
- constructor (params) {
+ constructor(params) {
/**
* The methods required for communication
*/
- this.methods = params.methods
+ this.methods = params.methods;
/**
* @type {'windows'}
*/
- this.platform = 'windows'
+ this.platform = 'windows';
}
}
@@ -233,10 +233,10 @@ export class WindowsInteropMethods {
* @param {Window['addEventListener']} params.addEventListener
* @param {Window['removeEventListener']} params.removeEventListener
*/
- constructor (params) {
- this.postMessage = params.postMessage
- this.addEventListener = params.addEventListener
- this.removeEventListener = params.removeEventListener
+ constructor(params) {
+ this.postMessage = params.postMessage;
+ this.addEventListener = params.addEventListener;
+ this.removeEventListener = params.removeEventListener;
}
}
@@ -255,23 +255,23 @@ export class WindowsNotification {
* @param {Record} [params.Data]
* @internal
*/
- constructor (params) {
+ constructor(params) {
/**
* Alias for: {@link NotificationMessage.context}
*/
- this.Feature = params.Feature
+ this.Feature = params.Feature;
/**
* Alias for: {@link NotificationMessage.featureName}
*/
- this.SubFeatureName = params.SubFeatureName
+ this.SubFeatureName = params.SubFeatureName;
/**
* Alias for: {@link NotificationMessage.method}
*/
- this.Name = params.Name
+ this.Name = params.Name;
/**
* Alias for: {@link NotificationMessage.params}
*/
- this.Data = params.Data
+ this.Data = params.Data;
}
/**
@@ -279,15 +279,15 @@ export class WindowsNotification {
* @param {NotificationMessage} notification
* @returns {WindowsNotification}
*/
- static fromNotification (notification, data) {
+ static fromNotification(notification, data) {
/** @type {WindowsNotification} */
const output = {
Data: data,
Feature: notification.context,
SubFeatureName: notification.featureName,
- Name: notification.method
- }
- return output
+ Name: notification.method,
+ };
+ return output;
}
}
@@ -306,12 +306,12 @@ export class WindowsRequestMessage {
* @param {string} [params.Id]
* @internal
*/
- constructor (params) {
- this.Feature = params.Feature
- this.SubFeatureName = params.SubFeatureName
- this.Name = params.Name
- this.Data = params.Data
- this.Id = params.Id
+ constructor(params) {
+ this.Feature = params.Feature;
+ this.SubFeatureName = params.SubFeatureName;
+ this.Name = params.Name;
+ this.Data = params.Data;
+ this.Id = params.Id;
}
/**
@@ -320,15 +320,15 @@ export class WindowsRequestMessage {
* @param {Record} data
* @returns {WindowsRequestMessage}
*/
- static fromRequest (msg, data) {
+ static fromRequest(msg, data) {
/** @type {WindowsRequestMessage} */
const output = {
Data: data,
Feature: msg.context,
SubFeatureName: msg.featureName,
Name: msg.method,
- Id: msg.id
- }
- return output
+ Id: msg.id,
+ };
+ return output;
}
}
diff --git a/messaging/schema.js b/messaging/schema.js
index 2e661d36c..0e0e715fa 100644
--- a/messaging/schema.js
+++ b/messaging/schema.js
@@ -30,29 +30,29 @@ export class RequestMessage {
* @param {Record} [params.params]
* @internal
*/
- constructor (params) {
+ constructor(params) {
/**
* The global context for this message. For example, something like `contentScopeScripts` or `specialPages`
* @type {string}
*/
- this.context = params.context
+ this.context = params.context;
/**
* The name of the sub-feature, such as `duckPlayer` or `clickToLoad`
* @type {string}
*/
- this.featureName = params.featureName
+ this.featureName = params.featureName;
/**
* The name of the handler to be executed on the native side
*/
- this.method = params.method
+ this.method = params.method;
/**
* The `id` that native sides can use when sending back a response
*/
- this.id = params.id
+ this.id = params.id;
/**
* Optional data payload - must be a plain key/value object
*/
- this.params = params.params
+ this.params = params.params;
}
}
@@ -69,29 +69,29 @@ export class MessageResponse {
* @param {MessageError} [params.error]
* @internal
*/
- constructor (params) {
+ constructor(params) {
/**
* The global context for this message. For example, something like `contentScopeScripts` or `specialPages`
* @type {string}
*/
- this.context = params.context
+ this.context = params.context;
/**
* The name of the sub-feature, such as `duckPlayer` or `clickToLoad`
* @type {string}
*/
- this.featureName = params.featureName
+ this.featureName = params.featureName;
/**
* The resulting payload - must be a plain object
*/
- this.result = params.result
+ this.result = params.result;
/**
* The `id` that is used to pair this response with its sender
*/
- this.id = params.id
+ this.id = params.id;
/**
* An optional error
*/
- this.error = params.error
+ this.error = params.error;
}
}
@@ -108,23 +108,23 @@ export class NotificationMessage {
* @param {Record} [params.params]
* @internal
*/
- constructor (params) {
+ constructor(params) {
/**
* The global context for this message. For example, something like `contentScopeScripts` or `specialPages`
*/
- this.context = params.context
+ this.context = params.context;
/**
* The name of the sub-feature, such as `duckPlayer` or `clickToLoad`
*/
- this.featureName = params.featureName
+ this.featureName = params.featureName;
/**
* The name of the handler to be executed on the native side
*/
- this.method = params.method
+ this.method = params.method;
/**
* An optional payload
*/
- this.params = params.params
+ this.params = params.params;
}
}
@@ -136,10 +136,10 @@ export class Subscription {
* @param {string} params.subscriptionName
* @internal
*/
- constructor (params) {
- this.context = params.context
- this.featureName = params.featureName
- this.subscriptionName = params.subscriptionName
+ constructor(params) {
+ this.context = params.context;
+ this.featureName = params.featureName;
+ this.subscriptionName = params.subscriptionName;
}
}
@@ -155,11 +155,11 @@ export class SubscriptionEvent {
* @param {Record} [params.params]
* @internal
*/
- constructor (params) {
- this.context = params.context
- this.featureName = params.featureName
- this.subscriptionName = params.subscriptionName
- this.params = params.params
+ constructor(params) {
+ this.context = params.context;
+ this.featureName = params.featureName;
+ this.subscriptionName = params.subscriptionName;
+ this.params = params.params;
}
}
@@ -172,8 +172,8 @@ export class MessageError {
* @param {string} params.message
* @internal
*/
- constructor (params) {
- this.message = params.message
+ constructor(params) {
+ this.message = params.message;
}
}
@@ -182,18 +182,16 @@ export class MessageError {
* @param {Record} data
* @return {data is MessageResponse}
*/
-export function isResponseFor (request, data) {
+export function isResponseFor(request, data) {
if ('result' in data) {
- return data.featureName === request.featureName &&
- data.context === request.context &&
- data.id === request.id
+ return data.featureName === request.featureName && data.context === request.context && data.id === request.id;
}
if ('error' in data) {
if ('message' in data.error) {
- return true
+ return true;
}
}
- return false
+ return false;
}
/**
@@ -201,12 +199,10 @@ export function isResponseFor (request, data) {
* @param {Record} data
* @return {data is SubscriptionEvent}
*/
-export function isSubscriptionEventFor (sub, data) {
+export function isSubscriptionEventFor(sub, data) {
if ('subscriptionName' in data) {
- return data.featureName === sub.featureName &&
- data.context === sub.context &&
- data.subscriptionName === sub.subscriptionName
+ return data.featureName === sub.featureName && data.context === sub.context && data.subscriptionName === sub.subscriptionName;
}
- return false
+ return false;
}
diff --git a/package-lock.json b/package-lock.json
index 1018905c5..4de725f80 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,11 +14,12 @@
"types-generator"
],
"devDependencies": {
- "@duckduckgo/eslint-config": "github:duckduckgo/eslint-config#51dc868a4342379d6aec1d0f4214156e7046d5f6",
+ "@duckduckgo/eslint-config": "github:duckduckgo/eslint-config#v0.1.0",
"@playwright/test": "^1.48.2",
"@types/eslint__js": "^8.42.3",
"eslint": "^9.13.0",
"minimist": "^1.2.8",
+ "prettier": "3.3.3",
"stylelint": "^15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-csstree-validator": "^3.0.0",
@@ -45,7 +46,7 @@
"@types/jasmine": "^5.1.4",
"@types/node": "^22.8.7",
"@typescript-eslint/eslint-plugin": "^6.9.1",
- "config-builder": "git+ssh://git@github.com/duckduckgo/privacy-configuration.git#207bcafcd8d67d0530569f7efcf84463194b999b",
+ "config-builder": "github:duckduckgo/privacy-configuration#1729260354597",
"fast-check": "^3.23.1",
"jasmine": "^5.4.0",
"minimist": "^1.2.8",
@@ -531,12 +532,12 @@
},
"node_modules/@duckduckgo/eslint-config": {
"version": "1.0.0",
- "resolved": "git+ssh://git@github.com/duckduckgo/eslint-config.git#51dc868a4342379d6aec1d0f4214156e7046d5f6",
- "integrity": "sha512-4LiMeyUHFI/sBat9IWu0kVE4CbYhVOQ0dSIBETTBxvAOvolVARZZZ7tlsaag6/5EU2gvHwTRLSVTOVUhLdzBRA==",
+ "resolved": "git+ssh://git@github.com/duckduckgo/eslint-config.git#09f3780bb1826fe123ef3e4eb36dcbd53ca1fd80",
"dev": true,
"license": "ISC",
"dependencies": {
"@eslint/js": "^9.13.0",
+ "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^17.11.1",
"eslint-plugin-promise": "^7.1.0"
@@ -3624,6 +3625,19 @@
"eslint": ">=6.0.0"
}
},
+ "node_modules/eslint-config-prettier": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -3800,9 +3814,9 @@
}
},
"node_modules/eslint-plugin-n/node_modules/globals": {
- "version": "15.11.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz",
- "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==",
+ "version": "15.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
+ "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6455,6 +6469,7 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
+ "license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -8408,12 +8423,12 @@
"requires": {}
},
"@duckduckgo/eslint-config": {
- "version": "git+ssh://git@github.com/duckduckgo/eslint-config.git#51dc868a4342379d6aec1d0f4214156e7046d5f6",
- "integrity": "sha512-4LiMeyUHFI/sBat9IWu0kVE4CbYhVOQ0dSIBETTBxvAOvolVARZZZ7tlsaag6/5EU2gvHwTRLSVTOVUhLdzBRA==",
+ "version": "git+ssh://git@github.com/duckduckgo/eslint-config.git#09f3780bb1826fe123ef3e4eb36dcbd53ca1fd80",
"dev": true,
- "from": "@duckduckgo/eslint-config@github:duckduckgo/eslint-config#51dc868a4342379d6aec1d0f4214156e7046d5f6",
+ "from": "@duckduckgo/eslint-config@github:duckduckgo/eslint-config#v0.1.0",
"requires": {
"@eslint/js": "^9.13.0",
+ "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^17.11.1",
"eslint-plugin-promise": "^7.1.0"
@@ -10486,6 +10501,13 @@
"semver": "^7.5.4"
}
},
+ "eslint-config-prettier": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+ "dev": true,
+ "requires": {}
+ },
"eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -10618,9 +10640,9 @@
}
},
"globals": {
- "version": "15.11.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz",
- "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==",
+ "version": "15.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
+ "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
"dev": true
},
"minimatch": {
@@ -11295,7 +11317,7 @@
"@types/jasmine": "^5.1.4",
"@types/node": "^22.8.7",
"@typescript-eslint/eslint-plugin": "^6.9.1",
- "config-builder": "git+ssh://git@github.com/duckduckgo/privacy-configuration.git#207bcafcd8d67d0530569f7efcf84463194b999b",
+ "config-builder": "github:duckduckgo/privacy-configuration#1729260354597",
"fast-check": "^3.23.1",
"immutable-json-patch": "^6.0.1",
"jasmine": "^5.4.0",
diff --git a/package.json b/package.json
index 117c7b8fc..4877eb699 100644
--- a/package.json
+++ b/package.json
@@ -16,10 +16,10 @@
"docs-watch": "typedoc --watch",
"tsc": "tsc",
"tsc-watch": "tsc --watch",
- "lint": "eslint . && npm run tsc && npm run lint-no-output-globals",
+ "lint": "eslint . && npm run tsc && npm run lint-no-output-globals && npx prettier . --check",
"lint-no-output-globals": "eslint --no-inline-config --config build-output.eslint.config.js Sources/ContentScopeScripts/dist/contentScope.js",
"postlint": "npm run lint --workspaces --if-present",
- "lint-fix": "eslint . --fix && npm run tsc",
+ "lint-fix": "eslint . --fix && npx prettier . --write && npm run tsc",
"stylelint": "npx stylelint \"**/*.css\"",
"stylelint-fix": "npx stylelint \"**/*.css\" --fix",
"serve": "http-server -c-1 --port 3220 integration-test/test-pages",
@@ -33,16 +33,17 @@
"types-generator"
],
"devDependencies": {
- "@duckduckgo/eslint-config": "github:duckduckgo/eslint-config#51dc868a4342379d6aec1d0f4214156e7046d5f6",
+ "@duckduckgo/eslint-config": "github:duckduckgo/eslint-config#v0.1.0",
+ "@playwright/test": "^1.48.2",
"@types/eslint__js": "^8.42.3",
"eslint": "^9.13.0",
"minimist": "^1.2.8",
+ "prettier": "3.3.3",
"stylelint": "^15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-csstree-validator": "^3.0.0",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
- "typescript-eslint": "^8.12.2",
- "@playwright/test": "^1.48.2"
+ "typescript-eslint": "^8.12.2"
}
}
diff --git a/scripts/script-utils.js b/scripts/script-utils.js
index 44fb6eb47..264f8b0df 100644
--- a/scripts/script-utils.js
+++ b/scripts/script-utils.js
@@ -1,25 +1,25 @@
-import { dirname } from 'node:path'
-import { mkdirSync, writeFileSync } from 'node:fs'
-import { fileURLToPath } from 'node:url'
-import minimist from 'minimist'
+import { dirname } from 'node:path';
+import { mkdirSync, writeFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import minimist from 'minimist';
/**
* A cross-platform 'mkdirp' + writing to disk
* @param {string[]} filepaths
* @param {string} content
*/
-export function write (filepaths, content) {
+export function write(filepaths, content) {
for (const filepath of filepaths.flat()) {
try {
- const pathWithoutFile = dirname(filepath)
- mkdirSync(pathWithoutFile, { recursive: true })
+ const pathWithoutFile = dirname(filepath);
+ mkdirSync(pathWithoutFile, { recursive: true });
} catch (e) {
// EEXIST is expected, for anything else re-throw
if (e.code !== 'EEXIST') {
- throw e
+ throw e;
}
}
- writeFileSync(filepath, content)
+ writeFileSync(filepath, content);
}
}
@@ -29,18 +29,18 @@ export function write (filepaths, content) {
* @param {string[]} requiredFields - array of required keys
* @param {string} [help] - optional help text
*/
-export function parseArgs (args, requiredFields, help = '') {
- const parsedArgs = minimist(args)
+export function parseArgs(args, requiredFields, help = '') {
+ const parsedArgs = minimist(args);
for (const field of requiredFields) {
if (!(field in parsedArgs)) {
- console.error(`Missing required argument: --${field}`)
- if (help) console.log(help)
- process.exit(1)
+ console.error(`Missing required argument: --${field}`);
+ if (help) console.log(help);
+ process.exit(1);
}
}
- return parsedArgs
+ return parsedArgs;
}
/**
@@ -48,12 +48,12 @@ export function parseArgs (args, requiredFields, help = '') {
*
* On windows, 'pathname' has a leading `/` which needs removing
*/
-export function cwd (current) {
- const pathname = new URL('.', current).pathname
+export function cwd(current) {
+ const pathname = new URL('.', current).pathname;
if (process.platform === 'win32') {
- return pathname.slice(1)
+ return pathname.slice(1);
}
- return pathname
+ return pathname;
}
/**
@@ -61,13 +61,11 @@ export function cwd (current) {
* @param metaUrl
* @return {boolean}
*/
-export function isLaunchFile (metaUrl) {
+export function isLaunchFile(metaUrl) {
if (!metaUrl) {
- throw new Error(
- 'Incorrect usage of isLaunchFile. Use isLaunchFile(import.meta.url)'
- )
+ throw new Error('Incorrect usage of isLaunchFile. Use isLaunchFile(import.meta.url)');
}
- const launchFilePath = process.argv[1]
- const moduleFilePath = fileURLToPath(metaUrl)
- return moduleFilePath === launchFilePath
+ const launchFilePath = process.argv[1];
+ const moduleFilePath = fileURLToPath(metaUrl);
+ return moduleFilePath === launchFilePath;
}
diff --git a/special-pages/index.mjs b/special-pages/index.mjs
index eae19ed74..91ac04fb7 100644
--- a/special-pages/index.mjs
+++ b/special-pages/index.mjs
@@ -3,17 +3,17 @@
*
* @module Special Pages
*/
-import { join, relative } from 'node:path'
-import { existsSync, cpSync, rmSync, readFileSync, writeFileSync } from 'node:fs'
-import { buildSync } from 'esbuild'
-import { cwd, parseArgs } from '../scripts/script-utils.js'
-import inliner from 'web-resource-inliner'
+import { join, relative } from 'node:path';
+import { existsSync, cpSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
+import { buildSync } from 'esbuild';
+import { cwd, parseArgs } from '../scripts/script-utils.js';
+import inliner from 'web-resource-inliner';
const CWD = cwd(import.meta.url);
-const ROOT = join(CWD, '../')
-const BUILD = join(ROOT, 'build')
-const APPLE_BUILD = join(ROOT, 'Sources/ContentScopeScripts/dist')
-const args = parseArgs(process.argv.slice(2), [])
+const ROOT = join(CWD, '../');
+const BUILD = join(ROOT, 'build');
+const APPLE_BUILD = join(ROOT, 'Sources/ContentScopeScripts/dist');
+const args = parseArgs(process.argv.slice(2), []);
const NODE_ENV = args.env || 'production';
const DEBUG = Boolean(args.debug);
@@ -23,7 +23,7 @@ export const support = {
integration: ['copy', 'build-js'],
windows: ['copy', 'build-js'],
apple: ['copy', 'build-js', 'inline-html'],
- android: ['copy', 'build-js']
+ android: ['copy', 'build-js'],
},
/** @type {Partial>} */
errorpage: {
@@ -38,7 +38,7 @@ export const support = {
},
/** @type {Partial>} */
example: {
- integration: ['copy', 'build-js']
+ integration: ['copy', 'build-js'],
},
/** @type {Partial>} */
'release-notes': {
@@ -56,67 +56,63 @@ export const support = {
windows: ['copy', 'build-js'],
apple: ['copy', 'build-js'],
},
-}
+};
/** @type {{src: string, dest: string, injectName: string}[]} */
-const copyJobs = []
+const copyJobs = [];
/** @type {{entryPoints: string[], outputDir: string, injectName: string, pageName: string}[]} */
-const buildJobs = []
+const buildJobs = [];
/** @type {{src: string}[]} */
-const inlineJobs = []
-const errors = []
-const DRY_RUN = false
+const inlineJobs = [];
+const errors = [];
+const DRY_RUN = false;
for (const [pageName, injectNames] of Object.entries(support)) {
- const pageSrc = join(CWD, 'pages', pageName, 'src')
+ const pageSrc = join(CWD, 'pages', pageName, 'src');
if (!existsSync(pageSrc)) {
- errors.push(`${pageSrc} does not exist. Each page must have a 'src' directory`)
- continue
+ errors.push(`${pageSrc} does not exist. Each page must have a 'src' directory`);
+ continue;
}
for (const [injectName, jobs] of Object.entries(injectNames)) {
-
// output main dir
- const buildDir = injectName === 'apple'
- ? APPLE_BUILD
- : join(BUILD, injectName)
+ const buildDir = injectName === 'apple' ? APPLE_BUILD : join(BUILD, injectName);
- const pageOutputDirectory = join(buildDir, 'pages', pageName)
+ const pageOutputDirectory = join(buildDir, 'pages', pageName);
for (const job of jobs) {
if (job === 'copy') {
copyJobs.push({
src: pageSrc,
dest: pageOutputDirectory,
- injectName
- })
+ injectName,
+ });
}
if (job === 'build-js') {
- const entryPoints = [
- join(pageSrc, 'js', 'index.js'),
- join(pageSrc, 'js', 'inline.js')
- ].filter(pathname => existsSync(pathname));
- const outputDir = join(pageOutputDirectory, 'js')
+ const entryPoints = [join(pageSrc, 'js', 'index.js'), join(pageSrc, 'js', 'inline.js')].filter((pathname) =>
+ existsSync(pathname),
+ );
+ const outputDir = join(pageOutputDirectory, 'js');
buildJobs.push({
entryPoints,
outputDir,
injectName,
- pageName
- })
+ pageName,
+ });
}
if (job === 'inline-html') {
- const htmlSrc = join(pageOutputDirectory, 'index.html')
- inlineJobs.push({src: htmlSrc})
+ const htmlSrc = join(pageOutputDirectory, 'index.html');
+ inlineJobs.push({ src: htmlSrc });
}
}
}
}
if (copyJobs.length === 0) {
- console.log('⚠️ nothing to copy. This probably means that there isn\'t any pages to release yet.')
+ console.log("⚠️ nothing to copy. This probably means that there isn't any pages to release yet.");
}
if (errors.length > 0) {
- exitWithErrors(errors)
+ exitWithErrors(errors);
}
for (const copyJob of copyJobs) {
@@ -124,18 +120,18 @@ for (const copyJob of copyJobs) {
if (!DRY_RUN) {
rmSync(copyJob.dest, {
force: true,
- recursive: true
- })
+ recursive: true,
+ });
cpSync(copyJob.src, copyJob.dest, {
force: true,
- recursive: true
- })
+ recursive: true,
+ });
}
}
for (const buildJob of buildJobs) {
- if (DEBUG) console.log('BUILD:', buildJob.entryPoints, relative(ROOT, buildJob.outputDir))
- if (DEBUG) console.log('\t- import.meta.env: ', NODE_ENV)
- if (DEBUG) console.log('\t- import.meta.injectName: ', buildJob.injectName)
+ if (DEBUG) console.log('BUILD:', buildJob.entryPoints, relative(ROOT, buildJob.outputDir));
+ if (DEBUG) console.log('\t- import.meta.env: ', NODE_ENV);
+ if (DEBUG) console.log('\t- import.meta.injectName: ', buildJob.injectName);
if (!DRY_RUN) {
buildSync({
entryPoints: buildJob.entryPoints,
@@ -151,30 +147,33 @@ for (const buildJob of buildJobs) {
'.data.svg': 'dataurl',
'.jpg': 'file',
'.png': 'file',
- '.riv': 'file'
+ '.riv': 'file',
},
define: {
'import.meta.env': JSON.stringify(NODE_ENV),
'import.meta.injectName': JSON.stringify(buildJob.injectName),
'import.meta.pageName': JSON.stringify(buildJob.pageName),
},
- dropLabels: buildJob.injectName === "integration" ? [] : ["$INTEGRATION"]
- })
+ dropLabels: buildJob.injectName === 'integration' ? [] : ['$INTEGRATION'],
+ });
}
}
for (const inlineJob of inlineJobs) {
- if (DEBUG) console.log('INLINE:', relative(ROOT, inlineJob.src))
+ if (DEBUG) console.log('INLINE:', relative(ROOT, inlineJob.src));
if (!DRY_RUN) {
- inliner.html({
- fileContent: readFileSync(inlineJob.src, 'utf8'),
- relativeTo: join(inlineJob.src, '..'),
- images: true,
- }, (error, result) => {
- if (error) {
- return exitWithErrors([error])
- }
- writeFileSync(inlineJob.src, result)
- })
+ inliner.html(
+ {
+ fileContent: readFileSync(inlineJob.src, 'utf8'),
+ relativeTo: join(inlineJob.src, '..'),
+ images: true,
+ },
+ (error, result) => {
+ if (error) {
+ return exitWithErrors([error]);
+ }
+ writeFileSync(inlineJob.src, result);
+ },
+ );
}
}
@@ -183,7 +182,7 @@ for (const inlineJob of inlineJobs) {
*/
function exitWithErrors(errors) {
for (const error of errors) {
- console.log(error)
+ console.log(error);
}
- process.exit(1)
+ process.exit(1);
}
diff --git a/special-pages/messages/new-tab/examples/rmf.js b/special-pages/messages/new-tab/examples/rmf.js
index a2cd030b6..73885cff1 100644
--- a/special-pages/messages/new-tab/examples/rmf.js
+++ b/special-pages/messages/new-tab/examples/rmf.js
@@ -2,52 +2,52 @@
* @type {import("../../../types/new-tab").RMFData}
*/
const rmfDataSmallMsg = {
- "content": {
- "messageType": "small",
- "id": "id-1",
- "titleText": "Tell Us Your Thoughts on Privacy Pro",
- "descriptionText": "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
+ content: {
+ messageType: 'small',
+ id: 'id-1',
+ titleText: 'Tell Us Your Thoughts on Privacy Pro',
+ descriptionText: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
},
-}
+};
/**
* @type {import("../../../types/new-tab").RMFData}
*/
const rmfDataMediumMsg = {
- "content": {
- messageType: 'medium',
- id: 'id-2',
- icon: 'DDGAnnounce',
- titleText: 'New Search Feature!',
- descriptionText: 'DuckDuckGo now offers Instant Answers for quicker access to the information you need.'
- },
-}
+ content: {
+ messageType: 'medium',
+ id: 'id-2',
+ icon: 'DDGAnnounce',
+ titleText: 'New Search Feature!',
+ descriptionText: 'DuckDuckGo now offers Instant Answers for quicker access to the information you need.',
+ },
+};
/**
* @type {import("../../../types/new-tab").RMFData}
*/
const rmfDataBigSingleActionMsg = {
- "content": {
+ content: {
messageType: 'big_single_action',
id: 'id-big-single',
titleText: 'Tell Us Your Thoughts on Privacy Pro',
descriptionText: 'Take our short anonymous survey and share your feedback.',
icon: 'PrivacyPro',
- primaryActionText: 'Take Survey'
- }
-}
+ primaryActionText: 'Take Survey',
+ },
+};
/**
* @type {import("../../../types/new-tab").RMFData}
*/
const rmfDataBigTwoActionMsg = {
- "content": {
+ content: {
messageType: 'big_two_action',
id: 'id-big-two',
titleText: 'Tell Us Your Thoughts on Privacy Pro',
descriptionText: 'Take our short anonymous survey and share your feedback.',
icon: 'Announce',
primaryActionText: 'Take Survey',
- secondaryActionText: 'Remind me'
- }
-}
\ No newline at end of file
+ secondaryActionText: 'Remind me',
+ },
+};
diff --git a/special-pages/messages/new-tab/examples/stats.js b/special-pages/messages/new-tab/examples/stats.js
index f6ec1244b..5cfa39409 100644
--- a/special-pages/messages/new-tab/examples/stats.js
+++ b/special-pages/messages/new-tab/examples/stats.js
@@ -2,29 +2,28 @@
* @type {import("../../../types/new-tab").PrivacyStatsData}
*/
const privacyStatsData = {
- "totalCount": 12345,
- "trackerCompanies": [
- { "displayName": "Tracker Co. A", "count": 1234 },
- { "displayName": "Tracker Co. B", "count": 5678 },
- { "displayName": "Tracker Co. C", "count": 91011 }
- ]
-}
+ totalCount: 12345,
+ trackerCompanies: [
+ { displayName: 'Tracker Co. A', count: 1234 },
+ { displayName: 'Tracker Co. B', count: 5678 },
+ { displayName: 'Tracker Co. C', count: 91011 },
+ ],
+};
/**
* @type {import("../../../types/new-tab").StatsConfig}
*/
const minimumConfig = {
- expansion: "expanded",
- animation: { kind: "none" }
-}
+ expansion: 'expanded',
+ animation: { kind: 'none' },
+};
/**
* @type {import("../../../types/new-tab").StatsConfig}
*/
const withAnimation = {
- expansion: "expanded",
- animation: { kind: "view-transitions" }
-}
-
-export {}
+ expansion: 'expanded',
+ animation: { kind: 'view-transitions' },
+};
+export {};
diff --git a/special-pages/messages/new-tab/examples/widgets.js b/special-pages/messages/new-tab/examples/widgets.js
index 2ec7a1daa..5ea90b76d 100644
--- a/special-pages/messages/new-tab/examples/widgets.js
+++ b/special-pages/messages/new-tab/examples/widgets.js
@@ -1,31 +1,28 @@
/**
* @type {import("../../../types/new-tab").Widgets}
*/
-const widgets = [
- { "id": "weatherWidget" },
- { "id": "newsWidget" }
-]
+const widgets = [{ id: 'weatherWidget' }, { id: 'newsWidget' }];
/**
* @type {import("../../../types/new-tab").WidgetListItem}
*/
-const widget = { "id": "newsWidget" }
+const widget = { id: 'newsWidget' };
/**
* @type {import("../../../types/new-tab").WidgetConfigs}
*/
const widgetConfigs = [
- { "id": "weatherWidget", "visibility": "visible" },
- { "id": "newsWidget", "visibility": "visible" }
-]
+ { id: 'weatherWidget', visibility: 'visible' },
+ { id: 'newsWidget', visibility: 'visible' },
+];
/**
* @type {import("../../../types/new-tab").WidgetConfigItem}
*/
const widgetConfig = {
- "id": "weatherWidget",
- "visibility": "visible"
-}
+ id: 'weatherWidget',
+ visibility: 'visible',
+};
/**
* Widgets + WidgetConfigs when delivered in first payload...
@@ -33,20 +30,15 @@ const widgetConfig = {
* @type {import("../../../types/new-tab").InitialSetupResponse}
*/
const initialSetupResponse = {
- widgets: [
- { "id": "updateNotification" },
- { "id": "rmf" },
- { "id": "favorites" },
- { "id": "privacyStats" }
- ],
+ widgets: [{ id: 'updateNotification' }, { id: 'rmf' }, { id: 'favorites' }, { id: 'privacyStats' }],
widgetConfigs: [
- { "id": "favorites", "visibility": "visible" },
- { "id": "privacyStats", "visibility": "visible" }
+ { id: 'favorites', visibility: 'visible' },
+ { id: 'privacyStats', visibility: 'visible' },
],
env: 'production',
locale: 'en',
platform: { name: 'windows' },
- updateNotification: { content: null }
-}
+ updateNotification: { content: null },
+};
-export {}
+export {};
diff --git a/special-pages/pages/duckplayer/app/components/Background.jsx b/special-pages/pages/duckplayer/app/components/Background.jsx
index 76f7bf242..d09468336 100644
--- a/special-pages/pages/duckplayer/app/components/Background.jsx
+++ b/special-pages/pages/duckplayer/app/components/Background.jsx
@@ -1,8 +1,6 @@
-import { h } from "preact";
-import styles from "./Background.module.css";
+import { h } from 'preact';
+import styles from './Background.module.css';
export function Background() {
- return (
-
- )
+ return ;
}
diff --git a/special-pages/pages/duckplayer/app/components/Button.jsx b/special-pages/pages/duckplayer/app/components/Button.jsx
index 3099e5a58..766d0e5d1 100644
--- a/special-pages/pages/duckplayer/app/components/Button.jsx
+++ b/special-pages/pages/duckplayer/app/components/Button.jsx
@@ -1,6 +1,6 @@
-import {h} from "preact"
-import cn from "classnames"
-import styles from "./Button.module.css";
+import { h } from 'preact';
+import cn from 'classnames';
+import styles from './Button.module.css';
/**
*
@@ -12,30 +12,19 @@ import styles from "./Button.module.css";
* @param {boolean} [props.highlight]
* @param {import("preact").ComponentProps<"button">} [props.buttonProps]
*/
-export function Button({
- children,
- formfactor = "mobile",
- icon = false,
- fill = false,
- highlight = false,
- buttonProps = {}
- }) {
+export function Button({ children, formfactor = 'mobile', icon = false, fill = false, highlight = false, buttonProps = {} }) {
const classes = cn({
[styles.button]: true,
- [styles.desktop]: formfactor === "desktop",
+ [styles.desktop]: formfactor === 'desktop',
[styles.highlight]: highlight === true,
[styles.fill]: fill === true,
[styles.iconOnly]: icon === true,
- })
+ });
return (
-