Skip to content

Commit fd50546

Browse files
authored
Merge branch 'main' into randerson/fix-macos-26-visual-artifacts
2 parents fdee967 + a30e237 commit fd50546

File tree

4 files changed

+191
-71
lines changed

4 files changed

+191
-71
lines changed

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/src/features/duck-ai-listener.js

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,8 @@ export default class DuckAiListener extends ContentFeature {
760760
if (pageDataParsed.content) {
761761
this.pageData = pageDataParsed;
762762

763+
this.promptTelemetry?.sendContextPixelInfo(pageDataParsed, DuckAiPromptTelemetry.CONTEXT_ATTACH_PIXEL_NAME);
764+
763765
// Resolve any pending context promise
764766
if (this.contextPromiseResolve) {
765767
this.contextPromiseResolve(true);
@@ -817,7 +819,6 @@ export default class DuckAiListener extends ContentFeature {
817819
const handleClick = this.handleSendMessage.bind(this);
818820

819821
sendButton.addEventListener('click', handleClick, true); // Capture phase
820-
// sendButton.addEventListener('click', handleClick, false); // Bubble phase
821822

822823
this.log.info('Set up message interception with multiple event listeners', sendButton);
823824
}
@@ -983,7 +984,7 @@ export default class DuckAiListener extends ContentFeature {
983984
this.mutationObserver = null;
984985

985986
// Callback function to execute when mutations are observed
986-
const callback = (_, observer) => {
987+
const callback = (/** @type {MutationRecord[]} */ _, observer) => {
987988
this.findTextBox();
988989
this.setupMessageInterception();
989990
if (this.textBox && this.pageData && this.sendButton && !this.hasContextBeenUsed) {
@@ -1011,13 +1012,17 @@ export default class DuckAiListener extends ContentFeature {
10111012
this.textBox = element;
10121013
this.log.info('Found AI text box');
10131014

1014-
// Add enter key handler to call handleSendMessage
1015-
element.addEventListener('keyup', (event) => {
1016-
if (event.key === 'Enter' && !event.shiftKey) {
1017-
this.log.info('Enter key pressed');
1018-
this.handleSendMessage();
1019-
}
1020-
});
1015+
// Add enter key handler using keydown with capture phase
1016+
element.addEventListener(
1017+
'keydown',
1018+
(event) => {
1019+
if (event.key === 'Enter' && !event.shiftKey) {
1020+
this.log.info('Enter key pressed');
1021+
this.handleSendMessage();
1022+
}
1023+
},
1024+
true,
1025+
);
10211026

10221027
// Set up property descriptor to intercept value reads for context appending
10231028
this.setupValuePropertyDescriptor(element);
@@ -1148,7 +1153,8 @@ ${truncatedWarning}
11481153
*/
11491154
class DuckAiPromptTelemetry {
11501155
static STORAGE_KEY = 'aiChatPageContextTelemetry';
1151-
static CONTEXT_PIXEL_NAME = 'dc_contextInfo';
1156+
static CONTEXT_ATTACH_PIXEL_NAME = 'dc_contextInfoOnAttach';
1157+
static CONTEXT_SEND_PIXEL_NAME = 'dc_contextInfoOnSubmit';
11521158
static DAILY_PIXEL_NAME = 'dc_pageContextDailyTelemetry';
11531159
static ONE_DAY_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
11541160

@@ -1298,11 +1304,11 @@ class DuckAiPromptTelemetry {
12981304

12991305
const telemetryData = {
13001306
totalPrompts: String(totalPrompts),
1301-
avgRawPromptSize: this.bucketSizeByThousands(avgRawPromptSize),
1307+
avgRawPromptSize: this.bucketSize(avgRawPromptSize),
13021308
...createSizeFields('raw', rawSizeBuckets),
1303-
avgTotalPromptSize: this.bucketSizeByThousands(avgTotalPromptSize),
1309+
avgTotalPromptSize: this.bucketSize(avgTotalPromptSize),
13041310
...createSizeFields('total', totalSizeBuckets),
1305-
avgContextSize: this.bucketSizeByThousands(avgContextSize),
1311+
avgContextSize: this.bucketSize(avgContextSize),
13061312
contextUsageRate: String(Math.round(contextUsageRate * 100)),
13071313
};
13081314

@@ -1337,7 +1343,8 @@ class DuckAiPromptTelemetry {
13371343
if (!globalThis?.DDG?.pixel) {
13381344
return;
13391345
}
1340-
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.CONTEXT_PIXEL_NAME] = {};
1346+
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.CONTEXT_SEND_PIXEL_NAME] = {};
1347+
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.CONTEXT_ATTACH_PIXEL_NAME] = {};
13411348
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.DAILY_PIXEL_NAME] = {};
13421349
}
13431350

@@ -1354,30 +1361,30 @@ class DuckAiPromptTelemetry {
13541361
}
13551362

13561363
/**
1357-
* Bucket numbers by thousands for privacy-friendly reporting
1364+
* Bucket numbers by hundreds for privacy-friendly reporting
13581365
* @param {number} number - Number to bucket
1359-
* @returns {string} Bucket lower bound (e.g., '0', '1000', '2000')
1366+
* @returns {string} Bucket lower bound (e.g., '0', '100', '200')
13601367
*/
1361-
bucketSizeByThousands(number) {
1368+
bucketSize(number) {
13621369
if (number <= 0) {
13631370
return '0';
13641371
}
1365-
const bucketIndex = Math.floor(number / 1000);
1366-
return String(bucketIndex * 1000);
1372+
const bucketIndex = Math.floor(number / 100);
1373+
return String(bucketIndex * 100);
13671374
}
13681375

13691376
/**
13701377
* Send context pixel info when context is used
13711378
* @param {Object} contextData - Context data object
13721379
*/
1373-
sendContextPixelInfo(contextData) {
1374-
if (!contextData?.content) {
1380+
sendContextPixelInfo(contextData, pixelName) {
1381+
if (!contextData?.content || contextData.content.length === 0) {
13751382
this.log.warn('sendContextPixelInfo: No content available for pixel tracking');
13761383
return;
13771384
}
13781385

1379-
this.sendPixel(DuckAiPromptTelemetry.CONTEXT_PIXEL_NAME, {
1380-
contextLength: this.bucketSizeByThousands(contextData.content.length),
1386+
this.sendPixel(pixelName, {
1387+
contextLength: contextData.content.length,
13811388
});
13821389
}
13831390

@@ -1406,7 +1413,7 @@ class DuckAiPromptTelemetry {
14061413

14071414
// Send context pixel info if context was used
14081415
if (contextData && contextSize > 0) {
1409-
this.sendContextPixelInfo(contextData);
1416+
this.sendContextPixelInfo(contextData, DuckAiPromptTelemetry.CONTEXT_SEND_PIXEL_NAME);
14101417
}
14111418
// Check if we should fire daily telemetry
14121419
this.checkShouldFireDailyTelemetry();

package-lock.json

Lines changed: 71 additions & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)