Skip to content

Commit 73c4bb7

Browse files
committed
chore: vdm record v2 changes
🔒 Scanned for secrets using gitleaks 8.29.1
1 parent a551b35 commit 73c4bb7

File tree

7 files changed

+763
-15
lines changed

7 files changed

+763
-15
lines changed

src/v0/destinations/tiktok_audience/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ export const ACTION_MAP: Record<string, string> = {
33
remove: 'delete',
44
};
55

6+
export const ACTION_RECORD_MAP: Record<string, string> = {
7+
insert: 'add',
8+
delete: 'remove',
9+
update: 'add',
10+
};
11+
612
export const SHA256_TRAITS = ['IDFA_SHA256', 'AAID_SHA256', 'EMAIL_SHA256', 'PHONE_SHA256'];
713

814
export const BASE_URL = 'https://business-api.tiktok.com/open_api/v1.3';
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import md5 from 'md5';
2+
import { hashToSha256, InstrumentationError, formatZodError } from '@rudderstack/integrations-lib';
3+
import type { TiktokAudienceRecordRequest } from './types.record';
4+
import { TiktokAudienceRecordRouterRequestSchema } from './types.record';
5+
import { SHA256_TRAITS, ENDPOINT, ENDPOINT_PATH, ACTION_RECORD_MAP } from './config';
6+
import { defaultRequestConfig } from '../../util';
7+
8+
function prepareIdentifiersPayload(event: TiktokAudienceRecordRequest): Record<string, any> {
9+
const { message, connection, destination } = event;
10+
const { isHashRequired, audienceId } = connection.config.destination;
11+
const { advertiserId } = destination.Config;
12+
const { action, identifiers } = message;
13+
14+
const hashIdentifier = (fieldName: string, value: string) => {
15+
if (isHashRequired) {
16+
if (SHA256_TRAITS.includes(fieldName)) {
17+
return hashToSha256(value);
18+
}
19+
return md5(value);
20+
}
21+
return value;
22+
};
23+
24+
const identifiersList = Object.entries(identifiers).map(([fieldName, value]) => {
25+
if (value) {
26+
return {
27+
id: hashIdentifier(fieldName, value!),
28+
audience_ids: [audienceId],
29+
};
30+
}
31+
return {};
32+
});
33+
34+
const payload = {
35+
batch_data: [identifiersList],
36+
id_schema: Object.keys(identifiers),
37+
advertiser_ids: [advertiserId],
38+
action: ACTION_RECORD_MAP[action],
39+
};
40+
return payload;
41+
}
42+
43+
function buildResponseForProcessTransformation(
44+
payload: Record<string, any>,
45+
event: TiktokAudienceRecordRequest,
46+
) {
47+
const accessToken = event.metadata?.secret?.accessToken;
48+
const userId = event.message?.userId;
49+
50+
const response = defaultRequestConfig();
51+
response.body.JSON = payload;
52+
response.userId = userId;
53+
response.endpoint = ENDPOINT;
54+
response.endpointPath = ENDPOINT_PATH;
55+
response.headers = {
56+
'Access-Token': accessToken,
57+
'Content-Type': 'application/json',
58+
};
59+
return response;
60+
}
61+
62+
function validateAudienceRecordEvent(event: unknown) {
63+
const result = TiktokAudienceRecordRouterRequestSchema.safeParse(event);
64+
if (!result.success) {
65+
throw new InstrumentationError(formatZodError(result.error));
66+
}
67+
return result.data;
68+
}
69+
70+
function processTiktokAudienceRecord(event: TiktokAudienceRecordRequest) {
71+
const payload = prepareIdentifiersPayload(event);
72+
return buildResponseForProcessTransformation(payload, event);
73+
}
74+
75+
export { validateAudienceRecordEvent, processTiktokAudienceRecord };

src/v0/destinations/tiktok_audience/transform.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import md5 from 'md5';
22
import { hashToSha256, InstrumentationError, formatZodError } from '@rudderstack/integrations-lib';
33
import type { RouterTransformationResponse } from '../../../types';
4-
import type { TiktokAudienceRequest } from './types';
5-
import { TiktokAudienceRouterRequestSchema } from './types';
4+
import type { TiktokAudienceListRequest } from './types';
5+
import { TiktokAudienceListRouterRequestSchema } from './types';
66
import { SHA256_TRAITS, ACTION_MAP, ENDPOINT, ENDPOINT_PATH } from './config';
77
import {
88
defaultRequestConfig,
99
getDestinationExternalIDInfoForRetl,
1010
getSuccessRespEvents,
1111
handleRtTfSingleEventError,
12+
isEventSentByVDMV2Flow,
1213
} from '../../util';
14+
import { processTiktokAudienceRecord, validateAudienceRecordEvent } from './transform.record';
1315

