@shware/analytics 5.1.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/use-app-analytics.cjs +7 -6
- package/dist/hooks/use-app-analytics.cjs.map +1 -1
- package/dist/hooks/use-app-analytics.mjs +4 -3
- package/dist/hooks/use-app-analytics.mjs.map +1 -1
- package/dist/hooks/use-web-analytics.cjs +14 -13
- package/dist/hooks/use-web-analytics.cjs.map +1 -1
- package/dist/hooks/use-web-analytics.mjs +7 -6
- package/dist/hooks/use-web-analytics.mjs.map +1 -1
- package/dist/native/setup.cjs +0 -1
- package/dist/native/setup.cjs.map +1 -1
- package/dist/native/setup.mjs +0 -1
- package/dist/native/setup.mjs.map +1 -1
- package/dist/schema/index.cjs +0 -5
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +0 -20
- package/dist/schema/index.d.cts.map +1 -1
- package/dist/schema/index.d.mts +0 -20
- package/dist/schema/index.d.mts.map +1 -1
- package/dist/schema/index.mjs +0 -5
- package/dist/schema/index.mjs.map +1 -1
- package/dist/server/action-source.cjs +28 -0
- package/dist/server/action-source.cjs.map +1 -0
- package/dist/server/action-source.d.cts +20 -0
- package/dist/server/action-source.d.cts.map +1 -0
- package/dist/server/action-source.d.mts +20 -0
- package/dist/server/action-source.d.mts.map +1 -0
- package/dist/server/action-source.mjs +27 -0
- package/dist/server/action-source.mjs.map +1 -0
- package/dist/server/index.d.cts +2 -1
- package/dist/server/index.d.mts +2 -1
- package/dist/server/meta-conversions-api.cjs +12 -8
- package/dist/server/meta-conversions-api.cjs.map +1 -1
- package/dist/server/meta-conversions-api.d.cts +4 -3
- package/dist/server/meta-conversions-api.d.cts.map +1 -1
- package/dist/server/meta-conversions-api.d.mts +4 -3
- package/dist/server/meta-conversions-api.d.mts.map +1 -1
- package/dist/server/meta-conversions-api.mjs +12 -8
- package/dist/server/meta-conversions-api.mjs.map +1 -1
- package/dist/server/openai-conversions-api.cjs +7 -6
- package/dist/server/openai-conversions-api.cjs.map +1 -1
- package/dist/server/openai-conversions-api.d.cts +3 -2
- package/dist/server/openai-conversions-api.d.cts.map +1 -1
- package/dist/server/openai-conversions-api.d.mts +3 -2
- package/dist/server/openai-conversions-api.d.mts.map +1 -1
- package/dist/server/openai-conversions-api.mjs +7 -6
- package/dist/server/openai-conversions-api.mjs.map +1 -1
- package/dist/server/reddit-conversions-api.cjs +6 -4
- package/dist/server/reddit-conversions-api.cjs.map +1 -1
- package/dist/server/reddit-conversions-api.d.cts +3 -2
- package/dist/server/reddit-conversions-api.d.cts.map +1 -1
- package/dist/server/reddit-conversions-api.d.mts +3 -2
- package/dist/server/reddit-conversions-api.d.mts.map +1 -1
- package/dist/server/reddit-conversions-api.mjs +6 -4
- package/dist/server/reddit-conversions-api.mjs.map +1 -1
- package/dist/setup/session.cjs +28 -2
- package/dist/setup/session.cjs.map +1 -1
- package/dist/setup/session.d.cts +25 -2
- package/dist/setup/session.d.cts.map +1 -1
- package/dist/setup/session.d.mts +25 -2
- package/dist/setup/session.d.mts.map +1 -1
- package/dist/setup/session.mjs +28 -2
- package/dist/setup/session.mjs.map +1 -1
- package/dist/track/index.cjs +23 -12
- package/dist/track/index.cjs.map +1 -1
- package/dist/track/index.d.cts.map +1 -1
- package/dist/track/index.d.mts.map +1 -1
- package/dist/track/index.mjs +18 -7
- package/dist/track/index.mjs.map +1 -1
- package/dist/track/types.d.cts +0 -1
- package/dist/track/types.d.cts.map +1 -1
- package/dist/track/types.d.mts +0 -1
- package/dist/track/types.d.mts.map +1 -1
- package/dist/web/index.cjs +0 -3
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts.map +1 -1
- package/dist/web/index.d.mts.map +1 -1
- package/dist/web/index.mjs +0 -3
- package/dist/web/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_third_parties_ignored_events = require("../third-parties/ignored-events.cjs");
|
|
3
|
+
const require_server_action_source = require("./action-source.cjs");
|
|
3
4
|
const require_utils_field = require("../utils/field.cjs");
|
|
4
5
|
const require_track_oaiq = require("../track/oaiq.cjs");
|
|
5
6
|
let _shware_utils = require("@shware/utils");
|
|
@@ -14,8 +15,8 @@ const ENDPOINT = "https://bzr.openai.com/v1/events";
|
|
|
14
15
|
function sha256(value) {
|
|
15
16
|
return (0, node_crypto.createHash)("sha256").update(value).digest("hex");
|
|
16
17
|
}
|
|
17
|
-
function mapActionSource(
|
|
18
|
-
switch (
|
|
18
|
+
function mapActionSource(platform, actionSource) {
|
|
19
|
+
switch (require_server_action_source.resolveActionSource(platform, actionSource)) {
|
|
19
20
|
case "web": return "web";
|
|
20
21
|
case "app": return "mobile_app";
|
|
21
22
|
case "offline": return "offline";
|
|
@@ -36,7 +37,7 @@ function getUser(data) {
|
|
|
36
37
|
};
|
|
37
38
|
return Object.values(user).some((value) => value !== void 0) ? user : void 0;
|
|
38
39
|
}
|
|
39
|
-
function getServerEvent(event, data) {
|
|
40
|
+
function getServerEvent(event, data, actionSource) {
|
|
40
41
|
const { type, data: eventData } = require_track_oaiq.mapOAIEvent(event.name, event.properties);
|
|
41
42
|
return {
|
|
42
43
|
id: event.tags.idempotency_key ?? event.id,
|
|
@@ -44,15 +45,15 @@ function getServerEvent(event, data) {
|
|
|
44
45
|
custom_event_name: type === "custom" ? event.name : void 0,
|
|
45
46
|
timestamp_ms: Date.now(),
|
|
46
47
|
source_url: event.tags.source_url,
|
|
47
|
-
action_source: mapActionSource(event.
|
|
48
|
+
action_source: mapActionSource(event.platform, actionSource),
|
|
48
49
|
user: getUser(data),
|
|
49
50
|
data: eventData
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
|
-
async function sendEvents(apiKey, pixelId, events, data = {}, validateOnly = false) {
|
|
53
|
+
async function sendEvents(apiKey, pixelId, events, data = {}, validateOnly = false, actionSource) {
|
|
53
54
|
const dto = {
|
|
54
55
|
validate_only: validateOnly,
|
|
55
|
-
events: events.filter((event) => !require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)).filter((event) => !require_track_oaiq.NON_AD_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data))
|
|
56
|
+
events: events.filter((event) => !require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)).filter((event) => !require_track_oaiq.NON_AD_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, actionSource))
|
|
56
57
|
};
|
|
57
58
|
if (dto.events.length === 0) return;
|
|
58
59
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-conversions-api.cjs","names":["getFirst","mapOAIEvent","IGNORED_EVENTS","NON_AD_EVENTS"],"sources":["../../src/server/openai-conversions-api.ts"],"sourcesContent":["/**\n * OpenAI Conversions API\n * https://developers.openai.com/ads/conversions-api\n * https://developers.openai.com/ads/supported-events\n */\nimport { createHash } from 'node:crypto';\nimport { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type EventData, NON_AD_EVENTS, mapOAIEvent } from '../track/oaiq';\nimport type {
|
|
1
|
+
{"version":3,"file":"openai-conversions-api.cjs","names":["resolveActionSource","getFirst","mapOAIEvent","IGNORED_EVENTS","NON_AD_EVENTS"],"sources":["../../src/server/openai-conversions-api.ts"],"sourcesContent":["/**\n * OpenAI Conversions API\n * https://developers.openai.com/ads/conversions-api\n * https://developers.openai.com/ads/supported-events\n */\nimport { createHash } from 'node:crypto';\nimport { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type EventData, NON_AD_EVENTS, mapOAIEvent } from '../track/oaiq';\nimport type { Platform, TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { type EventActionSource, resolveActionSource } from './action-source';\n\nconst ENDPOINT = 'https://bzr.openai.com/v1/events';\n\ntype ActionSource =\n | 'web'\n | 'mobile_app'\n | 'offline'\n | 'physical_store'\n | 'phone_call'\n | 'email'\n | 'other';\n\n/**\n * User/identity fields. Email and external id must be sent as lowercase 64-char SHA-256 hex\n * strings; geographic, IP, and user-agent fields are sent as raw values.\n */\nexport interface OpenAIUser {\n email_sha256?: string;\n external_id_sha256?: string;\n /** Two-letter ISO 3166-1 country code (e.g. \"US\"). */\n country?: string;\n city?: string;\n zip_code?: string;\n ip_address?: string;\n user_agent?: string;\n}\n\nexport interface OpenAIEvent {\n /** Unique event id; combined with `type` for deduplication against pixel events. */\n id: string;\n /** Standard event name or `custom`. */\n type: string;\n /** Required when `type` is `custom`. */\n custom_event_name?: string;\n /** Event timestamp in ms; must be within 7 days and no more than 10 minutes in the future. */\n timestamp_ms: number;\n /** Required for `action_source: \"web\"`. */\n source_url?: string;\n action_source?: ActionSource;\n /** OpenAI-provided privacy-preserving identifier. */\n oppref?: string;\n /** When true, opts the event out of personalization. */\n opt_out?: boolean;\n user?: OpenAIUser;\n data: EventData;\n}\n\nexport interface CreateOpenAIEventsDTO {\n /** When true, validates the events without persisting them. */\n validate_only?: boolean;\n events: OpenAIEvent[];\n}\n\nfunction sha256(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nfunction mapActionSource(\n platform: Platform,\n actionSource?: EventActionSource\n): ActionSource | undefined {\n switch (resolveActionSource(platform, actionSource)) {\n case 'web':\n return 'web';\n case 'app':\n return 'mobile_app';\n case 'offline':\n return 'offline';\n default:\n return undefined;\n }\n}\n\nfunction getUser(data: UserProvidedData): OpenAIUser | undefined {\n const email = getFirst(data.email)?.trim().toLowerCase();\n const address = getFirst(data.address);\n\n const user: OpenAIUser = {\n email_sha256: email ? sha256(email) : undefined,\n external_id_sha256: data.user_id ? sha256(data.user_id) : undefined,\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n ip_address: data.ip_address,\n user_agent: data.user_agent,\n };\n\n return Object.values(user).some((value) => value !== undefined) ? user : undefined;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n actionSource?: EventActionSource\n): OpenAIEvent {\n const { type, data: eventData } = mapOAIEvent(event.name, event.properties);\n\n return {\n id: event.tags.idempotency_key ?? event.id,\n type,\n // For custom events the original track name is the OpenAI custom_event_name; this matches\n // the browser pixel so the two deduplicate. Standard events omit it.\n custom_event_name: type === 'custom' ? event.name : undefined,\n timestamp_ms: Date.now(),\n source_url: event.tags.source_url,\n action_source: mapActionSource(event.platform, actionSource),\n user: getUser(data),\n data: eventData,\n };\n}\n\nexport async function sendEvents(\n apiKey: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n validateOnly = false,\n actionSource?: EventActionSource\n) {\n const dto: CreateOpenAIEventsDTO = {\n validate_only: validateOnly,\n events: events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => !NON_AD_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, actionSource)),\n };\n\n if (dto.events.length === 0) return;\n\n try {\n const response = await fetch(`${ENDPOINT}?pid=${pixelId}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(dto),\n });\n\n if (response.ok) return;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send OpenAI conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n console.error('Failed to send OpenAI conversion, network error:', error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,WAAW;AAoDjB,SAAS,OAAO,OAAuB;CACrC,QAAA,GAAA,YAAA,WAAA,CAAkB,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACxD;AAEA,SAAS,gBACP,UACA,cAC0B;CAC1B,QAAQA,6BAAAA,oBAAoB,UAAU,YAAY,GAAlD;EACE,KAAK,OACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,SACE;CACJ;AACF;AAEA,SAAS,QAAQ,MAAgD;CAC/D,MAAM,QAAQC,oBAAAA,SAAS,KAAK,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;CACvD,MAAM,UAAUA,oBAAAA,SAAS,KAAK,OAAO;CAErC,MAAM,OAAmB;EACvB,cAAc,QAAQ,OAAO,KAAK,IAAI,KAAA;EACtC,oBAAoB,KAAK,UAAU,OAAO,KAAK,OAAO,IAAI,KAAA;EAC1D,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;EAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;EACxC,UAAU,SAAS;EACnB,YAAY,KAAK;EACjB,YAAY,KAAK;CACnB;CAEA,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC,MAAM,UAAU,UAAU,KAAA,CAAS,IAAI,OAAO,KAAA;AAC3E;AAEA,SAAgB,eAEd,OACA,MACA,cACa;CACb,MAAM,EAAE,MAAM,MAAM,cAAcC,mBAAAA,YAAY,MAAM,MAAM,MAAM,UAAU;CAE1E,OAAO;EACL,IAAI,MAAM,KAAK,mBAAmB,MAAM;EACxC;EAGA,mBAAmB,SAAS,WAAW,MAAM,OAAO,KAAA;EACpD,cAAc,KAAK,IAAI;EACvB,YAAY,MAAM,KAAK;EACvB,eAAe,gBAAgB,MAAM,UAAU,YAAY;EAC3D,MAAM,QAAQ,IAAI;EAClB,MAAM;CACR;AACF;AAEA,eAAsB,WACpB,QACA,SAEA,QACA,OAAyB,CAAC,GAC1B,eAAe,OACf,cACA;CACA,MAAM,MAA6B;EACjC,eAAe;EACf,QAAQ,OACL,QAAQ,UAAU,CAACC,qCAAAA,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,QAAQ,UAAU,CAACC,mBAAAA,cAAc,SAAS,MAAM,IAAI,CAAC,CAAC,CACtD,KAAK,UAAU,eAAe,OAAO,MAAM,YAAY,CAAC;CAC7D;CAEA,IAAI,IAAI,OAAO,WAAW,GAAG;CAE7B,IAAI;EACF,MAAM,WAAW,OAAA,GAAA,cAAA,MAAA,CAAY,GAAG,SAAS,OAAO,WAAW;GACzD,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,eAAe,UAAU;GAC3B;GACA,MAAM,KAAK,UAAU,GAAG;EAC1B,CAAC;EAED,IAAI,SAAS,IAAI;EACjB,MAAM,EAAE,WAAW;EACnB,MAAM,UAAU,MAAM,SAAS,KAAK;EACpC,QAAQ,MAAM,6CAA6C,OAAO,UAAU,SAAS;CACvF,SAAS,OAAO;EACd,QAAQ,MAAM,oDAAoD,KAAK;CACzE;AACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TrackEvent, UserProvidedData } from "../track/types.cjs";
|
|
2
|
+
import { EventActionSource } from "./action-source.cjs";
|
|
2
3
|
import { EventData } from "../track/oaiq.cjs";
|
|
3
4
|
//#region src/server/openai-conversions-api.d.ts
|
|
4
5
|
type ActionSource = 'web' | 'mobile_app' | 'offline' | 'physical_store' | 'phone_call' | 'email' | 'other';
|
|
@@ -40,8 +41,8 @@ interface CreateOpenAIEventsDTO {
|
|
|
40
41
|
validate_only?: boolean;
|
|
41
42
|
events: OpenAIEvent[];
|
|
42
43
|
}
|
|
43
|
-
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData): OpenAIEvent;
|
|
44
|
-
declare function sendEvents(apiKey: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, validateOnly?: boolean): Promise<void>;
|
|
44
|
+
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData, actionSource?: EventActionSource): OpenAIEvent;
|
|
45
|
+
declare function sendEvents(apiKey: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, validateOnly?: boolean, actionSource?: EventActionSource): Promise<void>;
|
|
45
46
|
//#endregion
|
|
46
47
|
export { CreateOpenAIEventsDTO, OpenAIEvent, OpenAIUser, getServerEvent, sendEvents };
|
|
47
48
|
//# sourceMappingURL=openai-conversions-api.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-conversions-api.d.cts","names":[],"sources":["../../src/server/openai-conversions-api.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"openai-conversions-api.d.cts","names":[],"sources":["../../src/server/openai-conversions-api.ts"],"mappings":";;;;KAeK;;;;;UAaY;EACf;EACA;;EAEA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;EACA,gBAAgB;;EAEhB;;EAEA;EACA,OAAO;EACP,MAAM;;UAGS;;EAEf;EACA,QAAQ;;iBAwCM,eAEd,OAAO,iBACP,MAAM,kBACN,eAAe,oBACd;iBAiBmB,WACpB,gBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,wBACA,eAAe,oBAAiB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TrackEvent, UserProvidedData } from "../track/types.mjs";
|
|
2
|
+
import { EventActionSource } from "./action-source.mjs";
|
|
2
3
|
import { EventData } from "../track/oaiq.mjs";
|
|
3
4
|
//#region src/server/openai-conversions-api.d.ts
|
|
4
5
|
type ActionSource = 'web' | 'mobile_app' | 'offline' | 'physical_store' | 'phone_call' | 'email' | 'other';
|
|
@@ -40,8 +41,8 @@ interface CreateOpenAIEventsDTO {
|
|
|
40
41
|
validate_only?: boolean;
|
|
41
42
|
events: OpenAIEvent[];
|
|
42
43
|
}
|
|
43
|
-
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData): OpenAIEvent;
|
|
44
|
-
declare function sendEvents(apiKey: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, validateOnly?: boolean): Promise<void>;
|
|
44
|
+
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData, actionSource?: EventActionSource): OpenAIEvent;
|
|
45
|
+
declare function sendEvents(apiKey: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, validateOnly?: boolean, actionSource?: EventActionSource): Promise<void>;
|
|
45
46
|
//#endregion
|
|
46
47
|
export { CreateOpenAIEventsDTO, OpenAIEvent, OpenAIUser, getServerEvent, sendEvents };
|
|
47
48
|
//# sourceMappingURL=openai-conversions-api.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-conversions-api.d.mts","names":[],"sources":["../../src/server/openai-conversions-api.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"openai-conversions-api.d.mts","names":[],"sources":["../../src/server/openai-conversions-api.ts"],"mappings":";;;;KAeK;;;;;UAaY;EACf;EACA;;EAEA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;EACA,gBAAgB;;EAEhB;;EAEA;EACA,OAAO;EACP,MAAM;;UAGS;;EAEf;EACA,QAAQ;;iBAwCM,eAEd,OAAO,iBACP,MAAM,kBACN,eAAe,oBACd;iBAiBmB,WACpB,gBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,wBACA,eAAe,oBAAiB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IGNORED_EVENTS } from "../third-parties/ignored-events.mjs";
|
|
2
|
+
import { resolveActionSource } from "./action-source.mjs";
|
|
2
3
|
import { getFirst } from "../utils/field.mjs";
|
|
3
4
|
import { NON_AD_EVENTS, mapOAIEvent } from "../track/oaiq.mjs";
|
|
4
5
|
import { fetch } from "@shware/utils";
|
|
@@ -13,8 +14,8 @@ const ENDPOINT = "https://bzr.openai.com/v1/events";
|
|
|
13
14
|
function sha256(value) {
|
|
14
15
|
return createHash("sha256").update(value).digest("hex");
|
|
15
16
|
}
|
|
16
|
-
function mapActionSource(
|
|
17
|
-
switch (
|
|
17
|
+
function mapActionSource(platform, actionSource) {
|
|
18
|
+
switch (resolveActionSource(platform, actionSource)) {
|
|
18
19
|
case "web": return "web";
|
|
19
20
|
case "app": return "mobile_app";
|
|
20
21
|
case "offline": return "offline";
|
|
@@ -35,7 +36,7 @@ function getUser(data) {
|
|
|
35
36
|
};
|
|
36
37
|
return Object.values(user).some((value) => value !== void 0) ? user : void 0;
|
|
37
38
|
}
|
|
38
|
-
function getServerEvent(event, data) {
|
|
39
|
+
function getServerEvent(event, data, actionSource) {
|
|
39
40
|
const { type, data: eventData } = mapOAIEvent(event.name, event.properties);
|
|
40
41
|
return {
|
|
41
42
|
id: event.tags.idempotency_key ?? event.id,
|
|
@@ -43,15 +44,15 @@ function getServerEvent(event, data) {
|
|
|
43
44
|
custom_event_name: type === "custom" ? event.name : void 0,
|
|
44
45
|
timestamp_ms: Date.now(),
|
|
45
46
|
source_url: event.tags.source_url,
|
|
46
|
-
action_source: mapActionSource(event.
|
|
47
|
+
action_source: mapActionSource(event.platform, actionSource),
|
|
47
48
|
user: getUser(data),
|
|
48
49
|
data: eventData
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
async function sendEvents(apiKey, pixelId, events, data = {}, validateOnly = false) {
|
|
52
|
+
async function sendEvents(apiKey, pixelId, events, data = {}, validateOnly = false, actionSource) {
|
|
52
53
|
const dto = {
|
|
53
54
|
validate_only: validateOnly,
|
|
54
|
-
events: events.filter((event) => !IGNORED_EVENTS.includes(event.name)).filter((event) => !NON_AD_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data))
|
|
55
|
+
events: events.filter((event) => !IGNORED_EVENTS.includes(event.name)).filter((event) => !NON_AD_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, actionSource))
|
|
55
56
|
};
|
|
56
57
|
if (dto.events.length === 0) return;
|
|
57
58
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-conversions-api.mjs","names":[],"sources":["../../src/server/openai-conversions-api.ts"],"sourcesContent":["/**\n * OpenAI Conversions API\n * https://developers.openai.com/ads/conversions-api\n * https://developers.openai.com/ads/supported-events\n */\nimport { createHash } from 'node:crypto';\nimport { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type EventData, NON_AD_EVENTS, mapOAIEvent } from '../track/oaiq';\nimport type {
|
|
1
|
+
{"version":3,"file":"openai-conversions-api.mjs","names":[],"sources":["../../src/server/openai-conversions-api.ts"],"sourcesContent":["/**\n * OpenAI Conversions API\n * https://developers.openai.com/ads/conversions-api\n * https://developers.openai.com/ads/supported-events\n */\nimport { createHash } from 'node:crypto';\nimport { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type EventData, NON_AD_EVENTS, mapOAIEvent } from '../track/oaiq';\nimport type { Platform, TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { type EventActionSource, resolveActionSource } from './action-source';\n\nconst ENDPOINT = 'https://bzr.openai.com/v1/events';\n\ntype ActionSource =\n | 'web'\n | 'mobile_app'\n | 'offline'\n | 'physical_store'\n | 'phone_call'\n | 'email'\n | 'other';\n\n/**\n * User/identity fields. Email and external id must be sent as lowercase 64-char SHA-256 hex\n * strings; geographic, IP, and user-agent fields are sent as raw values.\n */\nexport interface OpenAIUser {\n email_sha256?: string;\n external_id_sha256?: string;\n /** Two-letter ISO 3166-1 country code (e.g. \"US\"). */\n country?: string;\n city?: string;\n zip_code?: string;\n ip_address?: string;\n user_agent?: string;\n}\n\nexport interface OpenAIEvent {\n /** Unique event id; combined with `type` for deduplication against pixel events. */\n id: string;\n /** Standard event name or `custom`. */\n type: string;\n /** Required when `type` is `custom`. */\n custom_event_name?: string;\n /** Event timestamp in ms; must be within 7 days and no more than 10 minutes in the future. */\n timestamp_ms: number;\n /** Required for `action_source: \"web\"`. */\n source_url?: string;\n action_source?: ActionSource;\n /** OpenAI-provided privacy-preserving identifier. */\n oppref?: string;\n /** When true, opts the event out of personalization. */\n opt_out?: boolean;\n user?: OpenAIUser;\n data: EventData;\n}\n\nexport interface CreateOpenAIEventsDTO {\n /** When true, validates the events without persisting them. */\n validate_only?: boolean;\n events: OpenAIEvent[];\n}\n\nfunction sha256(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nfunction mapActionSource(\n platform: Platform,\n actionSource?: EventActionSource\n): ActionSource | undefined {\n switch (resolveActionSource(platform, actionSource)) {\n case 'web':\n return 'web';\n case 'app':\n return 'mobile_app';\n case 'offline':\n return 'offline';\n default:\n return undefined;\n }\n}\n\nfunction getUser(data: UserProvidedData): OpenAIUser | undefined {\n const email = getFirst(data.email)?.trim().toLowerCase();\n const address = getFirst(data.address);\n\n const user: OpenAIUser = {\n email_sha256: email ? sha256(email) : undefined,\n external_id_sha256: data.user_id ? sha256(data.user_id) : undefined,\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n ip_address: data.ip_address,\n user_agent: data.user_agent,\n };\n\n return Object.values(user).some((value) => value !== undefined) ? user : undefined;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n actionSource?: EventActionSource\n): OpenAIEvent {\n const { type, data: eventData } = mapOAIEvent(event.name, event.properties);\n\n return {\n id: event.tags.idempotency_key ?? event.id,\n type,\n // For custom events the original track name is the OpenAI custom_event_name; this matches\n // the browser pixel so the two deduplicate. Standard events omit it.\n custom_event_name: type === 'custom' ? event.name : undefined,\n timestamp_ms: Date.now(),\n source_url: event.tags.source_url,\n action_source: mapActionSource(event.platform, actionSource),\n user: getUser(data),\n data: eventData,\n };\n}\n\nexport async function sendEvents(\n apiKey: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n validateOnly = false,\n actionSource?: EventActionSource\n) {\n const dto: CreateOpenAIEventsDTO = {\n validate_only: validateOnly,\n events: events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => !NON_AD_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, actionSource)),\n };\n\n if (dto.events.length === 0) return;\n\n try {\n const response = await fetch(`${ENDPOINT}?pid=${pixelId}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(dto),\n });\n\n if (response.ok) return;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send OpenAI conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n console.error('Failed to send OpenAI conversion, network error:', error);\n }\n}\n"],"mappings":";;;;;;;;;;;;AAaA,MAAM,WAAW;AAoDjB,SAAS,OAAO,OAAuB;CACrC,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACxD;AAEA,SAAS,gBACP,UACA,cAC0B;CAC1B,QAAQ,oBAAoB,UAAU,YAAY,GAAlD;EACE,KAAK,OACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,SACE;CACJ;AACF;AAEA,SAAS,QAAQ,MAAgD;CAC/D,MAAM,QAAQ,SAAS,KAAK,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;CACvD,MAAM,UAAU,SAAS,KAAK,OAAO;CAErC,MAAM,OAAmB;EACvB,cAAc,QAAQ,OAAO,KAAK,IAAI,KAAA;EACtC,oBAAoB,KAAK,UAAU,OAAO,KAAK,OAAO,IAAI,KAAA;EAC1D,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;EAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;EACxC,UAAU,SAAS;EACnB,YAAY,KAAK;EACjB,YAAY,KAAK;CACnB;CAEA,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC,MAAM,UAAU,UAAU,KAAA,CAAS,IAAI,OAAO,KAAA;AAC3E;AAEA,SAAgB,eAEd,OACA,MACA,cACa;CACb,MAAM,EAAE,MAAM,MAAM,cAAc,YAAY,MAAM,MAAM,MAAM,UAAU;CAE1E,OAAO;EACL,IAAI,MAAM,KAAK,mBAAmB,MAAM;EACxC;EAGA,mBAAmB,SAAS,WAAW,MAAM,OAAO,KAAA;EACpD,cAAc,KAAK,IAAI;EACvB,YAAY,MAAM,KAAK;EACvB,eAAe,gBAAgB,MAAM,UAAU,YAAY;EAC3D,MAAM,QAAQ,IAAI;EAClB,MAAM;CACR;AACF;AAEA,eAAsB,WACpB,QACA,SAEA,QACA,OAAyB,CAAC,GAC1B,eAAe,OACf,cACA;CACA,MAAM,MAA6B;EACjC,eAAe;EACf,QAAQ,OACL,QAAQ,UAAU,CAAC,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,QAAQ,UAAU,CAAC,cAAc,SAAS,MAAM,IAAI,CAAC,CAAC,CACtD,KAAK,UAAU,eAAe,OAAO,MAAM,YAAY,CAAC;CAC7D;CAEA,IAAI,IAAI,OAAO,WAAW,GAAG;CAE7B,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,SAAS,OAAO,WAAW;GACzD,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,eAAe,UAAU;GAC3B;GACA,MAAM,KAAK,UAAU,GAAG;EAC1B,CAAC;EAED,IAAI,SAAS,IAAI;EACjB,MAAM,EAAE,WAAW;EACnB,MAAM,UAAU,MAAM,SAAS,KAAK;EACpC,QAAQ,MAAM,6CAA6C,OAAO,UAAU,SAAS;CACvF,SAAS,OAAO;EACd,QAAQ,MAAM,oDAAoD,KAAK;CACzE;AACF"}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_third_parties_ignored_events = require("../third-parties/ignored-events.cjs");
|
|
3
|
+
const require_server_action_source = require("./action-source.cjs");
|
|
3
4
|
const require_track_rdt = require("../track/rdt.cjs");
|
|
4
5
|
const require_utils_field = require("../utils/field.cjs");
|
|
5
6
|
let _shware_utils = require("@shware/utils");
|
|
6
7
|
//#region src/server/reddit-conversions-api.ts
|
|
7
|
-
function getServerEvent(event, data) {
|
|
8
|
+
function getServerEvent(event, data, actionSource) {
|
|
8
9
|
const { id, name, properties, tags, platform } = event;
|
|
9
10
|
const [type, params] = require_track_rdt.mapRDTEvent(name, properties, id);
|
|
11
|
+
const source = require_server_action_source.resolveActionSource(platform, actionSource);
|
|
10
12
|
return {
|
|
11
13
|
click_id: tags.rdt_cid,
|
|
12
14
|
event_at: Date.now(),
|
|
13
|
-
action_source:
|
|
15
|
+
action_source: source === "web" ? "WEBSITE" : source === "app" ? "APP" : "UNKNOWN",
|
|
14
16
|
type: {
|
|
15
17
|
tracking_type: type === "Custom" ? "CUSTOM" : require_track_rdt.mapServerStandardEvent(type),
|
|
16
18
|
custom_event_name: type === "Custom" ? params.customEventName : void 0
|
|
@@ -38,10 +40,10 @@ function getServerEvent(event, data) {
|
|
|
38
40
|
}
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
|
-
async function sendEvents(accessToken, pixelId, events, data = {}, testId) {
|
|
43
|
+
async function sendEvents(accessToken, pixelId, events, data = {}, testId, actionSource) {
|
|
42
44
|
const dto = { data: {
|
|
43
45
|
test_id: testId,
|
|
44
|
-
events: events.filter((event) => !require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data))
|
|
46
|
+
events: events.filter((event) => !require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, actionSource))
|
|
45
47
|
} };
|
|
46
48
|
if (dto.data.events.length === 0) return;
|
|
47
49
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reddit-conversions-api.cjs","names":["mapRDTEvent","mapServerStandardEvent","getFirst","IGNORED_EVENTS"],"sources":["../../src/server/reddit-conversions-api.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type ServerStandardEvent, mapRDTEvent, mapServerStandardEvent } from '../track/rdt';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\n\n/**\n * https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events\n * https://business.reddithelp.com/s/article/map-a-catalog-to-a-signal-source\n */\nexport interface RedditEvent {\n /** Match keys: Share user identifiers to match conversions to a Reddit ad engagement. */\n click_id?: string;\n\n /** Unix epoch timestamp in milliseconds, event_at can't be older than seven days. */\n event_at: number;\n\n action_source: 'WEBSITE' | 'APP' | (string & {});\n\n type: {\n tracking_type: ServerStandardEvent | 'CUSTOM';\n custom_event_name?: string;\n };\n\n /**\n * Event metadata\n * Share as much additional information about your conversion event as you'd like. If you're\n * using the Conversions API with the pixel, conversion_id is required for deduplication.\n */\n metadata?: {\n conversion_id?: string;\n currency?: string; // ISO 4217 3-letter currency code\n item_count?: number;\n value?: number;\n products?: { id: string; name?: string; category?: string }[];\n };\n\n user?: {\n email?: string;\n external_id?: string;\n ip_address?: string;\n phone_number?: string;\n user_agent?: string;\n\n /** The Identifier for Advertisers (IDFA) of the user's Apple device. */\n idfa?: string;\n\n /** The Android Advertising ID (AAID) of the user's Android device. */\n aaid?: string;\n /**\n * The value from the first-party Pixel _rdt_uuid cookie on your domain. Note that it is in\n * the {timestamp}.{uuid} format. You may use the full value or just the UUID portion.\n * Example: 1684189007728.7c73f2ae-a433-4d7b-9838-f467da98f48e\n */\n uuid?: string;\n\n screen_dimensions?: { width: number; height: number };\n\n /**\n * A structure of data processing options to specify the processing type for the event\n * https://business.reddithelp.com/s/article/Limited-Data-Use\n */\n data_processing_options?: {\n country: string;\n region: string;\n modes: string[] | ['LDU'];\n };\n };\n}\n\nexport interface CreateRedditEventDTO {\n data: { test_id?: string; events: RedditEvent[] };\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData\n): RedditEvent {\n const { id, name, properties, tags, platform } = event;\n const [type, params] = mapRDTEvent(name, properties, id);\n\n return {\n click_id: tags.rdt_cid,\n event_at: Date.now(),\n action_source:
|
|
1
|
+
{"version":3,"file":"reddit-conversions-api.cjs","names":["mapRDTEvent","resolveActionSource","mapServerStandardEvent","getFirst","IGNORED_EVENTS"],"sources":["../../src/server/reddit-conversions-api.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type ServerStandardEvent, mapRDTEvent, mapServerStandardEvent } from '../track/rdt';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { type EventActionSource, resolveActionSource } from './action-source';\n\n/**\n * https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events\n * https://business.reddithelp.com/s/article/map-a-catalog-to-a-signal-source\n */\nexport interface RedditEvent {\n /** Match keys: Share user identifiers to match conversions to a Reddit ad engagement. */\n click_id?: string;\n\n /** Unix epoch timestamp in milliseconds, event_at can't be older than seven days. */\n event_at: number;\n\n action_source: 'WEBSITE' | 'APP' | (string & {});\n\n type: {\n tracking_type: ServerStandardEvent | 'CUSTOM';\n custom_event_name?: string;\n };\n\n /**\n * Event metadata\n * Share as much additional information about your conversion event as you'd like. If you're\n * using the Conversions API with the pixel, conversion_id is required for deduplication.\n */\n metadata?: {\n conversion_id?: string;\n currency?: string; // ISO 4217 3-letter currency code\n item_count?: number;\n value?: number;\n products?: { id: string; name?: string; category?: string }[];\n };\n\n user?: {\n email?: string;\n external_id?: string;\n ip_address?: string;\n phone_number?: string;\n user_agent?: string;\n\n /** The Identifier for Advertisers (IDFA) of the user's Apple device. */\n idfa?: string;\n\n /** The Android Advertising ID (AAID) of the user's Android device. */\n aaid?: string;\n /**\n * The value from the first-party Pixel _rdt_uuid cookie on your domain. Note that it is in\n * the {timestamp}.{uuid} format. You may use the full value or just the UUID portion.\n * Example: 1684189007728.7c73f2ae-a433-4d7b-9838-f467da98f48e\n */\n uuid?: string;\n\n screen_dimensions?: { width: number; height: number };\n\n /**\n * A structure of data processing options to specify the processing type for the event\n * https://business.reddithelp.com/s/article/Limited-Data-Use\n */\n data_processing_options?: {\n country: string;\n region: string;\n modes: string[] | ['LDU'];\n };\n };\n}\n\nexport interface CreateRedditEventDTO {\n data: { test_id?: string; events: RedditEvent[] };\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n actionSource?: EventActionSource\n): RedditEvent {\n const { id, name, properties, tags, platform } = event;\n const [type, params] = mapRDTEvent(name, properties, id);\n // Reddit documents WEBSITE and APP only, so an offline conversion shares the fallback with an\n // undeterminable platform rather than inventing an enum value the API may reject.\n const source = resolveActionSource(platform, actionSource);\n\n return {\n click_id: tags.rdt_cid,\n event_at: Date.now(),\n action_source: source === 'web' ? 'WEBSITE' : source === 'app' ? 'APP' : 'UNKNOWN',\n type: {\n tracking_type: type === 'Custom' ? 'CUSTOM' : mapServerStandardEvent(type),\n custom_event_name: type === 'Custom' ? params.customEventName : undefined,\n },\n metadata: {\n conversion_id: id,\n currency:\n 'currency' in params && typeof params.currency === 'string'\n ? params.currency.toUpperCase()\n : undefined,\n item_count:\n 'itemCount' in params && typeof params.itemCount === 'number'\n ? params.itemCount\n : undefined,\n value: 'value' in params && typeof params.value === 'number' ? params.value : undefined,\n products:\n 'products' in params && Array.isArray(params.products) && params.products.length > 0\n ? params.products\n : undefined,\n },\n user: {\n email: getFirst(data.email),\n external_id: data.user_id,\n ip_address: data.ip_address,\n phone_number: getFirst(data.phone_number),\n user_agent: data.user_agent,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n aaid: platform === 'android' ? tags.advertising_id : undefined,\n uuid: tags.rdt_uuid,\n screen_dimensions:\n tags.screen_width && tags.screen_height\n ? { width: tags.screen_width, height: tags.screen_height }\n : undefined,\n },\n };\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n testId?: string,\n actionSource?: EventActionSource\n) {\n const dto: CreateRedditEventDTO = {\n data: {\n test_id: testId,\n events: events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, actionSource)),\n },\n };\n\n if (dto.data.events.length === 0) return;\n\n try {\n const response = await fetch(\n `https://ads-api.reddit.com/api/v3/pixels/${pixelId}/conversion_events`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(dto),\n }\n );\n if (response.ok) return;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Reddit conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n console.error('Failed to send Reddit conversion, network error:', error);\n }\n}\n"],"mappings":";;;;;;;AA2EA,SAAgB,eAEd,OACA,MACA,cACa;CACb,MAAM,EAAE,IAAI,MAAM,YAAY,MAAM,aAAa;CACjD,MAAM,CAAC,MAAM,UAAUA,kBAAAA,YAAY,MAAM,YAAY,EAAE;CAGvD,MAAM,SAASC,6BAAAA,oBAAoB,UAAU,YAAY;CAEzD,OAAO;EACL,UAAU,KAAK;EACf,UAAU,KAAK,IAAI;EACnB,eAAe,WAAW,QAAQ,YAAY,WAAW,QAAQ,QAAQ;EACzE,MAAM;GACJ,eAAe,SAAS,WAAW,WAAWC,kBAAAA,uBAAuB,IAAI;GACzE,mBAAmB,SAAS,WAAW,OAAO,kBAAkB,KAAA;EAClE;EACA,UAAU;GACR,eAAe;GACf,UACE,cAAc,UAAU,OAAO,OAAO,aAAa,WAC/C,OAAO,SAAS,YAAY,IAC5B,KAAA;GACN,YACE,eAAe,UAAU,OAAO,OAAO,cAAc,WACjD,OAAO,YACP,KAAA;GACN,OAAO,WAAW,UAAU,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,KAAA;GAC9E,UACE,cAAc,UAAU,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,SAAS,IAC/E,OAAO,WACP,KAAA;EACR;EACA,MAAM;GACJ,OAAOC,oBAAAA,SAAS,KAAK,KAAK;GAC1B,aAAa,KAAK;GAClB,YAAY,KAAK;GACjB,cAAcA,oBAAAA,SAAS,KAAK,YAAY;GACxC,YAAY,KAAK;GACjB,MAAM,aAAa,QAAQ,KAAK,iBAAiB,KAAA;GACjD,MAAM,aAAa,YAAY,KAAK,iBAAiB,KAAA;GACrD,MAAM,KAAK;GACX,mBACE,KAAK,gBAAgB,KAAK,gBACtB;IAAE,OAAO,KAAK;IAAc,QAAQ,KAAK;GAAc,IACvD,KAAA;EACR;CACF;AACF;AAEA,eAAsB,WACpB,aACA,SAEA,QACA,OAAyB,CAAC,GAC1B,QACA,cACA;CACA,MAAM,MAA4B,EAChC,MAAM;EACJ,SAAS;EACT,QAAQ,OACL,QAAQ,UAAU,CAACC,qCAAAA,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,KAAK,UAAU,eAAe,OAAO,MAAM,YAAY,CAAC;CAC7D,EACF;CAEA,IAAI,IAAI,KAAK,OAAO,WAAW,GAAG;CAElC,IAAI;EACF,MAAM,WAAW,OAAA,GAAA,cAAA,MAAA,CACf,4CAA4C,QAAQ,qBACpD;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,eAAe,UAAU;GAC3B;GACA,MAAM,KAAK,UAAU,GAAG;EAC1B,CACF;EACA,IAAI,SAAS,IAAI;EACjB,MAAM,EAAE,WAAW;EACnB,MAAM,UAAU,MAAM,SAAS,KAAK;EACpC,QAAQ,MAAM,6CAA6C,OAAO,UAAU,SAAS;CACvF,SAAS,OAAO;EACd,QAAQ,MAAM,oDAAoD,KAAK;CACzE;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TrackEvent, UserProvidedData } from "../track/types.cjs";
|
|
2
2
|
import { ServerStandardEvent } from "../track/rdt.cjs";
|
|
3
|
+
import { EventActionSource } from "./action-source.cjs";
|
|
3
4
|
//#region src/server/reddit-conversions-api.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events
|
|
@@ -68,8 +69,8 @@ interface CreateRedditEventDTO {
|
|
|
68
69
|
events: RedditEvent[];
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
|
-
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData): RedditEvent;
|
|
72
|
-
declare function sendEvents(accessToken: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, testId?: string): Promise<void>;
|
|
72
|
+
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData, actionSource?: EventActionSource): RedditEvent;
|
|
73
|
+
declare function sendEvents(accessToken: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, testId?: string, actionSource?: EventActionSource): Promise<void>;
|
|
73
74
|
//#endregion
|
|
74
75
|
export { CreateRedditEventDTO, RedditEvent, getServerEvent, sendEvents };
|
|
75
76
|
//# sourceMappingURL=reddit-conversions-api.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reddit-conversions-api.d.cts","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"reddit-conversions-api.d.cts","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"mappings":";;;;;;;;UAWiB;;EAEf;;EAGA;EAEA;EAEA;IACE,eAAe;IACf;;;;;;;EAQF;IACE;IACA;IACA;IACA;IACA;MAAa;MAAY;MAAe;;;EAG1C;IACE;IACA;IACA;IACA;IACA;;IAGA;;IAGA;;;;;;IAMA;IAEA;MAAsB;MAAe;;;;;;IAMrC;MACE;MACA;MACA;;;;UAKW;EACf;IAAQ;IAAkB,QAAQ;;;iBAGpB,eAEd,OAAO,iBACP,MAAM,kBACN,eAAe,oBACd;iBAgDmB,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,iBACA,eAAe,oBAAiB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TrackEvent, UserProvidedData } from "../track/types.mjs";
|
|
2
2
|
import { ServerStandardEvent } from "../track/rdt.mjs";
|
|
3
|
+
import { EventActionSource } from "./action-source.mjs";
|
|
3
4
|
//#region src/server/reddit-conversions-api.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events
|
|
@@ -68,8 +69,8 @@ interface CreateRedditEventDTO {
|
|
|
68
69
|
events: RedditEvent[];
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
|
-
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData): RedditEvent;
|
|
72
|
-
declare function sendEvents(accessToken: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, testId?: string): Promise<void>;
|
|
72
|
+
declare function getServerEvent(event: TrackEvent<any>, data: UserProvidedData, actionSource?: EventActionSource): RedditEvent;
|
|
73
|
+
declare function sendEvents(accessToken: string, pixelId: string, events: TrackEvent<any>[], data?: UserProvidedData, testId?: string, actionSource?: EventActionSource): Promise<void>;
|
|
73
74
|
//#endregion
|
|
74
75
|
export { CreateRedditEventDTO, RedditEvent, getServerEvent, sendEvents };
|
|
75
76
|
//# sourceMappingURL=reddit-conversions-api.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reddit-conversions-api.d.mts","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"reddit-conversions-api.d.mts","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"mappings":";;;;;;;;UAWiB;;EAEf;;EAGA;EAEA;EAEA;IACE,eAAe;IACf;;;;;;;EAQF;IACE;IACA;IACA;IACA;IACA;MAAa;MAAY;MAAe;;;EAG1C;IACE;IACA;IACA;IACA;IACA;;IAGA;;IAGA;;;;;;IAMA;IAEA;MAAsB;MAAe;;;;;;IAMrC;MACE;MACA;MACA;;;;UAKW;EACf;IAAQ;IAAkB,QAAQ;;;iBAGpB,eAEd,OAAO,iBACP,MAAM,kBACN,eAAe,oBACd;iBAgDmB,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,iBACA,eAAe,oBAAiB"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { IGNORED_EVENTS } from "../third-parties/ignored-events.mjs";
|
|
2
|
+
import { resolveActionSource } from "./action-source.mjs";
|
|
2
3
|
import { mapRDTEvent, mapServerStandardEvent } from "../track/rdt.mjs";
|
|
3
4
|
import { getFirst } from "../utils/field.mjs";
|
|
4
5
|
import { fetch } from "@shware/utils";
|
|
5
6
|
//#region src/server/reddit-conversions-api.ts
|
|
6
|
-
function getServerEvent(event, data) {
|
|
7
|
+
function getServerEvent(event, data, actionSource) {
|
|
7
8
|
const { id, name, properties, tags, platform } = event;
|
|
8
9
|
const [type, params] = mapRDTEvent(name, properties, id);
|
|
10
|
+
const source = resolveActionSource(platform, actionSource);
|
|
9
11
|
return {
|
|
10
12
|
click_id: tags.rdt_cid,
|
|
11
13
|
event_at: Date.now(),
|
|
12
|
-
action_source:
|
|
14
|
+
action_source: source === "web" ? "WEBSITE" : source === "app" ? "APP" : "UNKNOWN",
|
|
13
15
|
type: {
|
|
14
16
|
tracking_type: type === "Custom" ? "CUSTOM" : mapServerStandardEvent(type),
|
|
15
17
|
custom_event_name: type === "Custom" ? params.customEventName : void 0
|
|
@@ -37,10 +39,10 @@ function getServerEvent(event, data) {
|
|
|
37
39
|
}
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
|
-
async function sendEvents(accessToken, pixelId, events, data = {}, testId) {
|
|
42
|
+
async function sendEvents(accessToken, pixelId, events, data = {}, testId, actionSource) {
|
|
41
43
|
const dto = { data: {
|
|
42
44
|
test_id: testId,
|
|
43
|
-
events: events.filter((event) => !IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data))
|
|
45
|
+
events: events.filter((event) => !IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, actionSource))
|
|
44
46
|
} };
|
|
45
47
|
if (dto.data.events.length === 0) return;
|
|
46
48
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reddit-conversions-api.mjs","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type ServerStandardEvent, mapRDTEvent, mapServerStandardEvent } from '../track/rdt';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\n\n/**\n * https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events\n * https://business.reddithelp.com/s/article/map-a-catalog-to-a-signal-source\n */\nexport interface RedditEvent {\n /** Match keys: Share user identifiers to match conversions to a Reddit ad engagement. */\n click_id?: string;\n\n /** Unix epoch timestamp in milliseconds, event_at can't be older than seven days. */\n event_at: number;\n\n action_source: 'WEBSITE' | 'APP' | (string & {});\n\n type: {\n tracking_type: ServerStandardEvent | 'CUSTOM';\n custom_event_name?: string;\n };\n\n /**\n * Event metadata\n * Share as much additional information about your conversion event as you'd like. If you're\n * using the Conversions API with the pixel, conversion_id is required for deduplication.\n */\n metadata?: {\n conversion_id?: string;\n currency?: string; // ISO 4217 3-letter currency code\n item_count?: number;\n value?: number;\n products?: { id: string; name?: string; category?: string }[];\n };\n\n user?: {\n email?: string;\n external_id?: string;\n ip_address?: string;\n phone_number?: string;\n user_agent?: string;\n\n /** The Identifier for Advertisers (IDFA) of the user's Apple device. */\n idfa?: string;\n\n /** The Android Advertising ID (AAID) of the user's Android device. */\n aaid?: string;\n /**\n * The value from the first-party Pixel _rdt_uuid cookie on your domain. Note that it is in\n * the {timestamp}.{uuid} format. You may use the full value or just the UUID portion.\n * Example: 1684189007728.7c73f2ae-a433-4d7b-9838-f467da98f48e\n */\n uuid?: string;\n\n screen_dimensions?: { width: number; height: number };\n\n /**\n * A structure of data processing options to specify the processing type for the event\n * https://business.reddithelp.com/s/article/Limited-Data-Use\n */\n data_processing_options?: {\n country: string;\n region: string;\n modes: string[] | ['LDU'];\n };\n };\n}\n\nexport interface CreateRedditEventDTO {\n data: { test_id?: string; events: RedditEvent[] };\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData\n): RedditEvent {\n const { id, name, properties, tags, platform } = event;\n const [type, params] = mapRDTEvent(name, properties, id);\n\n return {\n click_id: tags.rdt_cid,\n event_at: Date.now(),\n action_source:
|
|
1
|
+
{"version":3,"file":"reddit-conversions-api.mjs","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type ServerStandardEvent, mapRDTEvent, mapServerStandardEvent } from '../track/rdt';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { type EventActionSource, resolveActionSource } from './action-source';\n\n/**\n * https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events\n * https://business.reddithelp.com/s/article/map-a-catalog-to-a-signal-source\n */\nexport interface RedditEvent {\n /** Match keys: Share user identifiers to match conversions to a Reddit ad engagement. */\n click_id?: string;\n\n /** Unix epoch timestamp in milliseconds, event_at can't be older than seven days. */\n event_at: number;\n\n action_source: 'WEBSITE' | 'APP' | (string & {});\n\n type: {\n tracking_type: ServerStandardEvent | 'CUSTOM';\n custom_event_name?: string;\n };\n\n /**\n * Event metadata\n * Share as much additional information about your conversion event as you'd like. If you're\n * using the Conversions API with the pixel, conversion_id is required for deduplication.\n */\n metadata?: {\n conversion_id?: string;\n currency?: string; // ISO 4217 3-letter currency code\n item_count?: number;\n value?: number;\n products?: { id: string; name?: string; category?: string }[];\n };\n\n user?: {\n email?: string;\n external_id?: string;\n ip_address?: string;\n phone_number?: string;\n user_agent?: string;\n\n /** The Identifier for Advertisers (IDFA) of the user's Apple device. */\n idfa?: string;\n\n /** The Android Advertising ID (AAID) of the user's Android device. */\n aaid?: string;\n /**\n * The value from the first-party Pixel _rdt_uuid cookie on your domain. Note that it is in\n * the {timestamp}.{uuid} format. You may use the full value or just the UUID portion.\n * Example: 1684189007728.7c73f2ae-a433-4d7b-9838-f467da98f48e\n */\n uuid?: string;\n\n screen_dimensions?: { width: number; height: number };\n\n /**\n * A structure of data processing options to specify the processing type for the event\n * https://business.reddithelp.com/s/article/Limited-Data-Use\n */\n data_processing_options?: {\n country: string;\n region: string;\n modes: string[] | ['LDU'];\n };\n };\n}\n\nexport interface CreateRedditEventDTO {\n data: { test_id?: string; events: RedditEvent[] };\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n actionSource?: EventActionSource\n): RedditEvent {\n const { id, name, properties, tags, platform } = event;\n const [type, params] = mapRDTEvent(name, properties, id);\n // Reddit documents WEBSITE and APP only, so an offline conversion shares the fallback with an\n // undeterminable platform rather than inventing an enum value the API may reject.\n const source = resolveActionSource(platform, actionSource);\n\n return {\n click_id: tags.rdt_cid,\n event_at: Date.now(),\n action_source: source === 'web' ? 'WEBSITE' : source === 'app' ? 'APP' : 'UNKNOWN',\n type: {\n tracking_type: type === 'Custom' ? 'CUSTOM' : mapServerStandardEvent(type),\n custom_event_name: type === 'Custom' ? params.customEventName : undefined,\n },\n metadata: {\n conversion_id: id,\n currency:\n 'currency' in params && typeof params.currency === 'string'\n ? params.currency.toUpperCase()\n : undefined,\n item_count:\n 'itemCount' in params && typeof params.itemCount === 'number'\n ? params.itemCount\n : undefined,\n value: 'value' in params && typeof params.value === 'number' ? params.value : undefined,\n products:\n 'products' in params && Array.isArray(params.products) && params.products.length > 0\n ? params.products\n : undefined,\n },\n user: {\n email: getFirst(data.email),\n external_id: data.user_id,\n ip_address: data.ip_address,\n phone_number: getFirst(data.phone_number),\n user_agent: data.user_agent,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n aaid: platform === 'android' ? tags.advertising_id : undefined,\n uuid: tags.rdt_uuid,\n screen_dimensions:\n tags.screen_width && tags.screen_height\n ? { width: tags.screen_width, height: tags.screen_height }\n : undefined,\n },\n };\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n testId?: string,\n actionSource?: EventActionSource\n) {\n const dto: CreateRedditEventDTO = {\n data: {\n test_id: testId,\n events: events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, actionSource)),\n },\n };\n\n if (dto.data.events.length === 0) return;\n\n try {\n const response = await fetch(\n `https://ads-api.reddit.com/api/v3/pixels/${pixelId}/conversion_events`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(dto),\n }\n );\n if (response.ok) return;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Reddit conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n console.error('Failed to send Reddit conversion, network error:', error);\n }\n}\n"],"mappings":";;;;;;AA2EA,SAAgB,eAEd,OACA,MACA,cACa;CACb,MAAM,EAAE,IAAI,MAAM,YAAY,MAAM,aAAa;CACjD,MAAM,CAAC,MAAM,UAAU,YAAY,MAAM,YAAY,EAAE;CAGvD,MAAM,SAAS,oBAAoB,UAAU,YAAY;CAEzD,OAAO;EACL,UAAU,KAAK;EACf,UAAU,KAAK,IAAI;EACnB,eAAe,WAAW,QAAQ,YAAY,WAAW,QAAQ,QAAQ;EACzE,MAAM;GACJ,eAAe,SAAS,WAAW,WAAW,uBAAuB,IAAI;GACzE,mBAAmB,SAAS,WAAW,OAAO,kBAAkB,KAAA;EAClE;EACA,UAAU;GACR,eAAe;GACf,UACE,cAAc,UAAU,OAAO,OAAO,aAAa,WAC/C,OAAO,SAAS,YAAY,IAC5B,KAAA;GACN,YACE,eAAe,UAAU,OAAO,OAAO,cAAc,WACjD,OAAO,YACP,KAAA;GACN,OAAO,WAAW,UAAU,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,KAAA;GAC9E,UACE,cAAc,UAAU,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,SAAS,IAC/E,OAAO,WACP,KAAA;EACR;EACA,MAAM;GACJ,OAAO,SAAS,KAAK,KAAK;GAC1B,aAAa,KAAK;GAClB,YAAY,KAAK;GACjB,cAAc,SAAS,KAAK,YAAY;GACxC,YAAY,KAAK;GACjB,MAAM,aAAa,QAAQ,KAAK,iBAAiB,KAAA;GACjD,MAAM,aAAa,YAAY,KAAK,iBAAiB,KAAA;GACrD,MAAM,KAAK;GACX,mBACE,KAAK,gBAAgB,KAAK,gBACtB;IAAE,OAAO,KAAK;IAAc,QAAQ,KAAK;GAAc,IACvD,KAAA;EACR;CACF;AACF;AAEA,eAAsB,WACpB,aACA,SAEA,QACA,OAAyB,CAAC,GAC1B,QACA,cACA;CACA,MAAM,MAA4B,EAChC,MAAM;EACJ,SAAS;EACT,QAAQ,OACL,QAAQ,UAAU,CAAC,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,KAAK,UAAU,eAAe,OAAO,MAAM,YAAY,CAAC;CAC7D,EACF;CAEA,IAAI,IAAI,KAAK,OAAO,WAAW,GAAG;CAElC,IAAI;EACF,MAAM,WAAW,MAAM,MACrB,4CAA4C,QAAQ,qBACpD;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,eAAe,UAAU;GAC3B;GACA,MAAM,KAAK,UAAU,GAAG;EAC1B,CACF;EACA,IAAI,SAAS,IAAI;EACjB,MAAM,EAAE,WAAW;EACnB,MAAM,UAAU,MAAM,SAAS,KAAK;EACpC,QAAQ,MAAM,6CAA6C,OAAO,UAAU,SAAS;CACvF,SAAS,OAAO;EACd,QAAQ,MAAM,oDAAoD,KAAK;CACzE;AACF"}
|
package/dist/setup/session.cjs
CHANGED
|
@@ -65,9 +65,35 @@ var Session = class {
|
|
|
65
65
|
this.focused = typeof document !== "undefined" ? document.hasFocus() : true;
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
|
-
|
|
68
|
+
let session;
|
|
69
|
+
/**
|
|
70
|
+
* The session, built the first time something asks for it.
|
|
71
|
+
*
|
|
72
|
+
* Deliberately not a module-scope `new Session()`. The constructor calls
|
|
73
|
+
* `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
|
|
74
|
+
* Cloudflare Workers reject that outside a request handler:
|
|
75
|
+
*
|
|
76
|
+
* Disallowed operation called within global scope. Asynchronous I/O
|
|
77
|
+
* (ex: fetch() or connect()), setting a timeout, and generating random
|
|
78
|
+
* values are not allowed within global scope.
|
|
79
|
+
*
|
|
80
|
+
* Module scope in a Worker is evaluated once when the isolate boots and is
|
|
81
|
+
* then shared by every request that isolate serves, so a random value drawn
|
|
82
|
+
* there would be the same for all of them — which is why the runtime refuses
|
|
83
|
+
* to produce one. A host that server-renders on Workers reaches this module
|
|
84
|
+
* through `track()` on the server as well, and the throw happened as the
|
|
85
|
+
* isolate booted, taking down every route before a component rendered.
|
|
86
|
+
*
|
|
87
|
+
* Everything here is per-visitor browser or app state, so deferring the
|
|
88
|
+
* construction costs nothing and buys two things: the server bundle can be
|
|
89
|
+
* evaluated, and `startTime` marks when the session actually began rather
|
|
90
|
+
* than when the isolate happened to start.
|
|
91
|
+
*/
|
|
92
|
+
function getSession() {
|
|
93
|
+
return session ??= new Session();
|
|
94
|
+
}
|
|
69
95
|
//#endregion
|
|
70
96
|
exports.SESSION_TIMEOUT = SESSION_TIMEOUT;
|
|
71
|
-
exports.
|
|
97
|
+
exports.getSession = getSession;
|
|
72
98
|
|
|
73
99
|
//# sourceMappingURL=session.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.cjs","names":[],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\nclass Session {\n private id: string;\n private startTime: number;\n private lastActiveTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.id = uuidv7();\n this.startTime = Date.now();\n this.lastActiveTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n getId = () => this.id;\n\n isActive = () => this.active;\n isVisible = () => this.visible;\n isFocused = () => this.focused;\n isExpired = () => Date.now() - this.lastActiveTime > SESSION_TIMEOUT;\n\n updateLastActiveTime = () => {\n this.lastActiveTime = Date.now();\n };\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n refresh = () => {\n this.id = uuidv7();\n this.lastActiveTime = Date.now();\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.startTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.startTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n if (state === 'visible') {\n this.updateLastActiveTime();\n }\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nexport
|
|
1
|
+
{"version":3,"file":"session.cjs","names":[],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\nclass Session {\n private id: string;\n private startTime: number;\n private lastActiveTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.id = uuidv7();\n this.startTime = Date.now();\n this.lastActiveTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n getId = () => this.id;\n\n isActive = () => this.active;\n isVisible = () => this.visible;\n isFocused = () => this.focused;\n isExpired = () => Date.now() - this.lastActiveTime > SESSION_TIMEOUT;\n\n updateLastActiveTime = () => {\n this.lastActiveTime = Date.now();\n };\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n refresh = () => {\n this.id = uuidv7();\n this.lastActiveTime = Date.now();\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.startTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.startTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n if (state === 'visible') {\n this.updateLastActiveTime();\n }\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nlet session: Session | undefined;\n\n/**\n * The session, built the first time something asks for it.\n *\n * Deliberately not a module-scope `new Session()`. The constructor calls\n * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and\n * Cloudflare Workers reject that outside a request handler:\n *\n * Disallowed operation called within global scope. Asynchronous I/O\n * (ex: fetch() or connect()), setting a timeout, and generating random\n * values are not allowed within global scope.\n *\n * Module scope in a Worker is evaluated once when the isolate boots and is\n * then shared by every request that isolate serves, so a random value drawn\n * there would be the same for all of them — which is why the runtime refuses\n * to produce one. A host that server-renders on Workers reaches this module\n * through `track()` on the server as well, and the throw happened as the\n * isolate booted, taking down every route before a component rendered.\n *\n * Everything here is per-visitor browser or app state, so deferring the\n * construction costs nothing and buys two things: the server bundle can be\n * evaluated, and `startTime` marks when the session actually began rather\n * than when the isolate happened to start.\n */\nexport function getSession() {\n return (session ??= new Session());\n}\n"],"mappings":";;;AAEA,MAAa,kBAAkB,OAAU;AAEzC,IAAM,UAAN,MAAc;CAUZ,cAAc;EAWA,KAAA,cAAA,KAAK;EAEF,KAAA,iBAAA,KAAK;EACJ,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK,IAAI,IAAI,KAAK,iBAAiB;EAExB,KAAA,6BAAA;GAC3B,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAEgB,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAEgB,KAAA,gBAAA;GACd,KAAK,MAAA,GAAA,KAAA,GAAA,CAAY;GACjB,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,YAAY;EACnB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,IAAI,UAAU,WACZ,KAAK,qBAAqB;GAE5B,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EA5EE,KAAK,MAAA,GAAA,KAAA,GAAA,CAAY;EACjB,KAAK,YAAY,KAAK,IAAI;EAC1B,KAAK,iBAAiB,KAAK,IAAI;EAC/B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AAqEF;AAEA,IAAI;;;;;;;;;;;;;;;;;;;;;;;;AAyBJ,SAAgB,aAAa;CAC3B,OAAQ,YAAY,IAAI,QAAQ;AAClC"}
|
package/dist/setup/session.d.cts
CHANGED
|
@@ -25,7 +25,30 @@ declare class Session {
|
|
|
25
25
|
visibilitychange: (state: DocumentVisibilityState) => void;
|
|
26
26
|
flush: () => number;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The session, built the first time something asks for it.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately not a module-scope `new Session()`. The constructor calls
|
|
32
|
+
* `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
|
|
33
|
+
* Cloudflare Workers reject that outside a request handler:
|
|
34
|
+
*
|
|
35
|
+
* Disallowed operation called within global scope. Asynchronous I/O
|
|
36
|
+
* (ex: fetch() or connect()), setting a timeout, and generating random
|
|
37
|
+
* values are not allowed within global scope.
|
|
38
|
+
*
|
|
39
|
+
* Module scope in a Worker is evaluated once when the isolate boots and is
|
|
40
|
+
* then shared by every request that isolate serves, so a random value drawn
|
|
41
|
+
* there would be the same for all of them — which is why the runtime refuses
|
|
42
|
+
* to produce one. A host that server-renders on Workers reaches this module
|
|
43
|
+
* through `track()` on the server as well, and the throw happened as the
|
|
44
|
+
* isolate booted, taking down every route before a component rendered.
|
|
45
|
+
*
|
|
46
|
+
* Everything here is per-visitor browser or app state, so deferring the
|
|
47
|
+
* construction costs nothing and buys two things: the server bundle can be
|
|
48
|
+
* evaluated, and `startTime` marks when the session actually began rather
|
|
49
|
+
* than when the isolate happened to start.
|
|
50
|
+
*/
|
|
51
|
+
declare function getSession(): Session;
|
|
29
52
|
//#endregion
|
|
30
|
-
export { SESSION_TIMEOUT,
|
|
53
|
+
export { SESSION_TIMEOUT, getSession };
|
|
31
54
|
//# sourceMappingURL=session.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.cts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B
|
|
1
|
+
{"version":3,"file":"session.d.cts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCc,cAAU"}
|
package/dist/setup/session.d.mts
CHANGED
|
@@ -25,7 +25,30 @@ declare class Session {
|
|
|
25
25
|
visibilitychange: (state: DocumentVisibilityState) => void;
|
|
26
26
|
flush: () => number;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The session, built the first time something asks for it.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately not a module-scope `new Session()`. The constructor calls
|
|
32
|
+
* `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
|
|
33
|
+
* Cloudflare Workers reject that outside a request handler:
|
|
34
|
+
*
|
|
35
|
+
* Disallowed operation called within global scope. Asynchronous I/O
|
|
36
|
+
* (ex: fetch() or connect()), setting a timeout, and generating random
|
|
37
|
+
* values are not allowed within global scope.
|
|
38
|
+
*
|
|
39
|
+
* Module scope in a Worker is evaluated once when the isolate boots and is
|
|
40
|
+
* then shared by every request that isolate serves, so a random value drawn
|
|
41
|
+
* there would be the same for all of them — which is why the runtime refuses
|
|
42
|
+
* to produce one. A host that server-renders on Workers reaches this module
|
|
43
|
+
* through `track()` on the server as well, and the throw happened as the
|
|
44
|
+
* isolate booted, taking down every route before a component rendered.
|
|
45
|
+
*
|
|
46
|
+
* Everything here is per-visitor browser or app state, so deferring the
|
|
47
|
+
* construction costs nothing and buys two things: the server bundle can be
|
|
48
|
+
* evaluated, and `startTime` marks when the session actually began rather
|
|
49
|
+
* than when the isolate happened to start.
|
|
50
|
+
*/
|
|
51
|
+
declare function getSession(): Session;
|
|
29
52
|
//#endregion
|
|
30
|
-
export { SESSION_TIMEOUT,
|
|
53
|
+
export { SESSION_TIMEOUT, getSession };
|
|
31
54
|
//# sourceMappingURL=session.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B
|
|
1
|
+
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCc,cAAU"}
|
package/dist/setup/session.mjs
CHANGED
|
@@ -64,8 +64,34 @@ var Session = class {
|
|
|
64
64
|
this.focused = typeof document !== "undefined" ? document.hasFocus() : true;
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
|
-
|
|
67
|
+
let session;
|
|
68
|
+
/**
|
|
69
|
+
* The session, built the first time something asks for it.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately not a module-scope `new Session()`. The constructor calls
|
|
72
|
+
* `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
|
|
73
|
+
* Cloudflare Workers reject that outside a request handler:
|
|
74
|
+
*
|
|
75
|
+
* Disallowed operation called within global scope. Asynchronous I/O
|
|
76
|
+
* (ex: fetch() or connect()), setting a timeout, and generating random
|
|
77
|
+
* values are not allowed within global scope.
|
|
78
|
+
*
|
|
79
|
+
* Module scope in a Worker is evaluated once when the isolate boots and is
|
|
80
|
+
* then shared by every request that isolate serves, so a random value drawn
|
|
81
|
+
* there would be the same for all of them — which is why the runtime refuses
|
|
82
|
+
* to produce one. A host that server-renders on Workers reaches this module
|
|
83
|
+
* through `track()` on the server as well, and the throw happened as the
|
|
84
|
+
* isolate booted, taking down every route before a component rendered.
|
|
85
|
+
*
|
|
86
|
+
* Everything here is per-visitor browser or app state, so deferring the
|
|
87
|
+
* construction costs nothing and buys two things: the server bundle can be
|
|
88
|
+
* evaluated, and `startTime` marks when the session actually began rather
|
|
89
|
+
* than when the isolate happened to start.
|
|
90
|
+
*/
|
|
91
|
+
function getSession() {
|
|
92
|
+
return session ??= new Session();
|
|
93
|
+
}
|
|
68
94
|
//#endregion
|
|
69
|
-
export { SESSION_TIMEOUT,
|
|
95
|
+
export { SESSION_TIMEOUT, getSession };
|
|
70
96
|
|
|
71
97
|
//# sourceMappingURL=session.mjs.map
|