@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.js CHANGED
@@ -2454,6 +2454,385 @@ function createRuntimeStore(initialState) {
2454
2454
  return new RuntimeStateStore(initialState);
2455
2455
  }
2456
2456
 
2457
+ // src/runtime/server-tracking.ts
2458
+ function normalizeEmail(email) {
2459
+ return (email || "").trim().toLowerCase();
2460
+ }
2461
+ function normalizePhone(phone) {
2462
+ return (phone || "").replace(/[^0-9]/g, "");
2463
+ }
2464
+ function normalizeText(text) {
2465
+ return (text || "").trim().toLowerCase();
2466
+ }
2467
+ async function hashSha256(value) {
2468
+ const normalized = (value || "").trim();
2469
+ if (!normalized) return "";
2470
+ if (typeof globalThis.crypto?.subtle?.digest === "function") {
2471
+ const encoder = new TextEncoder();
2472
+ const data = encoder.encode(normalized);
2473
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
2474
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2475
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2476
+ }
2477
+ throw new Error("Web Crypto API (crypto.subtle) is not available in current runtime environment.");
2478
+ }
2479
+ function generateTrackingEventId(prefix = "evt") {
2480
+ const timestamp = Date.now().toString(36);
2481
+ const randomPart = Math.random().toString(36).substring(2, 10);
2482
+ return `${prefix}_${timestamp}_${randomPart}`;
2483
+ }
2484
+ async function sendMetaCapiEvent(event, config, options) {
2485
+ const fetcher = options?.fetchFn || globalThis.fetch;
2486
+ if (!config.pixelId) {
2487
+ return {
2488
+ provider: "meta",
2489
+ success: false,
2490
+ error: "Meta Pixel ID is missing"
2491
+ };
2492
+ }
2493
+ const rawUserData = event.userData || {};
2494
+ const hashedUserData = {};
2495
+ if (rawUserData.email) {
2496
+ hashedUserData.em = [await hashSha256(normalizeEmail(String(rawUserData.email)))];
2497
+ }
2498
+ if (rawUserData.phone) {
2499
+ hashedUserData.ph = [await hashSha256(normalizePhone(String(rawUserData.phone)))];
2500
+ }
2501
+ if (rawUserData.firstName) {
2502
+ hashedUserData.fn = [await hashSha256(normalizeText(String(rawUserData.firstName)))];
2503
+ }
2504
+ if (rawUserData.lastName) {
2505
+ hashedUserData.ln = [await hashSha256(normalizeText(String(rawUserData.lastName)))];
2506
+ }
2507
+ if (rawUserData.city) {
2508
+ hashedUserData.ct = [await hashSha256(normalizeText(String(rawUserData.city)))];
2509
+ }
2510
+ if (rawUserData.state) {
2511
+ hashedUserData.st = [await hashSha256(normalizeText(String(rawUserData.state)))];
2512
+ }
2513
+ if (rawUserData.zip) {
2514
+ hashedUserData.zp = [await hashSha256(normalizeText(String(rawUserData.zip)))];
2515
+ }
2516
+ if (rawUserData.country) {
2517
+ hashedUserData.country = [await hashSha256(normalizeText(String(rawUserData.country)))];
2518
+ }
2519
+ const clientIp = rawUserData.clientIp || options?.clientIp;
2520
+ if (clientIp) hashedUserData.client_ip_address = clientIp;
2521
+ const clientUserAgent = rawUserData.clientUserAgent || options?.clientUserAgent;
2522
+ if (clientUserAgent) hashedUserData.client_user_agent = clientUserAgent;
2523
+ if (rawUserData.fbp) hashedUserData.fbp = rawUserData.fbp;
2524
+ if (rawUserData.fbc) hashedUserData.fbc = rawUserData.fbc;
2525
+ if (rawUserData.externalId) {
2526
+ hashedUserData.external_id = [await hashSha256(String(rawUserData.externalId))];
2527
+ }
2528
+ const eventTime = event.eventTime || Math.floor(Date.now() / 1e3);
2529
+ const eventSourceUrl = event.eventSourceUrl || options?.sourceUrl;
2530
+ const capiPayload = {
2531
+ data: [
2532
+ {
2533
+ event_name: event.eventName,
2534
+ event_time: eventTime,
2535
+ event_id: event.eventId,
2536
+ event_source_url: eventSourceUrl,
2537
+ action_source: event.actionSource || "website",
2538
+ user_data: hashedUserData,
2539
+ custom_data: {
2540
+ ...event.params || {},
2541
+ ...event.customData || {}
2542
+ }
2543
+ }
2544
+ ]
2545
+ };
2546
+ if (config.testEventCode) {
2547
+ capiPayload.test_event_code = config.testEventCode;
2548
+ }
2549
+ if (options?.simulateInDebug) {
2550
+ options.onLog?.("[Meta CAPI SIMULATED]", capiPayload);
2551
+ return { provider: "meta", success: true, data: { simulated: true, payload: capiPayload } };
2552
+ }
2553
+ try {
2554
+ const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(config.pixelId)}/events`;
2555
+ const headers = {
2556
+ "Content-Type": "application/json"
2557
+ };
2558
+ if (config.capiAccessToken) {
2559
+ headers["Authorization"] = `Bearer ${config.capiAccessToken}`;
2560
+ }
2561
+ const res = await fetcher(url, {
2562
+ method: "POST",
2563
+ headers,
2564
+ body: JSON.stringify(capiPayload)
2565
+ });
2566
+ const resJson = await res.json().catch(() => ({}));
2567
+ if (!res.ok) {
2568
+ return {
2569
+ provider: "meta",
2570
+ success: false,
2571
+ status: res.status,
2572
+ error: resJson?.error?.message || `HTTP ${res.status}`,
2573
+ data: resJson
2574
+ };
2575
+ }
2576
+ return {
2577
+ provider: "meta",
2578
+ success: true,
2579
+ status: res.status,
2580
+ data: resJson
2581
+ };
2582
+ } catch (err) {
2583
+ return {
2584
+ provider: "meta",
2585
+ success: false,
2586
+ error: err instanceof Error ? err.message : String(err)
2587
+ };
2588
+ }
2589
+ }
2590
+ async function sendTikTokEventsApi(event, config, options) {
2591
+ const fetcher = options?.fetchFn || globalThis.fetch;
2592
+ if (!config.pixelId) {
2593
+ return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
2594
+ }
2595
+ const rawUserData = event.userData || {};
2596
+ const user = {};
2597
+ if (rawUserData.email) {
2598
+ user.email = await hashSha256(normalizeEmail(String(rawUserData.email)));
2599
+ }
2600
+ if (rawUserData.phone) {
2601
+ user.phone_number = await hashSha256(normalizePhone(String(rawUserData.phone)));
2602
+ }
2603
+ if (rawUserData.clientIp || options?.clientIp) {
2604
+ user.ip = rawUserData.clientIp || options?.clientIp;
2605
+ }
2606
+ if (rawUserData.clientUserAgent || options?.clientUserAgent) {
2607
+ user.user_agent = rawUserData.clientUserAgent || options?.clientUserAgent;
2608
+ }
2609
+ const tiktokPayload = {
2610
+ event_source: "web",
2611
+ event_source_id: config.pixelId,
2612
+ data: [
2613
+ {
2614
+ event: event.eventName,
2615
+ event_id: event.eventId,
2616
+ timestamp: new Date(event.eventTime ? event.eventTime * 1e3 : Date.now()).toISOString(),
2617
+ user,
2618
+ properties: {
2619
+ ...event.params || {},
2620
+ ...event.customData || {}
2621
+ },
2622
+ page: {
2623
+ url: event.eventSourceUrl || options?.sourceUrl
2624
+ }
2625
+ }
2626
+ ]
2627
+ };
2628
+ if (config.testEventCode) {
2629
+ tiktokPayload.test_event_code = config.testEventCode;
2630
+ }
2631
+ if (options?.simulateInDebug) {
2632
+ options.onLog?.("[TikTok Events API SIMULATED]", tiktokPayload);
2633
+ return { provider: "tiktok", success: true, data: { simulated: true, payload: tiktokPayload } };
2634
+ }
2635
+ try {
2636
+ const url = "https://business-api.tiktok.com/open_api/v1.3/event/track/";
2637
+ const headers = {
2638
+ "Content-Type": "application/json"
2639
+ };
2640
+ if (config.accessToken) {
2641
+ headers["Access-Token"] = config.accessToken;
2642
+ }
2643
+ const res = await fetcher(url, {
2644
+ method: "POST",
2645
+ headers,
2646
+ body: JSON.stringify(tiktokPayload)
2647
+ });
2648
+ const resJson = await res.json().catch(() => ({}));
2649
+ if (!res.ok) {
2650
+ return {
2651
+ provider: "tiktok",
2652
+ success: false,
2653
+ status: res.status,
2654
+ error: resJson?.message || `HTTP ${res.status}`,
2655
+ data: resJson
2656
+ };
2657
+ }
2658
+ return { provider: "tiktok", success: true, status: res.status, data: resJson };
2659
+ } catch (err) {
2660
+ return {
2661
+ provider: "tiktok",
2662
+ success: false,
2663
+ error: err instanceof Error ? err.message : String(err)
2664
+ };
2665
+ }
2666
+ }
2667
+ async function sendGa4MeasurementEvent(event, config, options) {
2668
+ const fetcher = options?.fetchFn || globalThis.fetch;
2669
+ if (!config.measurementId) {
2670
+ return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
2671
+ }
2672
+ const clientId = event.userData?.clientId || event.userData?.externalId || "anonymous_client";
2673
+ const ga4Payload = {
2674
+ client_id: clientId,
2675
+ events: [
2676
+ {
2677
+ name: event.eventName.toLowerCase().replace(/[^a-z0-9_]/g, "_"),
2678
+ params: {
2679
+ ...event.params || {},
2680
+ ...event.customData || {},
2681
+ event_id: event.eventId
2682
+ }
2683
+ }
2684
+ ]
2685
+ };
2686
+ if (options?.simulateInDebug) {
2687
+ options.onLog?.("[GA4 Measurement Protocol SIMULATED]", ga4Payload);
2688
+ return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2689
+ }
2690
+ try {
2691
+ let url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(
2692
+ config.measurementId
2693
+ )}`;
2694
+ if (config.measurementProtocolSecret) {
2695
+ url += `&api_secret=${encodeURIComponent(config.measurementProtocolSecret)}`;
2696
+ }
2697
+ const res = await fetcher(url, {
2698
+ method: "POST",
2699
+ headers: { "Content-Type": "application/json" },
2700
+ body: JSON.stringify(ga4Payload)
2701
+ });
2702
+ return {
2703
+ provider: "google",
2704
+ success: res.ok,
2705
+ status: res.status,
2706
+ data: { status: res.status }
2707
+ };
2708
+ } catch (err) {
2709
+ return {
2710
+ provider: "google",
2711
+ success: false,
2712
+ error: err instanceof Error ? err.message : String(err)
2713
+ };
2714
+ }
2715
+ }
2716
+ async function sendCustomWebhookEvent(event, config, options) {
2717
+ const fetcher = options?.fetchFn || globalThis.fetch;
2718
+ if (!config.endpointUrl) {
2719
+ return { provider: "custom", success: false, error: "Custom Webhook URL is missing" };
2720
+ }
2721
+ const webhookPayload = {
2722
+ event: event.eventName,
2723
+ eventId: event.eventId,
2724
+ timestamp: event.eventTime ? event.eventTime * 1e3 : Date.now(),
2725
+ userData: event.userData,
2726
+ params: event.params,
2727
+ customData: event.customData,
2728
+ sourceUrl: event.eventSourceUrl || options?.sourceUrl
2729
+ };
2730
+ if (options?.simulateInDebug) {
2731
+ options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2732
+ return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2733
+ }
2734
+ try {
2735
+ const res = await fetcher(config.endpointUrl, {
2736
+ method: "POST",
2737
+ headers: {
2738
+ "Content-Type": "application/json",
2739
+ ...config.headers || {}
2740
+ },
2741
+ body: JSON.stringify(webhookPayload)
2742
+ });
2743
+ const resJson = await res.json().catch(() => ({}));
2744
+ return {
2745
+ provider: "custom",
2746
+ success: res.ok,
2747
+ status: res.status,
2748
+ data: resJson
2749
+ };
2750
+ } catch (err) {
2751
+ return {
2752
+ provider: "custom",
2753
+ success: false,
2754
+ error: err instanceof Error ? err.message : String(err)
2755
+ };
2756
+ }
2757
+ }
2758
+ async function dispatchServerTracking(event, config, options) {
2759
+ const eventId = event.eventId || generateTrackingEventId();
2760
+ const eventWithId = { ...event, eventId };
2761
+ if (!config || config.enabled === false) {
2762
+ return {
2763
+ success: true,
2764
+ eventId,
2765
+ skipped: true,
2766
+ reason: "Tracking is disabled globally",
2767
+ results: {}
2768
+ };
2769
+ }
2770
+ const isDebug = Boolean(config.debugMode);
2771
+ const simulate = isDebug && options?.simulateInDebug !== false;
2772
+ const mergedOptions = {
2773
+ ...options,
2774
+ simulateInDebug: simulate,
2775
+ onLog: (msg, data) => {
2776
+ if (isDebug) {
2777
+ console.log(`[KUBUILD Tracking] ${msg}`, data || "");
2778
+ }
2779
+ options?.onLog?.(msg, data);
2780
+ }
2781
+ };
2782
+ const providers = config.providers || {};
2783
+ const dispatchPromises = [];
2784
+ if (providers.meta && providers.meta.enabled !== false && providers.meta.capiEnabled) {
2785
+ dispatchPromises.push(
2786
+ sendMetaCapiEvent(eventWithId, providers.meta, mergedOptions).then((result) => ({
2787
+ key: "meta",
2788
+ result
2789
+ }))
2790
+ );
2791
+ }
2792
+ if (providers.tiktok && providers.tiktok.enabled !== false && providers.tiktok.eventsApiEnabled) {
2793
+ dispatchPromises.push(
2794
+ sendTikTokEventsApi(eventWithId, providers.tiktok, mergedOptions).then((result) => ({
2795
+ key: "tiktok",
2796
+ result
2797
+ }))
2798
+ );
2799
+ }
2800
+ if (providers.google && providers.google.enabled !== false && providers.google.measurementId && providers.google.measurementProtocolSecret) {
2801
+ dispatchPromises.push(
2802
+ sendGa4MeasurementEvent(eventWithId, providers.google, mergedOptions).then((result) => ({
2803
+ key: "google",
2804
+ result
2805
+ }))
2806
+ );
2807
+ }
2808
+ if (providers.custom && providers.custom.enabled !== false && providers.custom.endpointUrl) {
2809
+ dispatchPromises.push(
2810
+ sendCustomWebhookEvent(eventWithId, providers.custom, mergedOptions).then((result) => ({
2811
+ key: "custom",
2812
+ result
2813
+ }))
2814
+ );
2815
+ }
2816
+ const settled = await Promise.allSettled(dispatchPromises);
2817
+ const results = {};
2818
+ let overallSuccess = true;
2819
+ for (const item of settled) {
2820
+ if (item.status === "fulfilled") {
2821
+ results[item.value.key] = item.value.result;
2822
+ if (!item.value.result.success) {
2823
+ overallSuccess = false;
2824
+ }
2825
+ } else {
2826
+ overallSuccess = false;
2827
+ }
2828
+ }
2829
+ return {
2830
+ success: overallSuccess,
2831
+ eventId,
2832
+ results
2833
+ };
2834
+ }
2835
+
2457
2836
  // src/io/exporter.ts
