Skip to content

Commit 46a42d8

Browse files
bkboothclaude
andcommitted
refactor(audience): make httpSend generic for consent PUT
Add method option to TransportOptions so consent module can use httpSend instead of raw fetch. Type the consent payload as ConsentUpdatePayload and widen httpSend to accept both BatchPayload and ConsentUpdatePayload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4fdbd61 commit 46a42d8

6 files changed

Lines changed: 46 additions & 31 deletions

File tree

packages/audience/core/src/consent.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { createConsentManager } from './consent';
2+
import { httpSend } from './transport';
23

3-
// Mock fetch globally
4-
const mockFetch = jest.fn().mockResolvedValue({ ok: true });
5-
global.fetch = mockFetch;
4+
jest.mock('./transport', () => ({
5+
httpSend: jest.fn().mockResolvedValue(true),
6+
}));
7+
8+
const mockHttpSend = httpSend as jest.MockedFunction<typeof httpSend>;
69

710
function createMockQueue() {
811
return {
@@ -81,16 +84,11 @@ describe('createConsentManager', () => {
8184

8285
manager.setLevel('anonymous');
8386

84-
expect(mockFetch).toHaveBeenCalledWith(
87+
expect(mockHttpSend).toHaveBeenCalledWith(
8588
'https://api.dev.immutable.com/v1/audience/tracking-consent',
86-
expect.objectContaining({
87-
method: 'PUT',
88-
headers: expect.objectContaining({
89-
'Content-Type': 'application/json',
90-
'x-immutable-publishable-key': 'pk_test',
91-
}),
92-
body: JSON.stringify({ anonymousId: 'anon-1', status: 'anonymous', source: 'pixel' }),
93-
}),
89+
'pk_test',
90+
{ anonymousId: 'anon-1', status: 'anonymous', source: 'pixel' },
91+
{ method: 'PUT', keepalive: true },
9492
);
9593
});
9694

@@ -101,7 +99,7 @@ describe('createConsentManager', () => {
10199
manager.setLevel('anonymous');
102100
expect(queue.purge).not.toHaveBeenCalled();
103101
expect(queue.transform).not.toHaveBeenCalled();
104-
expect(mockFetch).not.toHaveBeenCalled();
102+
expect(mockHttpSend).not.toHaveBeenCalled();
105103
});
106104

107105
it('respects DNT by defaulting to none', () => {

packages/audience/core/src/consent.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import type { ConsentLevel, Message, Environment } from './types';
1+
import type {
2+
ConsentLevel, ConsentUpdatePayload, Message, Environment,
3+
} from './types';
24
import type { MessageQueue } from './queue';
35
import { CONSENT_PATH, getBaseUrl } from './config';
6+
import { httpSend } from './transport';
47

58
export interface ConsentManager {
69
level: ConsentLevel;
@@ -40,19 +43,8 @@ export function createConsentManager(
4043

4144
function notifyBackend(level: ConsentLevel): void {
4245
const url = `${getBaseUrl(environment)}${CONSENT_PATH}`;
43-
const payload = { anonymousId, status: level, source };
44-
// Uses fetch directly rather than httpSend because this is a PUT
45-
// to a different endpoint with a different payload shape than the
46-
// message ingest POST that httpSend is designed for.
47-
fetch(url, {
48-
method: 'PUT',
49-
headers: {
50-
'Content-Type': 'application/json',
51-
'x-immutable-publishable-key': publishableKey,
52-
},
53-
body: JSON.stringify(payload),
54-
keepalive: true,
55-
}).catch(() => {});
46+
const payload: ConsentUpdatePayload = { anonymousId, status: level, source };
47+
httpSend(url, publishableKey, payload, { method: 'PUT', keepalive: true });
5648
}
5749

5850
const manager: ConsentManager = {

packages/audience/core/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type {
1313
BatchPayload,
1414
ConsentLevel,
1515
ConsentStatus,
16+
ConsentUpdatePayload,
1617
} from './types';
1718

1819
export {
@@ -37,7 +38,7 @@ export {
3738

3839
export { generateId, getTimestamp, isBrowser } from './utils';
3940

40-
export type { Transport } from './transport';
41+
export type { Transport, TransportOptions } from './transport';
4142
export { httpTransport, httpSend } from './transport';
4243
export { MessageQueue } from './queue';
4344
export { collectContext } from './context';

packages/audience/core/src/transport.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ describe('httpSend', () => {
5151
}));
5252
});
5353

54+
it('uses specified method from options', async () => {
55+
const mockFetch = jest.fn().mockResolvedValue({ ok: true });
56+
global.fetch = mockFetch;
57+
58+
const consentPayload = { anonymousId: 'anon-1', status: 'anonymous' as const, source: 'pixel' };
59+
await httpSend('https://example.com/consent', 'pk', consentPayload, { method: 'PUT', keepalive: true });
60+
61+
expect(mockFetch).toHaveBeenCalledWith('https://example.com/consent', expect.objectContaining({
62+
method: 'PUT',
63+
keepalive: true,
64+
body: JSON.stringify(consentPayload),
65+
}));
66+
});
67+
5468
it('returns true on success', async () => {
5569
global.fetch = jest.fn().mockResolvedValue({ ok: true });
5670
expect(await httpSend('https://example.com', 'pk', payload)).toBe(true);

packages/audience/core/src/transport.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { track, trackError } from '@imtbl/metrics';
2-
import type { BatchPayload } from './types';
2+
import type { BatchPayload, ConsentUpdatePayload } from './types';
33

44
export interface TransportOptions {
5+
method?: string;
56
keepalive?: boolean;
67
}
78

@@ -12,12 +13,12 @@ export interface Transport {
1213
export async function httpSend(
1314
url: string,
1415
publishableKey: string,
15-
payload: BatchPayload,
16+
payload: BatchPayload | ConsentUpdatePayload,
1617
options?: TransportOptions,
1718
): Promise<boolean> {
1819
try {
1920
const response = await fetch(url, {
20-
method: 'POST',
21+
method: options?.method ?? 'POST',
2122
headers: {
2223
'Content-Type': 'application/json',
2324
'x-immutable-publishable-key': publishableKey,

packages/audience/core/src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,12 @@ export type ConsentLevel = 'none' | 'anonymous' | 'full';
9797
* - `'full'` — User accepted full tracking.
9898
*/
9999
export type ConsentStatus = 'not_set' | 'none' | 'anonymous' | 'full';
100+
101+
/**
102+
* PUT body for `/v1/audience/tracking-consent`.
103+
*/
104+
export interface ConsentUpdatePayload {
105+
anonymousId: string;
106+
status: ConsentLevel;
107+
source: string;
108+
}

0 commit comments

Comments
 (0)