Skip to content

Commit 1dc1b6a

Browse files
committed
chore: merge main
2 parents d28b97e + a30e237 commit 1dc1b6a

File tree

66 files changed

+2994
-701
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2994
-701
lines changed

.github/workflows/auto-respond-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
direction: last
5959

6060
- name: Create Comment Body
61-
uses: actions/github-script@v7
61+
uses: actions/github-script@v8
6262
id: create_body
6363
with:
6464
github-token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/build-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
direction: last
5858

5959
- name: Create Comment Body
60-
uses: actions/github-script@v7
60+
uses: actions/github-script@v8
6161
id: create_body
6262
with:
6363
github-token: ${{ secrets.GITHUB_TOKEN }}

.swift-version

Lines changed: 0 additions & 1 deletion
This file was deleted.

injected/docs/adding-bundles.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Adding a New Bundle
2+
3+
Bundles are how platforms integrate content-scope-scripts, they're often used within a context and so serve a distinct purpose. There is a cost to serving multiple bundles within the web page context so that should be avoided.
4+
5+
To add a new bundle to the Content Scope Scripts build system:
6+
7+
## 1. Define Build Configuration
8+
9+
**File**: `injected/scripts/entry-points.js`
10+
11+
Add your bundle to the `builds` object:
12+
13+
```js
14+
'my-new-bundle': {
15+
input: 'entry-points/my-entry.js',
16+
output: ['../build/my-platform/myScript.js'],
17+
},
18+
```
19+
20+
## 2. Update TypeScript Types
21+
22+
**File**: `injected/src/globals.d.ts`
23+
24+
Add the bundle name to the `injectName` union type:
25+
26+
```ts
27+
injectName?:
28+
| 'firefox'
29+
| 'apple'
30+
| 'my-new-bundle' // Add here
31+
```
32+
33+
## 3. Configure Platform Features (Optional)
34+
35+
**File**: `injected/src/features.js`
36+
37+
If creating a platform-specific bundle, add feature configuration:
38+
39+
```js
40+
'my-platform': [
41+
'cookie',
42+
...baseFeatures,
43+
'mySpecificFeature',
44+
],
45+
```
46+
47+
## Optional: 4. Create Entry Point
48+
49+
**Directory**: `injected/entry-points/`
50+
51+
Create your entry point file (e.g., `my-entry.js`) that imports and configures the required features for your bundle.
52+
53+
**Entry points** are the main files that define the implementation of a build. These should only be added if absolutely required.
54+
55+
## Remote configuration
56+
57+
Inject Name is a condition that can then be used in the config.
58+
59+
**Example**: Target specific bundles in feature configuration:
60+
61+
```json
62+
{
63+
"features": {
64+
"myFeature": {
65+
"state": "enabled",
66+
"settings": {
67+
"something": "hello",
68+
"conditionalChanges": [
69+
{
70+
"condition": {
71+
"injectName": "android-adsjs"
72+
},
73+
"patchSettings": [
74+
{
75+
"op": "replace",
76+
"path": "/something",
77+
"value": "else"
78+
}
79+
]
80+
}
81+
]
82+
}
83+
}
84+
}
85+
}
86+
```
87+
88+
This allows the same feature to have different behavior depending on which bundle it's running in.

injected/docs/platform-integration.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ The following placeholders are replaced during the build process:
6464
- **`$CONTENT_SCOPE$`** - Raw remote config object
6565
- **`$USER_UNPROTECTED_DOMAINS$`** - An array of user allowlisted domains
6666
- **`$USER_PREFERENCES$`** - An object containing:
67-
- `platform`: `{ name: '<ios | macos | extension | android | windows>', internal: <boolean> }`
67+
- `platform`:
68+
- `name`: '<ios | macos | extension | android | windows>',
69+
- `internal`: <boolean>
70+
- `version`: <string | number>
6871
- `debug`: boolean
6972
- `globalPrivacyControlValue`: boolean
7073
- `sessionKey`: `<CSRNG UUID 4 string>` (used for fingerprinting) - this should regenerate on browser close or every 24 hours

injected/entry-points/android-adsjs.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,54 @@
11
/**
22
* @module Android AdsJS integration
33
*/
4-
import { load, init } from '../src/content-scope-features.js';
5-
import { processConfig } from './../src/utils';
6-
import { AndroidAdsjsMessagingConfig } from '../../messaging/index.js';
4+
import { load, init, updateFeatureArgs } from '../src/content-scope-features.js';
5+
import { processConfig, isBeingFramed } from './../src/utils';
6+
import { AndroidAdsjsMessagingConfig, MessagingContext, Messaging } from '../../messaging/index.js';
7+
8+
/**
9+
* Send initial ping once per frame to establish communication with the platform.
10+
* This replaces the per-feature ping that was previously sent in AndroidAdsjsMessagingTransport.
11+
* When response is received, updates all loaded feature configurations.
12+
*
13+
* @param {AndroidAdsjsMessagingConfig} messagingConfig
14+
* @param {object} processedConfig - The base configuration
15+
*/
16+
async function sendInitialPingAndUpdate(messagingConfig, processedConfig) {
17+
// Only send ping in top context, not in frames
18+
if (isBeingFramed()) {
19+
return;
20+
}
21+
22+
try {
23+
// Create messaging context for the initial ping
24+
const messagingContext = new MessagingContext({
25+
context: 'contentScopeScripts',
26+
env: processedConfig.debug ? 'development' : 'production',
27+
featureName: 'messaging',
28+
});
29+
30+
// Create messaging instance - handles all the subscription/error boilerplate
31+
const messaging = new Messaging(messagingContext, messagingConfig);
32+
33+
if (processedConfig.debug) {
34+
console.log('AndroidAdsjs: Sending initial ping...');
35+
}
36+
37+
// Send the ping request
38+
const response = await messaging.request('initialPing', {});
39+
40+
// Update all loaded features with merged configuration
41+
if (response && typeof response === 'object') {
42+
const updatedConfig = { ...processedConfig, ...response };
43+
44+
await updateFeatureArgs(updatedConfig);
45+
}
46+
} catch (error) {
47+
if (processedConfig.debug) {
48+
console.error('AndroidAdsjs: Initial ping failed:', error);
49+
}
50+
}
51+
}
752