14-
function prepareIdentifiersList(event: TiktokAudienceRequest) {
16+
function prepareIdentifiersList(event: TiktokAudienceListRequest) {
1517
const { message, destination, metadata } = event;
1618
const { isHashRequired } = destination.Config;
1719

@@ -57,7 +59,7 @@ function prepareIdentifiersList(event: TiktokAudienceRequest) {
5759

5860
function buildResponseForProcessTransformation(
5961
identifiersList: any[],
60-
event: TiktokAudienceRequest,
62+
event: TiktokAudienceListRequest,
6163
) {
6264
const accessToken = event.metadata?.secret?.accessToken;
6365
const anonymousId = event.message?.anonymousId;
@@ -80,21 +82,24 @@ function buildResponseForProcessTransformation(
8082
return responses;
8183
}
8284

83-
function validateEvent(event: unknown) {
84-
const result = TiktokAudienceRouterRequestSchema.safeParse(event);
85+
function validateAudienceListEvent(event: unknown) {
86+
const result = TiktokAudienceListRouterRequestSchema.safeParse(event);
8587
if (!result.success) {
8688
throw new InstrumentationError(formatZodError(result.error));
8789
}
8890
return result.data;
8991
}
9092

91-
function processTiktokAudience(event: TiktokAudienceRequest) {
93+
function processTiktokAudienceList(event: TiktokAudienceListRequest) {
9294
const identifierLists = prepareIdentifiersList(event);
9395
return buildResponseForProcessTransformation(identifierLists, event);
9496
}
9597

9698
function process(event: unknown) {
97-
return processTiktokAudience(validateEvent(event));
99+
if (isEventSentByVDMV2Flow(event)) {
100+
return processTiktokAudienceRecord(validateAudienceRecordEvent(event));
101+
}
102+
return processTiktokAudienceList(validateAudienceListEvent(event));
98103
}
99104

100105
const processRouterDest = async (events: unknown[]): Promise<RouterTransformationResponse[]> => {
@@ -105,8 +110,16 @@ const processRouterDest = async (events: unknown[]): Promise<RouterTransformatio
105110

106111
for (const event of events) {
107112
try {
108-
const tiktokEvent = validateEvent(event);
109-
const response = processTiktokAudience(tiktokEvent);
113+
let tiktokEvent; let response;
114+
115+
if (isEventSentByVDMV2Flow(event)) {
116+
tiktokEvent = validateAudienceRecordEvent(event);
117+
response = processTiktokAudienceRecord(tiktokEvent);
118+
} else {
119+
tiktokEvent = validateAudienceListEvent(event);
120+
response = processTiktokAudienceList(tiktokEvent);
121+
}
122+
110123
successfulResponses.push(
111124
getSuccessRespEvents(response, [tiktokEvent.metadata], tiktokEvent.destination, true),
112125
);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { z } from 'zod';
2+
3+
const TiktokAudienceDestinationSchema = z
4+
.object({
5+
Config: z
6+
.object({
7+
advertiserId: z.string(),
8+
})
9+
.passthrough(),
10+
})
11+
.passthrough();
12+
13+
const TiktokAudienceConnectionSchema = z
14+
.object({
15+
config: z
16+
.object({
17+
destination: z
18+
.object({
19+
isHashRequired: z.boolean(),
20+
audienceId: z.string(),
21+
})
22+
.passthrough(),
23+
})
24+
.passthrough(),
25+
})
26+
.passthrough();
27+
28+
const TiktokAudienceMessageSchema = z
29+
.object({
30+
type: z.enum(['record'], {
31+
required_error: 'message Type is not present. Aborting message.',
32+
}),
33+
action: z.enum(['insert', 'delete', 'update'], {
34+
required_error: 'action is not present. Aborting message.',
35+
}),
36+
userId: z.string().optional(),
37+
identifiers: z.record(z.string(), z.string().nullable()),
38+
fields: z.record(z.string(), z.string().nullable()),
39+
})
40+
.passthrough();
41+
42+
const TiktokAudienceMetadataSchema = z
43+
.object({
44+
secret: z
45+
.object({
46+
accessToken: z.string(),
47+
})
48+
.passthrough(),
49+
})
50+
.passthrough();
51+
52+
export const TiktokAudienceRecordRouterRequestSchema = z
53+
.object({
54+
message: TiktokAudienceMessageSchema,
55+
destination: TiktokAudienceDestinationSchema,
56+
connection: TiktokAudienceConnectionSchema,
57+
metadata: TiktokAudienceMetadataSchema,
58+
})
59+
.passthrough();
60+
61+
export type TiktokAudienceRecordRequest = z.infer<typeof TiktokAudienceRecordRouterRequestSchema>;

src/v0/destinations/tiktok_audience/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from 'zod';
22
import { ACTION_MAP } from './config';
33

4-
export const TiktokAudienceDestinationSchema = z
4+
const TiktokAudienceDestinationSchema = z
55
.object({
66
Config: z
77
.object({
@@ -11,7 +11,7 @@ export const TiktokAudienceDestinationSchema = z
1111
})
1212
.passthrough();
1313

14-
export const TiktokAudienceMessageSchema = z
14+
const TiktokAudienceMessageSchema = z
1515
.object({
1616
type: z.enum(['audienceList'], {
1717
required_error: 'message Type is not present. Aborting message.',
@@ -59,7 +59,7 @@ export const TiktokAudienceMessageSchema = z
5959
})
6060
.passthrough();
6161

62-
export const TiktokAudienceMetadataSchema = z
62+
const TiktokAudienceMetadataSchema = z
6363
.object({
6464
secret: z
6565
.object({
@@ -70,12 +70,12 @@ export const TiktokAudienceMetadataSchema = z
7070
})
7171
.passthrough();
7272

73-
export const TiktokAudienceRouterRequestSchema = z
73+
export const TiktokAudienceListRouterRequestSchema = z
7474
.object({
7575
message: TiktokAudienceMessageSchema,
7676
destination: TiktokAudienceDestinationSchema,
7777
metadata: TiktokAudienceMetadataSchema,
7878
})
7979
.passthrough();
8080

81-
export type TiktokAudienceRequest = z.infer<typeof TiktokAudienceRouterRequestSchema>;
81+
export type TiktokAudienceListRequest = z.infer<typeof TiktokAudienceListRouterRequestSchema>;

0 commit comments

Comments
 (0)