2458
2837
  import { zipSync, strToU8 } from "fflate";
2459
2838
  import {
@@ -5642,6 +6021,7 @@ export {
5642
6021
  defaultIdGenerator,
5643
6022
  defaultMigrationRegistry,
5644
6023
  defaultRenameAssetStrategy,
6024
+ dispatchServerTracking,
5645
6025
  duplicateNode,
5646
6026
  evaluateActionCondition,
5647
6027
  evaluateCondition,
@@ -5661,6 +6041,7 @@ export {
5661
6041
  findMissingComponentNodes,
5662
6042
  findNodeById,
5663
6043
  findNodeLocation,
6044
+ generateTrackingEventId,
5664
6045
  getActiveArtboard,
5665
6046
  getAncestorChain,
5666
6047
  getComponentArtboards,
@@ -5670,6 +6051,7 @@ export {
5670
6051
  getPageArtboards,
5671
6052
  getParentNodeId,
5672
6053
  hasTemplateExpressions,
6054
+ hashSha256,
5673
6055
  importPackage,
5674
6056
  importStoraPackage,
5675
6057
  insertNode,
@@ -5687,6 +6069,9 @@ export {
5687
6069
  loadProjectDocument,
5688
6070
  migrateDocument,
5689
6071
  moveNode,
6072
+ normalizeEmail,
6073
+ normalizePhone,
6074
+ normalizeText,
5690
6075
  preflightPackage,
5691
6076
  previewImportPackage,
5692
6077
  remapAssetReferences,
@@ -5702,6 +6087,10 @@ export {
5702
6087
  sanitizeHtml,
5703
6088
  sanitizeUrl,
5704
6089
  saveDraftAsTemplate,
6090
+ sendCustomWebhookEvent,
6091
+ sendGa4MeasurementEvent,
6092
+ sendMetaCapiEvent,
6093
+ sendTikTokEventsApi,
5705
6094
  setActiveArtboard,
5706
6095
  sha256Sync,
5707
6096
  ungroupNodeFrame,