853
function initCode() {
954
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
@@ -24,6 +69,10 @@ function initCode() {
2469
debug: processedConfig.debug,
2570
});
2671

72+
// Send initial ping asynchronously to update feature configurations when response arrives
73+
sendInitialPingAndUpdate(processedConfig.messagingConfig, processedConfig);
74+
75+
// Load and init features immediately with base configuration
2776
load({
2877
platform: processedConfig.platform,
2978
site: processedConfig.site,

injected/entry-points/integration.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { load, init } from '../src/content-scope-features.js';
1+
import { load, init, updateFeatureArgs } from '../src/content-scope-features.js';
22
import { TestTransportConfig } from '../../messaging/index.js';
33
import { getTabUrl } from '../src/utils.js';
44

@@ -120,6 +120,7 @@ async function initCode() {
120120
window.__testContentScopeArgs = merged;
121121
// init features
122122
await init(merged);
123+
await updateFeatureArgs(merged);
123124

124125
// set status to initialized so that tests can resume
125126
setStatus('initialized');

injected/integration-test/web-compat.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,26 @@ test.describe('Ensure Notification interface is injected', () => {
130130
return window.Notification.maxActions;
131131
});
132132
expect(maxActionsPropDenied).toEqual(2);
133+
134+
const notificationToString = await page.evaluate(() => {
135+
return window.Notification.toString();
136+
});
137+
expect(notificationToString).toEqual('function Notification() { [native code] }');
138+
139+
const requestPermissionToString = await page.evaluate(() => {
140+
return window.Notification.requestPermission.toString();
141+
});
142+
expect(requestPermissionToString).toEqual('function requestPermission() { [native code] }');
143+
144+
const notificationToStringToString = await page.evaluate(() => {
145+
return window.Notification.toString.toString();
146+
});
147+
expect(notificationToStringToString).toEqual('function toString() { [native code] }');
148+
149+
const requestPermissionToStringToString = await page.evaluate(() => {
150+
return window.Notification.requestPermission.toString.toString();
151+
});
152+
expect(requestPermissionToStringToString).toEqual('function toString() { [native code] }');
133153
});
134154
});
135155

injected/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@
3838
"@canvas/image-data": "^1.0.0",
3939
"@fingerprintjs/fingerprintjs": "^4.6.2",
4040
"@types/chrome": "^0.1.1",
41-
"@types/jasmine": "^5.1.8",
41+
"@types/jasmine": "^5.1.9",
4242
"@types/node": "^24.1.0",
43-
"@typescript-eslint/eslint-plugin": "^8.41.0",
43+
"@typescript-eslint/eslint-plugin": "^8.42.0",
4444
"fast-check": "^4.2.0",
45-
"jasmine": "^5.9.0",
45+
"jasmine": "^5.10.0",
4646
"web-ext": "^8.9.0"
4747
}
4848
}

injected/src/content-feature.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export default class ContentFeature extends ConfigFeature {
4242
*/
4343
listenForUpdateChanges = false;
4444

45+
/**
46+
* Set this to true if you wish to receive configuration updates from initial ping responses (Android only).
47+
* @type {boolean}
48+
*/
49+
listenForConfigUpdates = false;
50+
4551
/** @type {ImportMeta} */
4652
#importConfig;
4753

@@ -56,6 +62,40 @@ export default class ContentFeature extends ConfigFeature {
5662
return this.args?.debug || false;
5763
}
5864

65+
get shouldLog() {
66+
return this.isDebug;
67+
}
68+
69+
/**
70+
* Logging utility for this feature (Stolen some inspo from DuckPlayer logger, will unify in the future)
71+
*/
72+
get log() {
73+
const shouldLog = this.shouldLog;
74+
const prefix = `${this.name.padEnd(20, ' ')} |`;
75+
76+
return {
77+
// These are getters to have the call site be the reported line number.
78+
get info() {
79+
if (!shouldLog) {
80+
return () => {};
81+
}
82+
return console.log.bind(console, prefix);
83+
},
84+
get warn() {
85+
if (!shouldLog) {
86+
return () => {};
87+
}
88+
return console.warn.bind(console, prefix);
89+
},
90+
get error() {
91+
if (!shouldLog) {
92+
return () => {};
93+
}
94+
return console.error.bind(console, prefix);
95+
},
96+
};
97+
}
98+
5999
get desktopModeEnabled() {
60100
return this.args?.desktopModeEnabled || false;
61101
}
@@ -223,6 +263,17 @@ export default class ContentFeature extends ConfigFeature {
223263
*/
224264
update() {}
225265

266+
/**
267+
* Called when user preferences are merged from initial ping response. (Android only)
268+
* Override this method in your feature to handle user preference updates.
269+
* This only happens once during initialization when the platform responds with user-specific settings.
270+
* @param {object} _updatedConfig - The configuration with merged user preferences
271+
*/
272+
onUserPreferencesMerged(_updatedConfig) {
273+
// Default implementation does nothing
274+
// Features can override this to handle user preference updates
275+
}
276+
226277
/**
227278
* Register a flag that will be added to page breakage reports
228279
*/

0 commit comments

Comments
 (0)