@nebulr-group/bridge-svelte 0.3.0-beta.6 → 0.3.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/client/tracking/pii-hashing.d.ts +11 -0
- package/dist/client/tracking/pii-hashing.js +29 -0
- package/dist/client/tracking/pii-hashing.spec.d.ts +1 -0
- package/dist/client/tracking/pii-hashing.spec.js +57 -0
- package/dist/client/tracking/reddit-tracking.d.ts +74 -0
- package/dist/client/tracking/reddit-tracking.js +59 -0
- package/dist/client/tracking/reddit-tracking.spec.d.ts +1 -0
- package/dist/client/tracking/reddit-tracking.spec.js +126 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/package.json +5 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side PII hashing for ad-platform advanced matching.
|
|
3
|
+
*
|
|
4
|
+
* Normalization MUST match the server-side util at
|
|
5
|
+
* `bridge-api/microservices/account/nebulr-api/utils/pii-hashing.util.ts`:
|
|
6
|
+
* trim → lowercase → SHA-256 → hex.
|
|
7
|
+
*
|
|
8
|
+
* Any divergence between client and server hashing silently hurts match rates.
|
|
9
|
+
* Tests must assert byte-identical output across both sides for the same input.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sha256Email(email: string | null | undefined): Promise<string | undefined>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side PII hashing for ad-platform advanced matching.
|
|
3
|
+
*
|
|
4
|
+
* Normalization MUST match the server-side util at
|
|
5
|
+
* `bridge-api/microservices/account/nebulr-api/utils/pii-hashing.util.ts`:
|
|
6
|
+
* trim → lowercase → SHA-256 → hex.
|
|
7
|
+
*
|
|
8
|
+
* Any divergence between client and server hashing silently hurts match rates.
|
|
9
|
+
* Tests must assert byte-identical output across both sides for the same input.
|
|
10
|
+
*/
|
|
11
|
+
export async function sha256Email(email) {
|
|
12
|
+
if (!email)
|
|
13
|
+
return undefined;
|
|
14
|
+
const normalized = email.trim().toLowerCase();
|
|
15
|
+
if (!normalized)
|
|
16
|
+
return undefined;
|
|
17
|
+
return sha256Hex(normalized);
|
|
18
|
+
}
|
|
19
|
+
async function sha256Hex(input) {
|
|
20
|
+
if (typeof globalThis.crypto?.subtle?.digest !== 'function') {
|
|
21
|
+
// SSR / non-browser context without WebCrypto — skip hashing.
|
|
22
|
+
// Callers treat undefined as "advanced matching unavailable" and fall back to plain pixel.
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const encoder = new TextEncoder();
|
|
26
|
+
const data = encoder.encode(input);
|
|
27
|
+
const buf = await globalThis.crypto.subtle.digest('SHA-256', data);
|
|
28
|
+
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import { webcrypto } from 'node:crypto';
|
|
3
|
+
import { sha256Email } from './pii-hashing.js';
|
|
4
|
+
/**
|
|
5
|
+
* Client-side PII hashing fixtures.
|
|
6
|
+
*
|
|
7
|
+
* The expected SHA-256 hex outputs below are shared BYTE-FOR-BYTE with
|
|
8
|
+
* the server-side test at
|
|
9
|
+
* bridge-api/microservices/account/nebulr-api/utils/pii-hashing.util.spec.ts.
|
|
10
|
+
*
|
|
11
|
+
* Reddit advanced-matching match rate depends on client and server producing
|
|
12
|
+
* identical hashes for the same raw email. If either side changes
|
|
13
|
+
* normalization (trim/lowercase) or algorithm, the hashes diverge and
|
|
14
|
+
* match rates silently fall — these tests catch that drift.
|
|
15
|
+
*/
|
|
16
|
+
// SHA-256 hex of "user@example.com"
|
|
17
|
+
const USER_AT_EXAMPLE_COM_HASH = 'b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514';
|
|
18
|
+
// SHA-256 hex of "admin@gmail.com"
|
|
19
|
+
const ADMIN_AT_GMAIL_COM_HASH = '7932b2e116b076a54f452848eaabd5857f61bd957fe8a218faf216f24c9885bb';
|
|
20
|
+
/**
|
|
21
|
+
* Vitest's `environment: 'node'` exposes Node's `webcrypto` at `globalThis.crypto`
|
|
22
|
+
* from v19+, but the `digest()` API is the same as the browser `crypto.subtle`
|
|
23
|
+
* that `sha256Email` expects. We defensively wire it up here so the test is
|
|
24
|
+
* robust across Node versions.
|
|
25
|
+
*/
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
if (!globalThis.crypto || typeof globalThis.crypto.subtle?.digest !== 'function') {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
globalThis.crypto = webcrypto;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
describe('sha256Email (client)', () => {
|
|
33
|
+
it('hashes a plain, already-normalized email to the known SHA-256 hex (matches server fixture)', async () => {
|
|
34
|
+
await expect(sha256Email('user@example.com')).resolves.toBe(USER_AT_EXAMPLE_COM_HASH);
|
|
35
|
+
});
|
|
36
|
+
it('trims whitespace and lowercases before hashing — output matches the plain form byte-for-byte', async () => {
|
|
37
|
+
const messy = await sha256Email(' User@Example.COM ');
|
|
38
|
+
const clean = await sha256Email('user@example.com');
|
|
39
|
+
expect(messy).toBe(clean);
|
|
40
|
+
expect(messy).toBe(USER_AT_EXAMPLE_COM_HASH);
|
|
41
|
+
});
|
|
42
|
+
it('hashes a second fixture email consistently (matches server fixture)', async () => {
|
|
43
|
+
await expect(sha256Email('admin@gmail.com')).resolves.toBe(ADMIN_AT_GMAIL_COM_HASH);
|
|
44
|
+
});
|
|
45
|
+
it('returns undefined for empty string', async () => {
|
|
46
|
+
await expect(sha256Email('')).resolves.toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
it('returns undefined for null', async () => {
|
|
49
|
+
await expect(sha256Email(null)).resolves.toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
it('returns undefined for undefined', async () => {
|
|
52
|
+
await expect(sha256Email(undefined)).resolves.toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
it('returns undefined for whitespace-only input (normalization collapses to empty)', async () => {
|
|
55
|
+
await expect(sha256Email(' ')).resolves.toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reddit / GA4 conversion tracking via GTM dataLayer.
|
|
3
|
+
*
|
|
4
|
+
* Emits standard events (SignUp, Lead, PageVisit, Purchase) with optional
|
|
5
|
+
* advanced-matching (hashed email) and a shared `conversion_id` that pairs
|
|
6
|
+
* with server-side CAPI events for Reddit's deduplication.
|
|
7
|
+
*
|
|
8
|
+
* This util lives in bridge-svelte so it can be consumed uniformly by
|
|
9
|
+
* bridge-cloud-views, bridge-admin-ui, and the marketing site — the hashing
|
|
10
|
+
* rules and dataLayer contract must stay consistent across all three.
|
|
11
|
+
*
|
|
12
|
+
* GTM-side: tags MUST read the `conversion_id` dataLayer variable and forward
|
|
13
|
+
* it to the Reddit Pixel tag as the event dedup key. Same for
|
|
14
|
+
* `user_data.email_address` (advanced matching).
|
|
15
|
+
*/
|
|
16
|
+
export type RedditConversionEvent = 'SignUp' | 'Lead' | 'PageVisit' | 'Purchase';
|
|
17
|
+
export interface RedditUserData {
|
|
18
|
+
/** Pre-hashed (SHA-256 hex of trim+lowercase). Use `sha256Email()` before passing. */
|
|
19
|
+
email_address?: string;
|
|
20
|
+
phone_number?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RedditEcommerceItem {
|
|
23
|
+
item_id: string;
|
|
24
|
+
item_name: string;
|
|
25
|
+
item_category?: string;
|
|
26
|
+
price: number;
|
|
27
|
+
quantity: number;
|
|
28
|
+
}
|
|
29
|
+
export interface RedditEcommerce {
|
|
30
|
+
value: number;
|
|
31
|
+
currency: string;
|
|
32
|
+
items: RedditEcommerceItem[];
|
|
33
|
+
}
|
|
34
|
+
export interface PushConversionEventOptions {
|
|
35
|
+
user_data?: RedditUserData;
|
|
36
|
+
ecommerce?: RedditEcommerce;
|
|
37
|
+
/**
|
|
38
|
+
* How the user signed up or logged in. Surfaces as a GA4 custom dimension.
|
|
39
|
+
* e.g. 'email' | 'passkey' | 'google' | 'linkedin'
|
|
40
|
+
*/
|
|
41
|
+
signup_method?: string;
|
|
42
|
+
/**
|
|
43
|
+
* UUID shared between this pixel event and the server-side CAPI call.
|
|
44
|
+
* Reddit dedupes on this value — without it, pixel + CAPI would double-count.
|
|
45
|
+
*/
|
|
46
|
+
conversion_id?: string;
|
|
47
|
+
}
|
|
48
|
+
declare global {
|
|
49
|
+
interface Window {
|
|
50
|
+
dataLayer?: Record<string, unknown>[];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export interface RedditTrackingGate {
|
|
54
|
+
/**
|
|
55
|
+
* Return true to allow the event, false to suppress. Lets consuming apps
|
|
56
|
+
* gate on things like "is this the right app?" or "is tracking enabled in env?".
|
|
57
|
+
* When undefined, all events are pushed (assumes caller handles gating).
|
|
58
|
+
*/
|
|
59
|
+
(): boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Install an app-level gate. Called once at app bootstrap (e.g. from
|
|
63
|
+
* BridgeBootstrap or the app's layout). When the gate returns false, all
|
|
64
|
+
* pushConversionEvent calls no-op.
|
|
65
|
+
*/
|
|
66
|
+
export declare function configureRedditTracking(gate: RedditTrackingGate | undefined): void;
|
|
67
|
+
/**
|
|
68
|
+
* Push a conversion event to dataLayer for GTM (Reddit Pixel + GA4).
|
|
69
|
+
*
|
|
70
|
+
* No-ops when not in a browser, dataLayer is missing, or the gate returns false.
|
|
71
|
+
*/
|
|
72
|
+
export declare function pushConversionEvent(event: RedditConversionEvent, options?: PushConversionEventOptions): void;
|
|
73
|
+
/** @deprecated Use pushConversionEvent. Kept for backward compatibility. */
|
|
74
|
+
export declare const pushRedditEvent: typeof pushConversionEvent;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reddit / GA4 conversion tracking via GTM dataLayer.
|
|
3
|
+
*
|
|
4
|
+
* Emits standard events (SignUp, Lead, PageVisit, Purchase) with optional
|
|
5
|
+
* advanced-matching (hashed email) and a shared `conversion_id` that pairs
|
|
6
|
+
* with server-side CAPI events for Reddit's deduplication.
|
|
7
|
+
*
|
|
8
|
+
* This util lives in bridge-svelte so it can be consumed uniformly by
|
|
9
|
+
* bridge-cloud-views, bridge-admin-ui, and the marketing site — the hashing
|
|
10
|
+
* rules and dataLayer contract must stay consistent across all three.
|
|
11
|
+
*
|
|
12
|
+
* GTM-side: tags MUST read the `conversion_id` dataLayer variable and forward
|
|
13
|
+
* it to the Reddit Pixel tag as the event dedup key. Same for
|
|
14
|
+
* `user_data.email_address` (advanced matching).
|
|
15
|
+
*/
|
|
16
|
+
let _gate;
|
|
17
|
+
/**
|
|
18
|
+
* Install an app-level gate. Called once at app bootstrap (e.g. from
|
|
19
|
+
* BridgeBootstrap or the app's layout). When the gate returns false, all
|
|
20
|
+
* pushConversionEvent calls no-op.
|
|
21
|
+
*/
|
|
22
|
+
export function configureRedditTracking(gate) {
|
|
23
|
+
_gate = gate;
|
|
24
|
+
}
|
|
25
|
+
function getDataLayer() {
|
|
26
|
+
if (typeof window === 'undefined')
|
|
27
|
+
return undefined;
|
|
28
|
+
return window.dataLayer;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Push a conversion event to dataLayer for GTM (Reddit Pixel + GA4).
|
|
32
|
+
*
|
|
33
|
+
* No-ops when not in a browser, dataLayer is missing, or the gate returns false.
|
|
34
|
+
*/
|
|
35
|
+
export function pushConversionEvent(event, options) {
|
|
36
|
+
if (_gate && !_gate())
|
|
37
|
+
return;
|
|
38
|
+
const dataLayer = getDataLayer();
|
|
39
|
+
if (!dataLayer || !Array.isArray(dataLayer))
|
|
40
|
+
return;
|
|
41
|
+
const payload = { event };
|
|
42
|
+
if (options?.user_data && Object.keys(options.user_data).length > 0) {
|
|
43
|
+
payload.user_data = options.user_data;
|
|
44
|
+
}
|
|
45
|
+
if (options?.ecommerce) {
|
|
46
|
+
// Reddit/GA4 best practice: clear ecommerce before pushing a new one
|
|
47
|
+
dataLayer.push({ ecommerce: null });
|
|
48
|
+
payload.ecommerce = options.ecommerce;
|
|
49
|
+
}
|
|
50
|
+
if (options?.signup_method != null && options.signup_method !== '') {
|
|
51
|
+
payload.signup_method = options.signup_method;
|
|
52
|
+
}
|
|
53
|
+
if (options?.conversion_id != null && options.conversion_id !== '') {
|
|
54
|
+
payload.conversion_id = options.conversion_id;
|
|
55
|
+
}
|
|
56
|
+
dataLayer.push(payload);
|
|
57
|
+
}
|
|
58
|
+
/** @deprecated Use pushConversionEvent. Kept for backward compatibility. */
|
|
59
|
+
export const pushRedditEvent = pushConversionEvent;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { configureRedditTracking, pushConversionEvent } from './reddit-tracking.js';
|
|
3
|
+
function installWindow(dataLayer) {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
globalThis.window = { dataLayer };
|
|
6
|
+
}
|
|
7
|
+
function uninstallWindow() {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
delete globalThis.window;
|
|
10
|
+
}
|
|
11
|
+
describe('pushConversionEvent', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Reset the module-level gate between tests.
|
|
14
|
+
configureRedditTracking(undefined);
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
uninstallWindow();
|
|
18
|
+
configureRedditTracking(undefined);
|
|
19
|
+
});
|
|
20
|
+
it('no-ops when the configured gate returns false', () => {
|
|
21
|
+
const dl = [];
|
|
22
|
+
installWindow(dl);
|
|
23
|
+
configureRedditTracking(() => false);
|
|
24
|
+
pushConversionEvent('SignUp', {
|
|
25
|
+
user_data: { email_address: 'hash-abc' },
|
|
26
|
+
conversion_id: 'cid-1'
|
|
27
|
+
});
|
|
28
|
+
expect(dl).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
it('no-ops when window is missing (SSR / non-browser context)', () => {
|
|
31
|
+
// No installWindow — globalThis.window is undefined.
|
|
32
|
+
configureRedditTracking(() => true);
|
|
33
|
+
// Must not throw.
|
|
34
|
+
expect(() => pushConversionEvent('SignUp', { conversion_id: 'cid-ssr' })).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
it('no-ops when window.dataLayer is missing', () => {
|
|
37
|
+
installWindow(undefined);
|
|
38
|
+
configureRedditTracking(() => true);
|
|
39
|
+
// Must not throw.
|
|
40
|
+
expect(() => pushConversionEvent('SignUp', { conversion_id: 'cid-no-dl' })).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
it('pushes a full conversion payload when gate returns true — event, user_data, ecommerce (with cleanup push), signup_method, conversion_id', () => {
|
|
43
|
+
const dl = [];
|
|
44
|
+
installWindow(dl);
|
|
45
|
+
configureRedditTracking(() => true);
|
|
46
|
+
pushConversionEvent('Purchase', {
|
|
47
|
+
user_data: { email_address: 'hash-abc' },
|
|
48
|
+
ecommerce: {
|
|
49
|
+
value: 29,
|
|
50
|
+
currency: 'USD',
|
|
51
|
+
items: [
|
|
52
|
+
{ item_id: 'premium', item_name: 'Premium Monthly', price: 29, quantity: 1 }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
signup_method: 'email',
|
|
56
|
+
conversion_id: 'cid-purchase-1'
|
|
57
|
+
});
|
|
58
|
+
// ecommerce cleanup push MUST come first, then the real event payload
|
|
59
|
+
expect(dl).toHaveLength(2);
|
|
60
|
+
expect(dl[0]).toEqual({ ecommerce: null });
|
|
61
|
+
const payload = dl[1];
|
|
62
|
+
expect(payload.event).toBe('Purchase');
|
|
63
|
+
expect(payload.user_data).toEqual({ email_address: 'hash-abc' });
|
|
64
|
+
expect(payload.ecommerce).toMatchObject({
|
|
65
|
+
value: 29,
|
|
66
|
+
currency: 'USD'
|
|
67
|
+
});
|
|
68
|
+
expect(payload.signup_method).toBe('email');
|
|
69
|
+
expect(payload.conversion_id).toBe('cid-purchase-1');
|
|
70
|
+
});
|
|
71
|
+
it('propagates conversion_id through to the pushed payload', () => {
|
|
72
|
+
const dl = [];
|
|
73
|
+
installWindow(dl);
|
|
74
|
+
configureRedditTracking(() => true);
|
|
75
|
+
pushConversionEvent('SignUp', { conversion_id: 'my-shared-uuid' });
|
|
76
|
+
expect(dl).toHaveLength(1);
|
|
77
|
+
expect(dl[0].conversion_id).toBe('my-shared-uuid');
|
|
78
|
+
});
|
|
79
|
+
it('omits user_data from payload when provided as an empty object', () => {
|
|
80
|
+
const dl = [];
|
|
81
|
+
installWindow(dl);
|
|
82
|
+
configureRedditTracking(() => true);
|
|
83
|
+
pushConversionEvent('Lead', { user_data: {}, conversion_id: 'cid-lead' });
|
|
84
|
+
expect(dl).toHaveLength(1);
|
|
85
|
+
const payload = dl[0];
|
|
86
|
+
expect(payload.event).toBe('Lead');
|
|
87
|
+
expect('user_data' in payload).toBe(false);
|
|
88
|
+
expect(payload.conversion_id).toBe('cid-lead');
|
|
89
|
+
});
|
|
90
|
+
it('omits signup_method when the option is undefined or empty string', () => {
|
|
91
|
+
const dl = [];
|
|
92
|
+
installWindow(dl);
|
|
93
|
+
configureRedditTracking(() => true);
|
|
94
|
+
pushConversionEvent('SignUp', { conversion_id: 'cid-sm-undefined' });
|
|
95
|
+
pushConversionEvent('SignUp', { signup_method: '', conversion_id: 'cid-sm-empty' });
|
|
96
|
+
expect(dl).toHaveLength(2);
|
|
97
|
+
expect('signup_method' in dl[0]).toBe(false);
|
|
98
|
+
expect('signup_method' in dl[1]).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
it('omits conversion_id from payload when the option is undefined or empty string', () => {
|
|
101
|
+
const dl = [];
|
|
102
|
+
installWindow(dl);
|
|
103
|
+
configureRedditTracking(() => true);
|
|
104
|
+
pushConversionEvent('SignUp');
|
|
105
|
+
pushConversionEvent('SignUp', { conversion_id: '' });
|
|
106
|
+
expect(dl).toHaveLength(2);
|
|
107
|
+
expect('conversion_id' in dl[0]).toBe(false);
|
|
108
|
+
expect('conversion_id' in dl[1]).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
it('pushes without gate-check when no gate is configured', () => {
|
|
111
|
+
const dl = [];
|
|
112
|
+
installWindow(dl);
|
|
113
|
+
// No configureRedditTracking call — default is "allow all".
|
|
114
|
+
pushConversionEvent('PageVisit', { conversion_id: 'cid-pv' });
|
|
115
|
+
expect(dl).toHaveLength(1);
|
|
116
|
+
expect(dl[0].event).toBe('PageVisit');
|
|
117
|
+
});
|
|
118
|
+
it('emits the ecommerce cleanup push only when ecommerce option is provided', () => {
|
|
119
|
+
const dl = [];
|
|
120
|
+
installWindow(dl);
|
|
121
|
+
configureRedditTracking(() => true);
|
|
122
|
+
pushConversionEvent('SignUp', { conversion_id: 'cid-no-ecom' });
|
|
123
|
+
expect(dl).toHaveLength(1);
|
|
124
|
+
expect(dl[0]).not.toEqual({ ecommerce: null });
|
|
125
|
+
});
|
|
126
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ export * from './auth/route-guard.js';
|
|
|
27
27
|
export * from './shared/profile.js';
|
|
28
28
|
export * from './shared/types/config.js';
|
|
29
29
|
export { logger, setLoggerDebug } from './shared/logger.js';
|
|
30
|
+
export { pushConversionEvent, pushRedditEvent, configureRedditTracking, } from './client/tracking/reddit-tracking.js';
|
|
31
|
+
export type { RedditConversionEvent, RedditEcommerce, RedditEcommerceItem, RedditUserData, PushConversionEventOptions, RedditTrackingGate, } from './client/tracking/reddit-tracking.js';
|
|
32
|
+
export { sha256Email } from './client/tracking/pii-hashing.js';
|
|
30
33
|
export type { AuthConfigResponse, AuthResult, AuthState, BridgeAuthConfig, BridgeAuthEventName, BridgeAuthEvents, FederationConnection, MagicLinkResult, MfaResult, PasskeyAuthOptions, PasskeyRegistrationOptions, PasskeyVerificationResult, SignupResult, SsoOptions, SsoResult, TenantUser, } from '@nebulr-group/bridge-auth-core';
|
|
31
34
|
export { BridgeAuth, BridgeAuthError, HttpError, TeamService, ApiTokenService } from '@nebulr-group/bridge-auth-core';
|
|
32
35
|
export type { ApiToken, CreateApiTokenInput, CreateApiTokenResponse, } from '@nebulr-group/bridge-auth-core';
|
package/dist/index.js
CHANGED
|
@@ -36,4 +36,7 @@ export * from './shared/profile.js';
|
|
|
36
36
|
export * from './shared/types/config.js';
|
|
37
37
|
// Logger
|
|
38
38
|
export { logger, setLoggerDebug } from './shared/logger.js';
|
|
39
|
+
// Conversion tracking (Reddit / GA4 via GTM dataLayer)
|
|
40
|
+
export { pushConversionEvent, pushRedditEvent, configureRedditTracking, } from './client/tracking/reddit-tracking.js';
|
|
41
|
+
export { sha256Email } from './client/tracking/pii-hashing.js';
|
|
39
42
|
export { BridgeAuth, BridgeAuthError, HttpError, TeamService, ApiTokenService } from '@nebulr-group/bridge-auth-core';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulr-group/bridge-svelte",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Bridge Svelte library, This library helps you to add bridge authentication and feature flags, and payments to your svelte application.",
|
|
5
5
|
"author": "Iman Pouya",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"prepack": "svelte-kit sync && svelte-package",
|
|
21
21
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
22
22
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
23
|
+
"test:unit": "vitest run",
|
|
23
24
|
"package": "bun run prepack && bun pm pack --destination ../",
|
|
24
25
|
"publish:local": "bun run prepack && node -e \"const fs=require('fs'),p=JSON.parse(fs.readFileSync('package.json','utf8')),v=p.version.match(/^(\\d+\\.\\d+\\.\\d+)-alpha\\.(\\d+)$/);if(v){p.version=v[1]+'-alpha.'+(+v[2]+1);}else{p.version=p.version+'-alpha.0';}fs.writeFileSync('package.json',JSON.stringify(p,null,'\t')+'\\n');console.log('Version bumped to '+p.version);\" && npm publish --registry http://host.docker.internal:4873 --no-git-checks --ignore-scripts && npm dist-tag add @nebulr-group/bridge-svelte@$(node -p \"require('./package.json').version\") alpha --registry http://host.docker.internal:4873"
|
|
25
26
|
},
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
63
|
-
"@nebulr-group/bridge-auth-core": "0.1.0
|
|
64
|
+
"@nebulr-group/bridge-auth-core": "0.1.0"
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
67
|
"@sveltejs/kit": "^2.16.0",
|
|
@@ -71,7 +72,8 @@
|
|
|
71
72
|
"svelte": "^5.0.0",
|
|
72
73
|
"svelte-check": "^4.0.0",
|
|
73
74
|
"typescript": "^5.0.0",
|
|
74
|
-
"vite": "^6.2.6"
|
|
75
|
+
"vite": "^6.2.6",
|
|
76
|
+
"vitest": "^4.1.4"
|
|
75
77
|
},
|
|
76
78
|
"keywords": [
|
|
77
79
|
"svelte",
|