Skip to content
5 changes: 5 additions & 0 deletions .changeset/eight-adults-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-signals': minor
---

Fix CSP errors with sandboxStrategy: global
5 changes: 5 additions & 0 deletions .changeset/empty-eagles-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-signals': minor
---

Update max signals in buffer to 100
11 changes: 9 additions & 2 deletions packages/signals/signals-example/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React TypeScript App</title>
<!---

1. Requres 'unsafe-inline'
- Refused to execute inline script because it violates the following Content Security Policy directive: [directive] Either the 'unsafe-inline' keyword, a hash ('sha256-XDT/UwTV/dYYXFad1cmKD+q1zZNK8KGVRYvIdvkmo7I='), or a nonce ('nonce-...') is required to enable inline execution.
2. Requires 'unsafe-eval'
processSignal() error in sandbox Error: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive
-->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://*.segment.com https://*.googletagmanager.com blob:">
</head>

<body>
<div id="root"></div>
</body>

</html>
</html>
2 changes: 1 addition & 1 deletion packages/signals/signals-example/src/lib/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const isStage = process.env.STAGE === 'true'

const signalsPlugin = new SignalsPlugin({
...(isStage ? { apiHost: 'signals.segment.build/v1' } : {}),
// enableDebugLogging: true,
sandboxStrategy: 'global',
// processSignal: processSignalExample,
})

