|
197 | 197 | switch (configSettingType) {
|
198 | 198 | case "object":
|
199 | 199 | if (Array.isArray(configSetting)) {
|
200 |
| - configSetting = processAttrByCriteria(configSetting); |
201 |
| - if (configSetting === void 0) { |
| 200 | + const selectedSetting = processAttrByCriteria(configSetting); |
| 201 | + if (selectedSetting === void 0) { |
202 | 202 | return defaultValue;
|
203 | 203 | }
|
| 204 | + return processAttr(selectedSetting, defaultValue); |
204 | 205 | }
|
205 | 206 | if (!configSetting.type) {
|
206 | 207 | return defaultValue;
|
|
209 | 210 | if (configSetting.functionName && functionMap[configSetting.functionName]) {
|
210 | 211 | return functionMap[configSetting.functionName];
|
211 | 212 | }
|
| 213 | + if (configSetting.functionValue) { |
| 214 | + const functionValue = configSetting.functionValue; |
| 215 | + return () => processAttr(functionValue, void 0); |
| 216 | + } |
212 | 217 | }
|
213 | 218 | if (configSetting.type === "undefined") {
|
214 | 219 | return void 0;
|
215 | 220 | }
|
| 221 | + if (configSetting.async) { |
| 222 | + return DDGPromise.resolve(configSetting.value); |
| 223 | + } |
216 | 224 | return configSetting.value;
|
217 | 225 | default:
|
218 | 226 | return defaultValue;
|
|
386 | 394 | }
|
387 | 395 | return false;
|
388 | 396 | }
|
| 397 | + function isMaxSupportedVersion(maxSupportedVersion, currentVersion) { |
| 398 | + if (typeof currentVersion === "string" && typeof maxSupportedVersion === "string") { |
| 399 | + if (satisfiesMinVersion(currentVersion, maxSupportedVersion)) { |
| 400 | + return true; |
| 401 | + } |
| 402 | + } else if (typeof currentVersion === "number" && typeof maxSupportedVersion === "number") { |
| 403 | + if (maxSupportedVersion >= currentVersion) { |
| 404 | + return true; |
| 405 | + } |
| 406 | + } |
| 407 | + return false; |
| 408 | + } |
389 | 409 | function processConfig(data, userList, preferences, platformSpecificFeatures2 = []) {
|
390 | 410 | const topLevelHostname = getTabHostname();
|
391 | 411 | const site = computeLimitedSiteObject();
|
|
530 | 550 | android: [...baseFeatures, "webCompat", "breakageReporting", "duckPlayer", "messageBridge"],
|
531 | 551 | "android-broker-protection": ["brokerProtection"],
|
532 | 552 | "android-autofill-password-import": ["autofillPasswordImport"],
|
| 553 | + "android-adsjs": [ |
| 554 | + "apiManipulation", |
| 555 | + "webCompat", |
| 556 | + "fingerprintingHardware", |
| 557 | + "fingerprintingScreenSize", |
| 558 | + "fingerprintingTemporaryStorage", |
| 559 | + "fingerprintingAudio", |
| 560 | + "fingerprintingBattery", |
| 561 | + "gpc", |
| 562 | + "breakageReporting" |
| 563 | + ], |
533 | 564 | windows: [
|
534 | 565 | "cookie",
|
535 | 566 | ...baseFeatures,
|
|
1524 | 1555 | }
|
1525 | 1556 | };
|
1526 | 1557 |
|
| 1558 | + // ../messaging/lib/android-adsjs.js |
| 1559 | + var AndroidAdsjsMessagingTransport = class { |
| 1560 | + /** |
| 1561 | + * @param {AndroidAdsjsMessagingConfig} config |
| 1562 | + * @param {MessagingContext} messagingContext |
| 1563 | + * @internal |
| 1564 | + */ |
| 1565 | + constructor(config, messagingContext) { |
| 1566 | + this.messagingContext = messagingContext; |
| 1567 | + this.config = config; |
| 1568 | + this.config.sendInitialPing(messagingContext); |
| 1569 | + } |
| 1570 | + /** |
| 1571 | + * @param {NotificationMessage} msg |
| 1572 | + */ |
| 1573 | + notify(msg) { |
| 1574 | + try { |
| 1575 | + this.config.sendMessageThrows?.(msg); |
| 1576 | + } catch (e) { |
| 1577 | + console.error(".notify failed", e); |
| 1578 | + } |
| 1579 | + } |
| 1580 | + /** |
| 1581 | + * @param {RequestMessage} msg |
| 1582 | + * @return {Promise<any>} |
| 1583 | + */ |
| 1584 | + request(msg) { |
| 1585 | + return new Promise((resolve, reject) => { |
| 1586 | + const unsub = this.config.subscribe(msg.id, handler); |
| 1587 | + try { |
| 1588 | + this.config.sendMessageThrows?.(msg); |
| 1589 | + } catch (e) { |
| 1590 | + unsub(); |
| 1591 | + reject(new Error("request failed to send: " + e.message || "unknown error")); |
| 1592 | + } |
| 1593 | + function handler(data) { |
| 1594 | + if (isResponseFor(msg, data)) { |
| 1595 | + if (data.result) { |
| 1596 | + resolve(data.result || {}); |
| 1597 | + return unsub(); |
| 1598 | + } |
| 1599 | + if (data.error) { |
| 1600 | + reject(new Error(data.error.message)); |
| 1601 | + return unsub(); |
| 1602 | + } |
| 1603 | + unsub(); |
| 1604 | + throw new Error("unreachable: must have `result` or `error` key by this point"); |
| 1605 | + } |
| 1606 | + } |
| 1607 | + }); |
| 1608 | + } |
| 1609 | + /** |
| 1610 | + * @param {Subscription} msg |
| 1611 | + * @param {(value: unknown | undefined) => void} callback |
| 1612 | + */ |
| 1613 | + subscribe(msg, callback) { |
| 1614 | + const unsub = this.config.subscribe(msg.subscriptionName, (data) => { |
| 1615 | + if (isSubscriptionEventFor(msg, data)) { |
| 1616 | + callback(data.params || {}); |
| 1617 | + } |
| 1618 | + }); |
| 1619 | + return () => { |
| 1620 | + unsub(); |
| 1621 | + }; |
| 1622 | + } |
| 1623 | + }; |
| 1624 | + var AndroidAdsjsMessagingConfig = class { |
| 1625 | + /** |
| 1626 | + * @param {object} params |
| 1627 | + * @param {Record<string, any>} params.target |
| 1628 | + * @param {boolean} params.debug |
| 1629 | + * @param {string} params.objectName - the object name for addWebMessageListener |
| 1630 | + */ |
| 1631 | + constructor(params) { |
| 1632 | + /** @type {{ |
| 1633 | + * postMessage: (message: string) => void, |
| 1634 | + * addEventListener: (type: string, listener: (event: MessageEvent) => void) => void, |
| 1635 | + * } | null} */ |
| 1636 | + __publicField(this, "_capturedHandler"); |
| 1637 | + this.target = params.target; |
| 1638 | + this.debug = params.debug; |
| 1639 | + this.objectName = params.objectName; |
| 1640 | + this.listeners = new globalThis.Map(); |
| 1641 | + this._captureGlobalHandler(); |
| 1642 | + this._setupEventListener(); |
| 1643 | + } |
| 1644 | + /** |
| 1645 | + * The transport can call this to transmit a JSON payload along with a secret |
| 1646 | + * to the native Android handler via postMessage. |
| 1647 | + * |
| 1648 | + * Note: This can throw - it's up to the transport to handle the error. |
| 1649 | + * |
| 1650 | + * @type {(json: object) => void} |
| 1651 | + * @throws |
| 1652 | + * @internal |
| 1653 | + */ |
| 1654 | + sendMessageThrows(message) { |
| 1655 | + if (!this.objectName) { |
| 1656 | + throw new Error("Object name not set for WebMessageListener"); |
| 1657 | + } |
| 1658 | + if (this._capturedHandler && this._capturedHandler.postMessage) { |
| 1659 | + this._capturedHandler.postMessage(JSON.stringify(message)); |
| 1660 | + } else { |
| 1661 | + throw new Error("postMessage not available"); |
| 1662 | + } |
| 1663 | + } |
| 1664 | + /** |
| 1665 | + * A subscription on Android is just a named listener. All messages from |
| 1666 | + * android -> are delivered through a single function, and this mapping is used |
| 1667 | + * to route the messages to the correct listener. |
| 1668 | + * |
| 1669 | + * Note: Use this to implement request->response by unsubscribing after the first |
| 1670 | + * response. |
| 1671 | + * |
| 1672 | + * @param {string} id |
| 1673 | + * @param {(msg: MessageResponse | SubscriptionEvent) => void} callback |
| 1674 | + * @returns {() => void} |
| 1675 | + * @internal |
| 1676 | + */ |
| 1677 | + subscribe(id, callback) { |
| 1678 | + this.listeners.set(id, callback); |
| 1679 | + return () => { |
| 1680 | + this.listeners.delete(id); |
| 1681 | + }; |
| 1682 | + } |
| 1683 | + /** |
| 1684 | + * Accept incoming messages and try to deliver it to a registered listener. |
| 1685 | + * |
| 1686 | + * This code is defensive to prevent any single handler from affecting another if |
| 1687 | + * it throws (producer interference). |
| 1688 | + * |
| 1689 | + * @param {MessageResponse | SubscriptionEvent} payload |
| 1690 | + * @internal |
| 1691 | + */ |
| 1692 | + _dispatch(payload) { |
| 1693 | + if (!payload) return this._log("no response"); |
| 1694 | + if ("id" in payload) { |
| 1695 | + if (this.listeners.has(payload.id)) { |
| 1696 | + this._tryCatch(() => this.listeners.get(payload.id)?.(payload)); |
| 1697 | + } else { |
| 1698 | + this._log("no listeners for ", payload); |
| 1699 | + } |
| 1700 | + } |
| 1701 | + if ("subscriptionName" in payload) { |
| 1702 | + if (this.listeners.has(payload.subscriptionName)) { |
| 1703 | + this._tryCatch(() => this.listeners.get(payload.subscriptionName)?.(payload)); |
| 1704 | + } else { |
| 1705 | + this._log("no subscription listeners for ", payload); |
| 1706 | + } |
| 1707 | + } |
| 1708 | + } |
| 1709 | + /** |
| 1710 | + * |
| 1711 | + * @param {(...args: any[]) => any} fn |
| 1712 | + * @param {string} [context] |
| 1713 | + */ |
| 1714 | + _tryCatch(fn, context = "none") { |
| 1715 | + try { |
| 1716 | + return fn(); |
| 1717 | + } catch (e) { |
| 1718 | + if (this.debug) { |
| 1719 | + console.error("AndroidAdsjsMessagingConfig error:", context); |
| 1720 | + console.error(e); |
| 1721 | + } |
| 1722 | + } |
| 1723 | + } |
| 1724 | + /** |
| 1725 | + * @param {...any} args |
| 1726 | + */ |
| 1727 | + _log(...args) { |
| 1728 | + if (this.debug) { |
| 1729 | + console.log("AndroidAdsjsMessagingConfig", ...args); |
| 1730 | + } |
| 1731 | + } |
| 1732 | + /** |
| 1733 | + * Capture the global handler and remove it from the global object. |
| 1734 | + */ |
| 1735 | + _captureGlobalHandler() { |
| 1736 | + const { target, objectName } = this; |
| 1737 | + if (Object.prototype.hasOwnProperty.call(target, objectName)) { |
| 1738 | + this._capturedHandler = target[objectName]; |
| 1739 | + delete target[objectName]; |
| 1740 | + } else { |
| 1741 | + this._capturedHandler = null; |
| 1742 | + this._log("Android adsjs messaging interface not available", objectName); |
| 1743 | + } |
| 1744 | + } |
| 1745 | + /** |
| 1746 | + * Set up event listener for incoming messages from the captured handler. |
| 1747 | + */ |
| 1748 | + _setupEventListener() { |
| 1749 | + if (!this._capturedHandler || !this._capturedHandler.addEventListener) { |
| 1750 | + this._log("No event listener support available"); |
| 1751 | + return; |
| 1752 | + } |
| 1753 | + this._capturedHandler.addEventListener("message", (event) => { |
| 1754 | + try { |
| 1755 | + const data = ( |
| 1756 | + /** @type {MessageEvent} */ |
| 1757 | + event.data |
| 1758 | + ); |
| 1759 | + if (typeof data === "string") { |
| 1760 | + const parsedData = JSON.parse(data); |
| 1761 | + this._dispatch(parsedData); |
| 1762 | + } |
| 1763 | + } catch (e) { |
| 1764 | + this._log("Error processing incoming message:", e); |
| 1765 | + } |
| 1766 | + }); |
| 1767 | + } |
| 1768 | + /** |
| 1769 | + * Send an initial ping message to the platform to establish communication. |
| 1770 | + * This is a fire-and-forget notification that signals the JavaScript side is ready. |
| 1771 | + * Only sends in top context (not in frames) and if the messaging interface is available. |
| 1772 | + * |
| 1773 | + * @param {MessagingContext} messagingContext |
| 1774 | + * @returns {boolean} true if ping was sent, false if in frame or interface not ready |
| 1775 | + */ |
| 1776 | + sendInitialPing(messagingContext) { |
| 1777 | + if (isBeingFramed()) { |
| 1778 | + this._log("Skipping initial ping - running in frame context"); |
| 1779 | + return false; |
| 1780 | + } |
| 1781 | + try { |
| 1782 | + const message = new RequestMessage({ |
| 1783 | + id: "initialPing", |
| 1784 | + context: messagingContext.context, |
| 1785 | + featureName: "messaging", |
| 1786 | + method: "initialPing" |
| 1787 | + }); |
| 1788 | + this.sendMessageThrows(message); |
| 1789 | + this._log("Initial ping sent successfully"); |
| 1790 | + return true; |
| 1791 | + } catch (e) { |
| 1792 | + this._log("Failed to send initial ping:", e); |
| 1793 | + return false; |
| 1794 | + } |
| 1795 | + } |
| 1796 | + }; |
| 1797 | + |
1527 | 1798 | // ../messaging/index.js
|
1528 | 1799 | var MessagingContext = class {
|
1529 | 1800 | /**
|
|
1652 | 1923 | if (config instanceof AndroidMessagingConfig) {
|
1653 | 1924 | return new AndroidMessagingTransport(config, messagingContext);
|
1654 | 1925 | }
|
| 1926 | + if (config instanceof AndroidAdsjsMessagingConfig) { |
| 1927 | + return new AndroidAdsjsMessagingTransport(config, messagingContext); |
| 1928 | + } |
1655 | 1929 | if (config instanceof TestTransportConfig) {
|
1656 | 1930 | return new TestTransport(config, messagingContext);
|
1657 | 1931 | }
|
|
2926 | 3200 | * @property {string[] | string} [domain]
|
2927 | 3201 | * @property {object} [urlPattern]
|
2928 | 3202 | * @property {object} [minSupportedVersion]
|
| 3203 | + * @property {object} [maxSupportedVersion] |
2929 | 3204 | * @property {object} [experiment]
|
2930 | 3205 | * @property {string} [experiment.experimentName]
|
2931 | 3206 | * @property {string} [experiment.cohort]
|
2932 | 3207 | * @property {object} [context]
|
2933 | 3208 | * @property {boolean} [context.frame] - true if the condition applies to frames
|
2934 | 3209 | * @property {boolean} [context.top] - true if the condition applies to the top frame
|
2935 | 3210 | * @property {string} [injectName] - the inject name to match against (e.g., "apple-isolated")
|
| 3211 | + * @property {boolean} [internal] - true if the condition applies to internal builds |
2936 | 3212 | */
|
2937 | 3213 | /**
|
2938 | 3214 | * Takes multiple conditional blocks and returns true if any apply.
|
|
2958 | 3234 | urlPattern: this._matchUrlPatternConditional,
|
2959 | 3235 | experiment: this._matchExperimentConditional,
|
2960 | 3236 | minSupportedVersion: this._matchMinSupportedVersion,
|
2961 |
| - injectName: this._matchInjectNameConditional |
| 3237 | + maxSupportedVersion: this._matchMaxSupportedVersion, |
| 3238 | + injectName: this._matchInjectNameConditional, |
| 3239 | + internal: this._matchInternalConditional |
2962 | 3240 | };
|
2963 | 3241 | for (const key in conditionBlock) {
|
2964 | 3242 | if (!conditionChecks[key]) {
|
|
3049 | 3327 | if (!currentInjectName) return false;
|
3050 | 3328 | return conditionBlock.injectName === currentInjectName;
|
3051 | 3329 | }
|
| 3330 | + /** |
| 3331 | + * Takes a condition block and returns true if the internal state matches the condition. |
| 3332 | + * @param {ConditionBlock} conditionBlock |
| 3333 | + * @returns {boolean} |
| 3334 | + */ |
| 3335 | + _matchInternalConditional(conditionBlock) { |
| 3336 | + if (conditionBlock.internal === void 0) return false; |
| 3337 | + const isInternal = __privateGet(this, _args)?.platform?.internal; |
| 3338 | + if (isInternal === void 0) return false; |
| 3339 | + return Boolean(conditionBlock.internal) === Boolean(isInternal); |
| 3340 | + } |
3052 | 3341 | /**
|
3053 | 3342 | * Takes a condition block and returns true if the platform version satisfies the `minSupportedFeature`
|
3054 | 3343 | * @param {ConditionBlock} conditionBlock
|
|
3058 | 3347 | if (!conditionBlock.minSupportedVersion) return false;
|
3059 | 3348 | return isSupportedVersion(conditionBlock.minSupportedVersion, __privateGet(this, _args)?.platform?.version);
|
3060 | 3349 | }
|
| 3350 | + /** |
| 3351 | + * Takes a condition block and returns true if the platform version satisfies the `maxSupportedFeature` |
| 3352 | + * @param {ConditionBlock} conditionBlock |
| 3353 | + * @returns {boolean} |
| 3354 | + */ |
| 3355 | + _matchMaxSupportedVersion(conditionBlock) { |
| 3356 | + if (!conditionBlock.maxSupportedVersion) return false; |
| 3357 | + return isMaxSupportedVersion(conditionBlock.maxSupportedVersion, __privateGet(this, _args)?.platform?.version); |
| 3358 | + } |
3061 | 3359 | /**
|
3062 | 3360 | * Return the settings object for a feature
|
3063 | 3361 | * @param {string} [featureName] - The name of the feature to get the settings for; defaults to the name of the feature
|
|
0 commit comments