@kubuild/core 0.5.0 → 0.6.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/index.cjs CHANGED
@@ -58,6 +58,7 @@ __export(index_exports, {
58
58
  defaultIdGenerator: () => defaultIdGenerator,
59
59
  defaultMigrationRegistry: () => defaultMigrationRegistry,
60
60
  defaultRenameAssetStrategy: () => defaultRenameAssetStrategy,
61
+ dispatchServerTracking: () => dispatchServerTracking,
61
62
  duplicateNode: () => duplicateNode,
62
63
  evaluateActionCondition: () => evaluateActionCondition,
63
64
  evaluateCondition: () => evaluateCondition,
@@ -77,6 +78,7 @@ __export(index_exports, {
77
78
  findMissingComponentNodes: () => findMissingComponentNodes,
78
79
  findNodeById: () => findNodeById,
79
80
  findNodeLocation: () => findNodeLocation,
81
+ generateTrackingEventId: () => generateTrackingEventId,
80
82
  getActiveArtboard: () => getActiveArtboard,
81
83
  getAncestorChain: () => getAncestorChain,
82
84
  getComponentArtboards: () => getComponentArtboards,
@@ -86,6 +88,7 @@ __export(index_exports, {
86
88
  getPageArtboards: () => getPageArtboards,
87
89
  getParentNodeId: () => getParentNodeId,
88
90
  hasTemplateExpressions: () => hasTemplateExpressions,
91
+ hashSha256: () => hashSha256,
89
92
  importPackage: () => importPackage,
90
93
  importStoraPackage: () => importStoraPackage,
91
94
  insertNode: () => insertNode,
@@ -103,6 +106,9 @@ __export(index_exports, {
103
106
  loadProjectDocument: () => loadProjectDocument,
104
107
  migrateDocument: () => migrateDocument,
105
108
  moveNode: () => moveNode,
109
+ normalizeEmail: () => normalizeEmail,
110
+ normalizePhone: () => normalizePhone,
111
+ normalizeText: () => normalizeText,
106
112
  preflightPackage: () => preflightPackage,
107
113
  previewImportPackage: () => previewImportPackage,
108
114
  remapAssetReferences: () => remapAssetReferences,
@@ -118,6 +124,10 @@ __export(index_exports, {
118
124
  sanitizeHtml: () => sanitizeHtml,
119
125
  sanitizeUrl: () => sanitizeUrl,
120
126
  saveDraftAsTemplate: () => saveDraftAsTemplate,
127
+ sendCustomWebhookEvent: () => sendCustomWebhookEvent,
128
+ sendGa4MeasurementEvent: () => sendGa4MeasurementEvent,
129
+ sendMetaCapiEvent: () => sendMetaCapiEvent,
130
+ sendTikTokEventsApi: () => sendTikTokEventsApi,
121
131
  setActiveArtboard: () => setActiveArtboard,
122
132
  sha256Sync: () => sha256Sync,
123
133
  ungroupNodeFrame: () => ungroupNodeFrame,
@@ -2584,6 +2594,385 @@ function createRuntimeStore(initialState) {
2584
2594
  return new RuntimeStateStore(initialState);
2585
2595
  }
2586
2596
 
2597
+ // src/runtime/server-tracking.ts
2598
+ function normalizeEmail(email) {
2599
+ return (email || "").trim().toLowerCase();
2600
+ }
2601
+ function normalizePhone(phone) {
2602
+ return (phone || "").replace(/[^0-9]/g, "");
2603
+ }
2604
+ function normalizeText(text) {
2605
+ return (text || "").trim().toLowerCase();
2606
+ }
2607
+ async function hashSha256(value) {
2608
+ const normalized = (value || "").trim();
2609
+ if (!normalized) return "";
2610
+ if (typeof globalThis.crypto?.subtle?.digest === "function") {
2611
+ const encoder = new TextEncoder();
2612
+ const data = encoder.encode(normalized);
2613
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
2614
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2615
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2616
+ }
2617
+ throw new Error("Web Crypto API (crypto.subtle) is not available in current runtime environment.");
2618
+ }
2619
+ function generateTrackingEventId(prefix = "evt") {
2620
+ const timestamp = Date.now().toString(36);
2621
+ const randomPart = Math.random().toString(36).substring(2, 10);
2622
+ return `${prefix}_${timestamp}_${randomPart}`;
2623
+ }
2624
+ async function sendMetaCapiEvent(event, config, options) {
2625
+ const fetcher = options?.fetchFn || globalThis.fetch;
2626
+ if (!config.pixelId) {
2627
+ return {
2628
+ provider: "meta",
2629
+ success: false,
2630
+ error: "Meta Pixel ID is missing"
2631
+ };
2632
+ }
2633
+ const rawUserData = event.userData || {};
2634
+ const hashedUserData = {};
2635
+ if (rawUserData.email) {
2636
+ hashedUserData.em = [await hashSha256(normalizeEmail(String(rawUserData.email)))];
2637
+ }
2638
+ if (rawUserData.phone) {
2639
+ hashedUserData.ph = [await hashSha256(normalizePhone(String(rawUserData.phone)))];
2640
+ }
2641
+ if (rawUserData.firstName) {
2642
+ hashedUserData.fn = [await hashSha256(normalizeText(String(rawUserData.firstName)))];
2643
+ }
2644
+ if (rawUserData.lastName) {
2645
+ hashedUserData.ln = [await hashSha256(normalizeText(String(rawUserData.lastName)))];
2646
+ }
2647
+ if (rawUserData.city) {
2648
+ hashedUserData.ct = [await hashSha256(normalizeText(String(rawUserData.city)))];
2649
+ }
2650
+ if (rawUserData.state) {
2651
+ hashedUserData.st = [await hashSha256(normalizeText(String(rawUserData.state)))];
2652
+ }
2653
+ if (rawUserData.zip) {
2654
+ hashedUserData.zp = [await hashSha256(normalizeText(String(rawUserData.zip)))];
2655
+ }
2656
+ if (rawUserData.country) {
2657
+ hashedUserData.country = [await hashSha256(normalizeText(String(rawUserData.country)))];
2658
+ }
2659
+ const clientIp = rawUserData.clientIp || options?.clientIp;
2660
+ if (clientIp) hashedUserData.client_ip_address = clientIp;
2661
+ const clientUserAgent = rawUserData.clientUserAgent || options?.clientUserAgent;
2662
+ if (clientUserAgent) hashedUserData.client_user_agent = clientUserAgent;
2663
+ if (rawUserData.fbp) hashedUserData.fbp = rawUserData.fbp;
2664
+ if (rawUserData.fbc) hashedUserData.fbc = rawUserData.fbc;
2665
+ if (rawUserData.externalId) {
2666
+ hashedUserData.external_id = [await hashSha256(String(rawUserData.externalId))];
2667
+ }
2668
+ const eventTime = event.eventTime || Math.floor(Date.now() / 1e3);
2669
+ const eventSourceUrl = event.eventSourceUrl || options?.sourceUrl;
2670
+ const capiPayload = {
2671
+ data: [
2672
+ {
2673
+ event_name: event.eventName,
2674
+ event_time: eventTime,
2675
+ event_id: event.eventId,
2676
+ event_source_url: eventSourceUrl,
2677
+ action_source: event.actionSource || "website",
2678
+ user_data: hashedUserData,
2679
+ custom_data: {
2680
+ ...event.params || {},
2681
+ ...event.customData || {}
2682
+ }
2683
+ }
2684
+ ]
2685
+ };
2686
+ if (config.testEventCode) {
2687
+ capiPayload.test_event_code = config.testEventCode;
2688
+ }
2689
+ if (options?.simulateInDebug) {
2690
+ options.onLog?.("[Meta CAPI SIMULATED]", capiPayload);
2691
+ return { provider: "meta", success: true, data: { simulated: true, payload: capiPayload } };
2692
+ }
2693
+ try {
2694
+ const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(config.pixelId)}/events`;
2695
+ const headers = {
2696
+ "Content-Type": "application/json"
2697
+ };
2698
+ if (config.capiAccessToken) {
2699
+ headers["Authorization"] = `Bearer ${config.capiAccessToken}`;
2700
+ }
2701
+ const res = await fetcher(url, {
2702
+ method: "POST",
2703
+ headers,
2704
+ body: JSON.stringify(capiPayload)
2705
+ });
2706
+ const resJson = await res.json().catch(() => ({}));
2707
+ if (!res.ok) {
2708
+ return {
2709
+ provider: "meta",
2710
+ success: false,
2711
+ status: res.status,
2712
+ error: resJson?.error?.message || `HTTP ${res.status}`,
2713
+ data: resJson
2714
+ };
2715
+ }
2716
+ return {
2717
+ provider: "meta",
2718
+ success: true,
2719
+ status: res.status,
2720
+ data: resJson
2721
+ };
2722
+ } catch (err) {
2723
+ return {
2724
+ provider: "meta",
2725
+ success: false,
2726
+ error: err instanceof Error ? err.message : String(err)
2727
+ };
2728
+ }
2729
+ }
2730
+ async function sendTikTokEventsApi(event, config, options) {
2731
+ const fetcher = options?.fetchFn || globalThis.fetch;
2732
+ if (!config.pixelId) {
2733
+ return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
2734
+ }
2735
+ const rawUserData = event.userData || {};
2736
+ const user = {};
2737
+ if (rawUserData.email) {
2738
+ user.email = await hashSha256(normalizeEmail(String(rawUserData.email)));
2739
+ }
2740
+ if (rawUserData.phone) {
2741
+ user.phone_number = await hashSha256(normalizePhone(String(rawUserData.phone)));
2742
+ }
2743
+ if (rawUserData.clientIp || options?.clientIp) {
2744
+ user.ip = rawUserData.clientIp || options?.clientIp;
2745
+ }
2746
+ if (rawUserData.clientUserAgent || options?.clientUserAgent) {
2747
+ user.user_agent = rawUserData.clientUserAgent || options?.clientUserAgent;
2748
+ }
2749
+ const tiktokPayload = {
2750
+ event_source: "web",
2751
+ event_source_id: config.pixelId,
2752
+ data: [
2753
+ {
2754
+ event: event.eventName,
2755
+ event_id: event.eventId,
2756
+ timestamp: new Date(event.eventTime ? event.eventTime * 1e3 : Date.now()).toISOString(),
2757
+ user,
2758
+ properties: {
2759
+ ...event.params || {},
2760
+ ...event.customData || {}
2761
+ },
2762
+ page: {
2763
+ url: event.eventSourceUrl || options?.sourceUrl
2764
+ }
2765
+ }
2766
+ ]
2767
+ };
2768
+ if (config.testEventCode) {
2769
+ tiktokPayload.test_event_code = config.testEventCode;
2770
+ }
2771
+ if (options?.simulateInDebug) {
2772
+ options.onLog?.("[TikTok Events API SIMULATED]", tiktokPayload);
2773
+ return { provider: "tiktok", success: true, data: { simulated: true, payload: tiktokPayload } };
2774
+ }
2775
+ try {
2776
+ const url = "https://business-api.tiktok.com/open_api/v1.3/event/track/";
2777
+ const headers = {
2778
+ "Content-Type": "application/json"
2779
+ };
2780
+ if (config.accessToken) {
2781
+ headers["Access-Token"] = config.accessToken;
2782
+ }
2783
+ const res = await fetcher(url, {
2784
+ method: "POST",
2785
+ headers,
2786
+ body: JSON.stringify(tiktokPayload)
2787
+ });
2788
+ const resJson = await res.json().catch(() => ({}));
2789
+ if (!res.ok) {
2790
+ return {
2791
+ provider: "tiktok",
2792
+ success: false,
2793
+ status: res.status,
2794
+ error: resJson?.message || `HTTP ${res.status}`,
2795
+ data: resJson
2796
+ };
2797
+ }
2798
+ return { provider: "tiktok", success: true, status: res.status, data: resJson };
2799
+ } catch (err) {
2800
+ return {
2801
+ provider: "tiktok",
2802
+ success: false,
2803
+ error: err instanceof Error ? err.message : String(err)
2804
+ };
2805
+ }
2806
+ }
2807
+ async function sendGa4MeasurementEvent(event, config, options) {
2808
+ const fetcher = options?.fetchFn || globalThis.fetch;
2809
+ if (!config.measurementId) {
2810
+ return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
2811
+ }
2812
+ const clientId = event.userData?.clientId || event.userData?.externalId || "anonymous_client";
2813
+ const ga4Payload = {
2814
+ client_id: clientId,
2815
+ events: [
2816
+ {
2817
+ name: event.eventName.toLowerCase().replace(/[^a-z0-9_]/g, "_"),
2818
+ params: {
2819
+ ...event.params || {},
2820
+ ...event.customData || {},
2821
+ event_id: event.eventId
2822
+ }
2823
+ }
2824
+ ]
2825
+ };
2826
+ if (options?.simulateInDebug) {
2827
+ options.onLog?.("[GA4 Measurement Protocol SIMULATED]", ga4Payload);
2828
+ return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2829
+ }
2830
+ try {
2831
+ let url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(
2832
+ config.measurementId
2833
+ )}`;
2834
+ if (config.measurementProtocolSecret) {
2835
+ url += `&api_secret=${encodeURIComponent(config.measurementProtocolSecret)}`;
2836
+ }
2837
+ const res = await fetcher(url, {
2838
+ method: "POST",
2839
+ headers: { "Content-Type": "application/json" },
2840
+ body: JSON.stringify(ga4Payload)
2841
+ });
2842
+ return {
2843
+ provider: "google",
2844
+ success: res.ok,
2845
+ status: res.status,
2846
+ data: { status: res.status }
2847
+ };
2848
+ } catch (err) {
2849
+ return {
2850
+ provider: "google",
2851
+ success: false,
2852
+ error: err instanceof Error ? err.message : String(err)
2853
+ };
2854
+ }
2855
+ }
2856
+ async function sendCustomWebhookEvent(event, config, options) {
2857
+ const fetcher = options?.fetchFn || globalThis.fetch;
2858
+ if (!config.endpointUrl) {
2859
+ return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
2860
+ }
2861
+ const webhookPayload = {
2862
+ event: event.eventName,
2863
+ eventId: event.eventId,
2864
+ timestamp: event.eventTime ? event.eventTime * 1e3 : Date.now(),
2865
+ userData: event.userData,
2866
+ params: event.params,
2867
+ customData: event.customData,
2868
+ sourceUrl: event.eventSourceUrl || options?.sourceUrl
2869
+ };
2870
+ if (options?.simulateInDebug) {
2871
+ options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2872
+ return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2873
+ }
2874
+ try {
2875
+ const res = await fetcher(config.endpointUrl, {
2876
+ method: "POST",
2877
+ headers: {
2878
+ "Content-Type": "application/json",
2879
+ ...config.headers || {}
2880
+ },
2881
+ body: JSON.stringify(webhookPayload)
2882
+ });
2883
+ const resJson = await res.json().catch(() => ({}));
2884
+ return {
2885
+ provider: "custom",
2886
+ success: res.ok,
2887
+ status: res.status,
2888
+ data: resJson
2889
+ };
2890
+ } catch (err) {
2891
+ return {
2892
+ provider: "custom",
2893
+ success: false,
2894
+ error: err instanceof Error ? err.message : String(err)
2895
+ };
2896
+ }
2897
+ }
2898
+ async function dispatchServerTracking(event, config, options) {
2899
+ const eventId = event.eventId || generateTrackingEventId();
2900
+ const eventWithId = { ...event, eventId };
2901
+ if (!config || config.enabled === false) {
2902
+ return {
2903
+ success: true,
2904
+ eventId,
2905
+ skipped: true,
2906
+ reason: "Tracking is disabled globally",
2907
+ results: {}
2908
+ };
2909
+ }
2910
+ const isDebug = Boolean(config.debugMode);
2911
+ const simulate = isDebug && options?.simulateInDebug !== false;
2912
+ const mergedOptions = {
2913
+ ...options,
2914
+ simulateInDebug: simulate,
2915
+ onLog: (msg, data) => {
2916
+ if (isDebug) {
2917
+ console.log(`[KUBUILD Tracking] ${msg}`, data || "");
2918
+ }
2919
+ options?.onLog?.(msg, data);
2920
+ }
2921
+ };
2922
+ const providers = config.providers || {};
2923
+ const dispatchPromises = [];
2924
+ if (providers.meta && providers.meta.enabled !== false && providers.meta.capiEnabled) {
2925
+ dispatchPromises.push(
2926
+ sendMetaCapiEvent(eventWithId, providers.meta, mergedOptions).then((result) => ({
2927
+ key: "meta",
2928
+ result
2929
+ }))
2930
+ );
2931
+ }
2932
+ if (providers.tiktok && providers.tiktok.enabled !== false && providers.tiktok.eventsApiEnabled) {
2933
+ dispatchPromises.push(
2934
+ sendTikTokEventsApi(eventWithId, providers.tiktok, mergedOptions).then((result) => ({
2935
+ key: "tiktok",
2936
+ result
2937
+ }))
2938
+ );
2939
+ }
2940
+ if (providers.google && providers.google.enabled !== false && providers.google.measurementId && providers.google.measurementProtocolSecret) {
2941
+ dispatchPromises.push(
2942
+ sendGa4MeasurementEvent(eventWithId, providers.google, mergedOptions).then((result) => ({
2943
+ key: "google",
2944
+ result
2945
+ }))
2946
+ );
2947
+ }
2948
+ if (providers.custom && providers.custom.enabled !== false && providers.custom.endpointUrl) {
2949
+ dispatchPromises.push(
2950
+ sendCustomWebhookEvent(eventWithId, providers.custom, mergedOptions).then((result) => ({
2951
+ key: "custom",
2952
+ result
2953
+ }))
2954
+ );
2955
+ }
2956
+ const settled = await Promise.allSettled(dispatchPromises);
2957
+ const results = {};
2958
+ let overallSuccess = true;
2959
+ for (const item of settled) {
2960
+ if (item.status === "fulfilled") {
2961
+ results[item.value.key] = item.value.result;
2962
+ if (!item.value.result.success) {
2963
+ overallSuccess = false;
2964
+ }
2965
+ } else {
2966
+ overallSuccess = false;
2967
+ }
2968
+ }
2969
+ return {
2970
+ success: overallSuccess,
2971
+ eventId,
2972
+ results
2973
+ };
2974
+ }
2975
+
2587
2976
  // src/io/exporter.ts
2588
2977
  var import_fflate = require("fflate");
2589
2978
  var import_schema6 = require("@kubuild/schema");
@@ -5748,6 +6137,7 @@ function isFormValid(errors) {
5748
6137
  defaultIdGenerator,
5749
6138
  defaultMigrationRegistry,
5750
6139
  defaultRenameAssetStrategy,
6140
+ dispatchServerTracking,
5751
6141
  duplicateNode,
5752
6142
  evaluateActionCondition,
5753
6143
  evaluateCondition,
@@ -5767,6 +6157,7 @@ function isFormValid(errors) {
5767
6157
  findMissingComponentNodes,
5768
6158
  findNodeById,
5769
6159
  findNodeLocation,
6160
+ generateTrackingEventId,
5770
6161
  getActiveArtboard,
5771
6162
  getAncestorChain,
5772
6163
  getComponentArtboards,
@@ -5776,6 +6167,7 @@ function isFormValid(errors) {
5776
6167
  getPageArtboards,
5777
6168
  getParentNodeId,
5778
6169
  hasTemplateExpressions,
6170
+ hashSha256,
5779
6171
  importPackage,
5780
6172
  importStoraPackage,
5781
6173
  insertNode,
@@ -5793,6 +6185,9 @@ function isFormValid(errors) {
5793
6185
  loadProjectDocument,
5794
6186
  migrateDocument,
5795
6187
  moveNode,
6188
+ normalizeEmail,
6189
+ normalizePhone,
6190
+ normalizeText,
5796
6191
  preflightPackage,
5797
6192
  previewImportPackage,
5798
6193
  remapAssetReferences,
@@ -5808,6 +6203,10 @@ function isFormValid(errors) {
5808
6203
  sanitizeHtml,
5809
6204
  sanitizeUrl,
5810
6205
  saveDraftAsTemplate,
6206
+ sendCustomWebhookEvent,
6207
+ sendGa4MeasurementEvent,
6208
+ sendMetaCapiEvent,
6209
+ sendTikTokEventsApi,
5811
6210
  setActiveArtboard,
5812
6211
  sha256Sync,
5813
6212
  ungroupNodeFrame,