Expand Down
8 changes: 8 additions & 0 deletions packages/signals/signals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ signalsPlugin.addSignal({ someData: 'foo' })
}
```

### Sandbox Strategies
If getting CSP errors, you can use the experimental 'global' sandbox strategy.

```ts
new SignalsPlugin({ sandboxStrategy: 'global' })
```


### Debugging
Debug mode **MUST** be enabled on the client to VIEW signals on segment.com.

Expand Down
2 changes: 1 addition & 1 deletion packages/signals/signals/src/core/buffer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface IDBPObjectStoreSignals
'readonly' | 'readwrite' | 'versionchange'
> {}

const MAX_BUFFER_SIZE_DEFAULT = 50
const MAX_BUFFER_SIZE_DEFAULT = 100

interface StoreSettings {
maxBufferSize?: number
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
import { Signal } from '@segment/analytics-signals-runtime'
import { logger } from '../../../lib/logger'
import { SignalBuffer } from '../../buffer'
import { SignalsSubscriber, SignalsMiddlewareContext } from '../../emitter'
import { SignalEventProcessor } from '../../processor/processor'
import { Sandbox, SandboxSettings } from '../../processor/sandbox'
import {
normalizeEdgeFunctionURL,
GlobalScopeSandbox,
WorkerSandbox,
IframeSandboxSettings,
SignalSandbox,
NoopSandbox,
} from '../../processor/sandbox'

export class SignalsEventProcessorSubscriber implements SignalsSubscriber {
processor!: SignalEventProcessor
buffer!: SignalBuffer
load(ctx: SignalsMiddlewareContext) {
this.buffer = ctx.buffer
this.processor = new SignalEventProcessor(
ctx.analyticsInstance,
new Sandbox(new SandboxSettings(ctx.unstableGlobalSettings.sandbox))
const sandboxSettings = ctx.unstableGlobalSettings.sandbox
const normalizedEdgeFunctionURL = normalizeEdgeFunctionURL(

Check warning on line 21 in packages/signals/signals/src/core/middleware/event-processor/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/middleware/event-processor/index.ts#L20-L21

Added lines #L20 - L21 were not covered by tests
sandboxSettings.functionHost,
sandboxSettings.edgeFnDownloadURL
)

let sandbox: SignalSandbox

if (!normalizedEdgeFunctionURL) {
console.warn(

Check warning on line 29 in packages/signals/signals/src/core/middleware/event-processor/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/middleware/event-processor/index.ts#L28-L29

Added lines #L28 - L29 were not covered by tests
`No processSignal function found. Have you written a processSignal function on app.segment.com?`
)
logger.debug('Initializing sandbox: noop')
sandbox = new NoopSandbox()
} else if (
sandboxSettings.sandboxStrategy === 'iframe' ||
sandboxSettings.processSignal

Check warning on line 36 in packages/signals/signals/src/core/middleware/event-processor/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/middleware/event-processor/index.ts#L32-L36

Added lines #L32 - L36 were not covered by tests
) {
logger.debug('Initializing sandbox: iframe')
sandbox = new WorkerSandbox(

Check warning on line 39 in packages/signals/signals/src/core/middleware/event-processor/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/middleware/event-processor/index.ts#L38-L39

Added lines #L38 - L39 were not covered by tests
new IframeSandboxSettings({
processSignal: sandboxSettings.processSignal,
edgeFnDownloadURL: normalizedEdgeFunctionURL,
})
)
} else {
logger.debug('Initializing sandbox: global scope')
sandbox = new GlobalScopeSandbox({

Check warning on line 47 in packages/signals/signals/src/core/middleware/event-processor/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/middleware/event-processor/index.ts#L45-L47

Added lines #L45 - L47 were not covered by tests
edgeFnDownloadURL: normalizedEdgeFunctionURL,
})
}

this.processor = new SignalEventProcessor(ctx.analyticsInstance, sandbox)

Check warning on line 52 in packages/signals/signals/src/core/middleware/event-processor/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/middleware/event-processor/index.ts#L52

Added line #L52 was not covered by tests
}
async process(signal: Signal) {
return this.processor.process(signal, await this.buffer.getAll())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { SandboxSettings, SandboxSettingsConfig } from '../sandbox'
import { IframeSandboxSettings, IframeSandboxSettingsConfig } from '../sandbox'

describe(SandboxSettings, () => {
describe(IframeSandboxSettings, () => {
const edgeFnResponseBody = `function processSignal() { console.log('hello world') }`
const baseSettings: SandboxSettingsConfig = {
functionHost: undefined,
const baseSettings: IframeSandboxSettingsConfig = {
processSignal: undefined,
edgeFnDownloadURL: 'http://example.com/download',
edgeFnFetchClient: jest.fn().mockReturnValue(
Expand All @@ -13,38 +12,37 @@ describe(SandboxSettings, () => {
),
}
test('initializes with provided settings', async () => {
const sandboxSettings = new SandboxSettings({ ...baseSettings })
const sandboxSettings = new IframeSandboxSettings({ ...baseSettings })
expect(baseSettings.edgeFnFetchClient).toHaveBeenCalledWith(
baseSettings.edgeFnDownloadURL
)
expect(await sandboxSettings.processSignal).toEqual(edgeFnResponseBody)
})

test('normalizes edgeFnDownloadURL when functionHost is provided', async () => {
const settings: SandboxSettingsConfig = {
test('should call edgeFnDownloadURL', async () => {
const settings: IframeSandboxSettingsConfig = {
...baseSettings,
processSignal: undefined,
functionHost: 'newHost.com',
edgeFnDownloadURL: 'https://original.com/download',
edgeFnDownloadURL: 'https://foo.com/download',
}
new SandboxSettings(settings)
new IframeSandboxSettings(settings)
expect(baseSettings.edgeFnFetchClient).toHaveBeenCalledWith(
'https://newHost.com/download'
'https://foo.com/download'
)
})

test('creates default processSignal when parameters are missing', async () => {
const consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementation(() => {})
const settings: SandboxSettingsConfig = {
const settings: IframeSandboxSettingsConfig = {
...baseSettings,
processSignal: undefined,
edgeFnDownloadURL: undefined,
}
const sandboxSettings = new SandboxSettings(settings)
expect(await sandboxSettings.processSignal).toEqual(
'globalThis.processSignal = function processSignal() {}'
const sandboxSettings = new IframeSandboxSettings(settings)
expect(await sandboxSettings.processSignal).toMatchInlineSnapshot(
`"globalThis.processSignal = function() {}"`
)
expect(baseSettings.edgeFnFetchClient).not.toHaveBeenCalled()
expect(consoleWarnSpy).toHaveBeenCalledWith(
Expand Down
14 changes: 7 additions & 7 deletions packages/signals/signals/src/core/processor/processor.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { logger } from '../../lib/logger'
import { Signal } from '@segment/analytics-signals-runtime'
import { AnyAnalytics } from '../../types'
import { AnalyticsMethodCalls, MethodName, Sandbox } from './sandbox'
import { AnalyticsMethodCalls, MethodName, SignalSandbox } from './sandbox'

export class SignalEventProcessor {
private sandbox: Sandbox
private analytics: AnyAnalytics
constructor(analytics: AnyAnalytics, sandbox: Sandbox) {
analytics: AnyAnalytics
sandbox: SignalSandbox
constructor(analytics: AnyAnalytics, sandbox: SignalSandbox) {

Check warning on line 9 in packages/signals/signals/src/core/processor/processor.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/processor/processor.ts#L9

Added line #L9 was not covered by tests
this.analytics = analytics
this.sandbox = sandbox
}

async process(signal: Signal, signals: Signal[]) {
let analyticsMethodCalls: AnalyticsMethodCalls
let analyticsMethodCalls: AnalyticsMethodCalls | undefined
try {
analyticsMethodCalls = await this.sandbox.process(signal, signals)
analyticsMethodCalls = await this.sandbox.execute(signal, signals)

Check warning on line 17 in packages/signals/signals/src/core/processor/processor.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/processor/processor.ts#L17

Added line #L17 was not covered by tests
} catch (err) {
// in practice, we should never hit this error, but if we do, we should log it.
console.error('Error processing signal', { signal, signals }, err)
Expand All @@ -34,6 +34,6 @@
}

cleanup() {
return this.sandbox.jsSandbox.destroy()
return this.sandbox.destroy()

Check warning on line 37 in packages/signals/signals/src/core/processor/processor.ts

View check run for this annotation

Codecov / codecov/patch

packages/signals/signals/src/core/processor/processor.ts#L37

Added line #L37 was not covered by tests
}
}
Loading