@kubuild/core 0.5.0 → 0.7.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
@@ -1,9 +1,9 @@
1
1
  // src/document/document-utils.ts
2
- import { SCHEMA_NAME } from "@kubuild/schema";
2
+ import { SCHEMA_NAME, CURRENT_SCHEMA_VERSION } from "@kubuild/schema";
3
3
  function createBlankDocument(title = "Untitled Page") {
4
4
  return {
5
5
  schema: SCHEMA_NAME,
6
- version: "1.0.0",
6
+ version: CURRENT_SCHEMA_VERSION,
7
7
  metadata: {
8
8
  title,
9
9
  description: "",
@@ -889,7 +889,7 @@ import {
889
889
  SCHEMA_NAME as SCHEMA_NAME2,
890
890
  PROJECT_SCHEMA_NAME,
891
891
  CURRENT_PROJECT_SCHEMA_VERSION,
892
- CURRENT_SCHEMA_VERSION,
892
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2,
893
893
  ARTBOARD_REFERENCE_NODE_TYPE
894
894
  } from "@kubuild/schema";
895
895
  var now = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -947,14 +947,14 @@ function createComponentArtboard(node, options = {}) {
947
947
  const triggerId = options.triggerId ?? (typeof node.props?.modalId === "string" && node.props.modalId.trim().length > 0 ? node.props.modalId : node.id);
948
948
  const document = {
949
949
  schema: SCHEMA_NAME2,
950
- version: CURRENT_SCHEMA_VERSION,
950
+ version: CURRENT_SCHEMA_VERSION2,
951
951
  metadata: {
952
952
  title: name,
953
953
  description: "",
954
954
  author: "",
955
955
  tags: [],
956
956
  category: "component",
957
- version: CURRENT_SCHEMA_VERSION,
957
+ version: CURRENT_SCHEMA_VERSION2,
958
958
  createdAt: now(),
959
959
  updatedAt: now()
960
960
  },
@@ -1411,6 +1411,15 @@ var DocumentHistoryManager = class {
1411
1411
  var FORBIDDEN_KEY_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1412
1412
  function resolveBinding(binding, context) {
1413
1413
  const segments = binding.key.split(".").filter(Boolean);
1414
+ if (segments.some((segment) => FORBIDDEN_KEY_SEGMENTS.has(segment))) {
1415
+ return applyMissingPolicy(binding);
1416
+ }
1417
+ if (context?.variables && typeof context.variables === "object" && Object.prototype.hasOwnProperty.call(context.variables, binding.key)) {
1418
+ const directValue = context.variables[binding.key];
1419
+ if (directValue !== void 0 && typeof directValue !== "function") {
1420
+ return { status: "resolved", value: directValue };
1421
+ }
1422
+ }
1414
1423
  let current = context?.variables;
1415
1424
  for (const segment of segments) {
1416
1425
  if (FORBIDDEN_KEY_SEGMENTS.has(segment)) {
@@ -1982,14 +1991,14 @@ var ActionPipelineExecutor = class {
1982
1991
  async executeSingleStep(step, context, parentSignal, options) {
1983
1992
  const stepStartTime = Date.now();
1984
1993
  if (step.condition && !evaluateActionCondition(step.condition, context)) {
1985
- const skippedResult = {
1994
+ const skippedResult2 = {
1986
1995
  stepId: step.id,
1987
1996
  stepType: step.type,
1988
1997
  status: "skipped",
1989
1998
  durationMs: Date.now() - stepStartTime
1990
1999
  };
1991
- options?.onStepComplete?.(step, skippedResult, context);
1992
- return skippedResult;
2000
+ options?.onStepComplete?.(step, skippedResult2, context);
2001
+ return skippedResult2;
1993
2002
  }
1994
2003
  options?.onStepStart?.(step, context);
1995
2004
  const stepTimeout = step.timeout;
@@ -2454,11 +2463,561 @@ function createRuntimeStore(initialState) {
2454
2463
  return new RuntimeStateStore(initialState);
2455
2464
  }
2456
2465
 
2466
+ // src/runtime/server-tracking.ts
2467
+ function normalizeEmail(email) {
2468
+ return (email || "").trim().toLowerCase();
2469
+ }
2470
+ function normalizePhone(phone) {
2471
+ return (phone || "").replace(/[^0-9]/g, "");
2472
+ }
2473
+ function normalizeText(text) {
2474
+ return (text || "").trim().toLowerCase();
2475
+ }
2476
+ async function hashSha256(value) {
2477
+ const normalized = (value || "").trim();
2478
+ if (!normalized) return "";
2479
+ if (typeof globalThis.crypto?.subtle?.digest === "function") {
2480
+ const encoder = new TextEncoder();
2481
+ const data = encoder.encode(normalized);
2482
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", data);
2483
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2484
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2485
+ }
2486
+ throw new Error("Web Crypto API (crypto.subtle) is not available in current runtime environment.");
2487
+ }
2488
+ function generateTrackingEventId(prefix = "evt") {
2489
+ const timestamp = Date.now().toString(36);
2490
+ const randomPart = Math.random().toString(36).substring(2, 10);
2491
+ return `${prefix}_${timestamp}_${randomPart}`;
2492
+ }
2493
+ async function sendMetaCapiEvent(event, config, secrets, options) {
2494
+ const fetcher = options?.fetchFn || globalThis.fetch;
2495
+ if (!config.pixelId) {
2496
+ return {
2497
+ provider: "meta",
2498
+ success: false,
2499
+ error: "Meta Pixel ID is missing"
2500
+ };
2501
+ }
2502
+ const rawUserData = event.userData || {};
2503
+ const hashedUserData = {};
2504
+ if (rawUserData.email) {
2505
+ hashedUserData.em = [await hashSha256(normalizeEmail(String(rawUserData.email)))];
2506
+ }
2507
+ if (rawUserData.phone) {
2508
+ hashedUserData.ph = [await hashSha256(normalizePhone(String(rawUserData.phone)))];
2509
+ }
2510
+ if (rawUserData.firstName) {
2511
+ hashedUserData.fn = [await hashSha256(normalizeText(String(rawUserData.firstName)))];
2512
+ }
2513
+ if (rawUserData.lastName) {
2514
+ hashedUserData.ln = [await hashSha256(normalizeText(String(rawUserData.lastName)))];
2515
+ }
2516
+ if (rawUserData.city) {
2517
+ hashedUserData.ct = [await hashSha256(normalizeText(String(rawUserData.city)))];
2518
+ }
2519
+ if (rawUserData.state) {
2520
+ hashedUserData.st = [await hashSha256(normalizeText(String(rawUserData.state)))];
2521
+ }
2522
+ if (rawUserData.zip) {
2523
+ hashedUserData.zp = [await hashSha256(normalizeText(String(rawUserData.zip)))];
2524
+ }
2525
+ if (rawUserData.country) {
2526
+ hashedUserData.country = [await hashSha256(normalizeText(String(rawUserData.country)))];
2527
+ }
2528
+ const clientIp = rawUserData.clientIp || options?.clientIp;
2529
+ if (clientIp) hashedUserData.client_ip_address = clientIp;
2530
+ const clientUserAgent = rawUserData.clientUserAgent || options?.clientUserAgent;
2531
+ if (clientUserAgent) hashedUserData.client_user_agent = clientUserAgent;
2532
+ if (rawUserData.fbp) hashedUserData.fbp = rawUserData.fbp;
2533
+ if (rawUserData.fbc) hashedUserData.fbc = rawUserData.fbc;
2534
+ if (rawUserData.externalId) {
2535
+ hashedUserData.external_id = [await hashSha256(String(rawUserData.externalId))];
2536
+ }
2537
+ const eventTime = event.eventTime || Math.floor(Date.now() / 1e3);
2538
+ const eventSourceUrl = event.eventSourceUrl || options?.sourceUrl;
2539
+ const capiPayload = {
2540
+ data: [
2541
+ {
2542
+ event_name: event.eventName,
2543
+ event_time: eventTime,
2544
+ event_id: event.eventId,
2545
+ event_source_url: eventSourceUrl,
2546
+ action_source: event.actionSource || "website",
2547
+ user_data: hashedUserData,
2548
+ custom_data: {
2549
+ ...event.params || {},
2550
+ ...event.customData || {}
2551
+ }
2552
+ }
2553
+ ]
2554
+ };
2555
+ if (config.testEventCode) {
2556
+ capiPayload.test_event_code = config.testEventCode;
2557
+ }
2558
+ if (options?.simulateInDebug) {
2559
+ options.onLog?.("[Meta CAPI SIMULATED]", capiPayload);
2560
+ return { provider: "meta", success: true, data: { simulated: true, payload: capiPayload } };
2561
+ }
2562
+ try {
2563
+ const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(config.pixelId)}/events`;
2564
+ const headers = {
2565
+ "Content-Type": "application/json"
2566
+ };
2567
+ if (!secrets?.capiAccessToken) {
2568
+ return skippedResult("meta", "No Meta CAPI access token resolved for this credential");
2569
+ }
2570
+ headers["Authorization"] = `Bearer ${secrets.capiAccessToken}`;
2571
+ const res = await fetcher(url, {
2572
+ method: "POST",
2573
+ headers,
2574
+ body: JSON.stringify(capiPayload)
2575
+ });
2576
+ const resJson = await res.json().catch(() => ({}));
2577
+ if (!res.ok) {
2578
+ return {
2579
+ provider: "meta",
2580
+ success: false,
2581
+ status: res.status,
2582
+ error: resJson?.error?.message || `HTTP ${res.status}`,
2583
+ data: resJson
2584
+ };
2585
+ }
2586
+ return {
2587
+ provider: "meta",
2588
+ success: true,
2589
+ status: res.status,
2590
+ data: resJson
2591
+ };
2592
+ } catch (err) {
2593
+ return {
2594
+ provider: "meta",
2595
+ success: false,
2596
+ error: err instanceof Error ? err.message : String(err)
2597
+ };
2598
+ }
2599
+ }
2600
+ async function sendTikTokEventsApi(event, config, secrets, options) {
2601
+ const fetcher = options?.fetchFn || globalThis.fetch;
2602
+ if (!config.pixelId) {
2603
+ return { provider: "tiktok", success: false, error: "TikTok Pixel ID is missing" };
2604
+ }
2605
+ const rawUserData = event.userData || {};
2606
+ const user = {};
2607
+ if (rawUserData.email) {
2608
+ user.email = await hashSha256(normalizeEmail(String(rawUserData.email)));
2609
+ }
2610
+ if (rawUserData.phone) {
2611
+ user.phone_number = await hashSha256(normalizePhone(String(rawUserData.phone)));
2612
+ }
2613
+ if (rawUserData.clientIp || options?.clientIp) {
2614
+ user.ip = rawUserData.clientIp || options?.clientIp;
2615
+ }
2616
+ if (rawUserData.clientUserAgent || options?.clientUserAgent) {
2617
+ user.user_agent = rawUserData.clientUserAgent || options?.clientUserAgent;
2618
+ }
2619
+ const tiktokPayload = {
2620
+ event_source: "web",
2621
+ event_source_id: config.pixelId,
2622
+ data: [
2623
+ {
2624
+ event: event.eventName,
2625
+ event_id: event.eventId,
2626
+ timestamp: new Date(event.eventTime ? event.eventTime * 1e3 : Date.now()).toISOString(),
2627
+ user,
2628
+ properties: {
2629
+ ...event.params || {},
2630
+ ...event.customData || {}
2631
+ },
2632
+ page: {
2633
+ url: event.eventSourceUrl || options?.sourceUrl
2634
+ }
2635
+ }
2636
+ ]
2637
+ };
2638
+ if (config.testEventCode) {
2639
+ tiktokPayload.test_event_code = config.testEventCode;
2640
+ }
2641
+ if (options?.simulateInDebug) {
2642
+ options.onLog?.("[TikTok Events API SIMULATED]", tiktokPayload);
2643
+ return { provider: "tiktok", success: true, data: { simulated: true, payload: tiktokPayload } };
2644
+ }
2645
+ try {
2646
+ const url = "https://business-api.tiktok.com/open_api/v1.3/event/track/";
2647
+ const headers = {
2648
+ "Content-Type": "application/json"
2649
+ };
2650
+ if (!secrets?.accessToken) {
2651
+ return skippedResult("tiktok", "No TikTok Events API access token resolved for this credential");
2652
+ }
2653
+ headers["Access-Token"] = secrets.accessToken;
2654
+ const res = await fetcher(url, {
2655
+ method: "POST",
2656
+ headers,
2657
+ body: JSON.stringify(tiktokPayload)
2658
+ });
2659
+ const resJson = await res.json().catch(() => ({}));
2660
+ if (!res.ok) {
2661
+ return {
2662
+ provider: "tiktok",
2663
+ success: false,
2664
+ status: res.status,
2665
+ error: resJson?.message || `HTTP ${res.status}`,
2666
+ data: resJson
2667
+ };
2668
+ }
2669
+ return { provider: "tiktok", success: true, status: res.status, data: resJson };
2670
+ } catch (err) {
2671
+ return {
2672
+ provider: "tiktok",
2673
+ success: false,
2674
+ error: err instanceof Error ? err.message : String(err)
2675
+ };
2676
+ }
2677
+ }
2678
+ async function sendGa4MeasurementEvent(event, config, secrets, options) {
2679
+ const fetcher = options?.fetchFn || globalThis.fetch;
2680
+ if (!config.measurementId) {
2681
+ return { provider: "google", success: false, error: "GA4 Measurement ID is missing" };
2682
+ }
2683
+ const clientId = event.userData?.clientId || event.userData?.externalId || "anonymous_client";
2684
+ const ga4Payload = {
2685
+ client_id: clientId,
2686
+ events: [
2687
+ {
2688
+ name: event.eventName.toLowerCase().replace(/[^a-z0-9_]/g, "_"),
2689
+ params: {
2690
+ ...event.params || {},
2691
+ ...event.customData || {},
2692
+ event_id: event.eventId
2693
+ }
2694
+ }
2695
+ ]
2696
+ };
2697
+ if (options?.simulateInDebug) {
2698
+ options.onLog?.("[GA4 Measurement Protocol SIMULATED]", ga4Payload);
2699
+ return { provider: "google", success: true, data: { simulated: true, payload: ga4Payload } };
2700
+ }
2701
+ try {
2702
+ if (!secrets?.measurementProtocolSecret) {
2703
+ return skippedResult("google", "No GA4 Measurement Protocol API secret resolved for this credential");
2704
+ }
2705
+ const url = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(config.measurementId)}&api_secret=${encodeURIComponent(secrets.measurementProtocolSecret)}`;
2706
+ const res = await fetcher(url, {
2707
+ method: "POST",
2708
+ headers: { "Content-Type": "application/json" },
2709
+ body: JSON.stringify(ga4Payload)
2710
+ });
2711
+ return {
2712
+ provider: "google",
2713
+ success: res.ok,
2714
+ status: res.status,
2715
+ data: { status: res.status }
2716
+ };
2717
+ } catch (err) {
2718
+ return {
2719
+ provider: "google",
2720
+ success: false,
2721
+ error: err instanceof Error ? err.message : String(err)
2722
+ };
2723
+ }
2724
+ }
2725
+ async function sendCustomWebhookEvent(event, _config, secrets, options) {
2726
+ const fetcher = options?.fetchFn || globalThis.fetch;
2727
+ const webhookPayload = {
2728
+ event: event.eventName,
2729
+ eventId: event.eventId,
2730
+ timestamp: event.eventTime ? event.eventTime * 1e3 : Date.now(),
2731
+ userData: event.userData,
2732
+ params: event.params,
2733
+ customData: event.customData,
2734
+ sourceUrl: event.eventSourceUrl || options?.sourceUrl
2735
+ };
2736
+ if (options?.simulateInDebug) {
2737
+ options.onLog?.("[Custom Webhook SIMULATED]", webhookPayload);
2738
+ return { provider: "custom", success: true, data: { simulated: true, payload: webhookPayload } };
2739
+ }
2740
+ if (!secrets?.endpointUrl) {
2741
+ return skippedResult("custom", "No custom webhook destination resolved for this credential");
2742
+ }
2743
+ try {
2744
+ const res = await fetcher(secrets.endpointUrl, {
2745
+ method: "POST",
2746
+ headers: {
2747
+ "Content-Type": "application/json",
2748
+ ...secrets.headers || {}
2749
+ },
2750
+ body: JSON.stringify(webhookPayload)
2751
+ });
2752
+ const resJson = await res.json().catch(() => ({}));
2753
+ return {
2754
+ provider: "custom",
2755
+ success: res.ok,
2756
+ status: res.status,
2757
+ data: resJson
2758
+ };
2759
+ } catch (err) {
2760
+ return {
2761
+ provider: "custom",
2762
+ success: false,
2763
+ error: err instanceof Error ? err.message : String(err)
2764
+ };
2765
+ }
2766
+ }
2767
+ function skippedResult(provider, reason) {
2768
+ return { provider, success: true, skipped: true, reason };
2769
+ }
2770
+ async function dispatchServerTracking(event, config, options) {
2771
+ const eventId = event.eventId || generateTrackingEventId();
2772
+ const eventWithId = { ...event, eventId };
2773
+ if (!config || config.enabled === false) {
2774
+ return {
2775
+ success: true,
2776
+ eventId,
2777
+ skipped: true,
2778
+ reason: "Tracking is disabled globally",
2779
+ results: {}
2780
+ };
2781
+ }
2782
+ const isDebug = Boolean(config.debugMode);
2783
+ const simulate = isDebug && options?.simulateInDebug !== false;
2784
+ const mergedOptions = {
2785
+ ...options,
2786
+ simulateInDebug: simulate,
2787
+ onLog: (msg, data) => {
2788
+ if (isDebug) {
2789
+ console.log(`[KUBUILD Tracking] ${msg}`, data || "");
2790
+ }
2791
+ options?.onLog?.(msg, data);
2792
+ }
2793
+ };
2794
+ const target = options?.provider || "all";
2795
+ const wants = (key) => target === "all" || target === key;
2796
+ const providers = config.providers || {};
2797
+ const tasks = [];
2798
+ const meta = providers.meta;
2799
+ if (wants("meta") && meta && meta.enabled !== false && meta.capiEnabled) {
2800
+ tasks.push({
2801
+ key: "meta",
2802
+ credentialId: meta.credentialId,
2803
+ run: (s) => sendMetaCapiEvent(eventWithId, meta, s, mergedOptions)
2804
+ });
2805
+ }
2806
+ const tiktok = providers.tiktok;
2807
+ if (wants("tiktok") && tiktok && tiktok.enabled !== false && tiktok.eventsApiEnabled) {
2808
+ tasks.push({
2809
+ key: "tiktok",
2810
+ credentialId: tiktok.credentialId,
2811
+ run: (s) => sendTikTokEventsApi(eventWithId, tiktok, s, mergedOptions)
2812
+ });
2813
+ }
2814
+ const google = providers.google;
2815
+ if (wants("google") && google && google.enabled !== false && google.measurementId) {
2816
+ tasks.push({
2817
+ key: "google",
2818
+ credentialId: google.credentialId,
2819
+ run: (s) => sendGa4MeasurementEvent(eventWithId, google, s, mergedOptions)
2820
+ });
2821
+ }
2822
+ const custom = providers.custom;
2823
+ if (wants("custom") && custom && custom.enabled !== false) {
2824
+ tasks.push({
2825
+ key: "custom",
2826
+ credentialId: custom.credentialId,
2827
+ run: (s) => sendCustomWebhookEvent(eventWithId, custom, s, mergedOptions)
2828
+ });
2829
+ }
2830
+ if (tasks.length === 0) {
2831
+ return {
2832
+ success: true,
2833
+ eventId,
2834
+ skipped: true,
2835
+ reason: target === "gtm" ? "GTM is client-side only (dataLayer); no server delivery" : "No server-side tracking provider is enabled",
2836
+ results: {}
2837
+ };
2838
+ }
2839
+ const resolve = options?.resolveSecrets;
2840
+ const runTask = async (task) => {
2841
+ let secrets = null;
2842
+ if (resolve) {
2843
+ try {
2844
+ secrets = await resolve({ provider: task.key, credentialId: task.credentialId, documentId: options?.documentId }) ?? null;
2845
+ } catch (err) {
2846
+ return {
2847
+ provider: task.key,
2848
+ success: false,
2849
+ error: `Secret resolution failed: ${err instanceof Error ? err.message : String(err)}`
2850
+ };
2851
+ }
2852
+ }
2853
+ if (!secrets && !simulate) {
2854
+ const reason = resolve ? `No secret resolved for provider "${task.key}"${task.credentialId ? ` (credentialId "${task.credentialId}")` : " (no credentialId linked)"}` : "No secret resolver configured (options.resolveSecrets)";
2855
+ mergedOptions.onLog?.(`[${task.key}] skipped: ${reason}`);
2856
+ return skippedResult(task.key, reason);
2857
+ }
2858
+ return task.run(secrets);
2859
+ };
2860
+ const settled = await Promise.allSettled(tasks.map((task) => runTask(task)));
2861
+ const results = {};
2862
+ let overallSuccess = true;
2863
+ let delivered = 0;
2864
+ settled.forEach((item, index) => {
2865
+ const key = tasks[index].key;
2866
+ if (item.status === "fulfilled") {
2867
+ results[key] = item.value;
2868
+ if (!item.value.success) overallSuccess = false;
2869
+ if (!item.value.skipped) delivered++;
2870
+ } else {
2871
+ overallSuccess = false;
2872
+ results[key] = {
2873
+ provider: key,
2874
+ success: false,
2875
+ error: item.reason instanceof Error ? item.reason.message : String(item.reason)
2876
+ };
2877
+ }
2878
+ });
2879
+ const allSkipped = delivered === 0 && overallSuccess;
2880
+ return {
2881
+ success: overallSuccess,
2882
+ eventId,
2883
+ ...allSkipped ? { skipped: true, reason: "All server providers were skipped" } : {},
2884
+ results
2885
+ };
2886
+ }
2887
+
2888
+ // src/runtime/tracking-relay.ts
2889
+ import {
2890
+ TRACKING_RELAY_PROTOCOL_VERSION,
2891
+ TrackingRelayRequestSchema
2892
+ } from "@kubuild/schema";
2893
+ var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
2894
+ function defaultClientIp(request) {
2895
+ const forwarded = request.headers.get("x-forwarded-for");
2896
+ if (forwarded) {
2897
+ const first = forwarded.split(",")[0]?.trim();
2898
+ if (first) return first;
2899
+ }
2900
+ return request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
2901
+ }
2902
+ function isOriginAllowed(origin, allowed) {
2903
+ if (!allowed) return true;
2904
+ if (!origin) return false;
2905
+ if (typeof allowed === "function") return allowed(origin);
2906
+ return allowed.includes(origin);
2907
+ }
2908
+ function createTrackingRelayHandler(options) {
2909
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
2910
+ return async (request) => {
2911
+ const origin = request.headers.get("origin");
2912
+ const corsHeaders = {};
2913
+ if (options.allowedOrigins && origin && isOriginAllowed(origin, options.allowedOrigins)) {
2914
+ corsHeaders["Access-Control-Allow-Origin"] = origin;
2915
+ corsHeaders["Vary"] = "Origin";
2916
+ }
2917
+ const json = (body, status, extra) => new Response(JSON.stringify(body), {
2918
+ status,
2919
+ headers: { "Content-Type": "application/json", ...corsHeaders, ...extra || {} }
2920
+ });
2921
+ const fail = (code, message, status, extra) => json({ version: TRACKING_RELAY_PROTOCOL_VERSION, success: false, error: { code, message } }, status, extra);
2922
+ if (!isOriginAllowed(origin, options.allowedOrigins)) {
2923
+ return fail("FORBIDDEN_ORIGIN", "Origin is not allowed", 403);
2924
+ }
2925
+ if (request.method === "OPTIONS") {
2926
+ return new Response(null, {
2927
+ status: 204,
2928
+ headers: {
2929
+ ...corsHeaders,
2930
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
2931
+ "Access-Control-Allow-Headers": "Content-Type",
2932
+ "Access-Control-Max-Age": "600"
2933
+ }
2934
+ });
2935
+ }
2936
+ if (request.method !== "POST") {
2937
+ return fail("METHOD_NOT_ALLOWED", "Only POST requests are accepted", 405, { Allow: "POST, OPTIONS" });
2938
+ }
2939
+ try {
2940
+ const text = await request.text();
2941
+ if (text.length > maxBodyBytes) {
2942
+ return fail("INVALID_REQUEST", `Request body exceeds ${maxBodyBytes} bytes`, 413);
2943
+ }
2944
+ let raw;
2945
+ try {
2946
+ raw = JSON.parse(text);
2947
+ } catch {
2948
+ return fail("INVALID_REQUEST", "Request body must be valid JSON", 400);
2949
+ }
2950
+ if (raw && typeof raw === "object" && "version" in raw && raw.version !== TRACKING_RELAY_PROTOCOL_VERSION) {
2951
+ return fail(
2952
+ "UNSUPPORTED_VERSION",
2953
+ `Unsupported relay protocol version; expected ${TRACKING_RELAY_PROTOCOL_VERSION}`,
2954
+ 400
2955
+ );
2956
+ }
2957
+ const parsed = TrackingRelayRequestSchema.safeParse(raw);
2958
+ if (!parsed.success) {
2959
+ const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
2960
+ return fail("INVALID_REQUEST", `Invalid relay request: ${issues}`, 400);
2961
+ }
2962
+ const body = parsed.data;
2963
+ const config = await options.getConfig({
2964
+ request,
2965
+ documentId: body.documentId,
2966
+ credentialId: body.credentialId,
2967
+ provider: body.provider
2968
+ });
2969
+ if (!config) {
2970
+ return fail("CONFIG_NOT_FOUND", "No tracking configuration found for this page", 404);
2971
+ }
2972
+ const clientIp = (options.getClientIp ?? defaultClientIp)(request);
2973
+ const clientUserAgent = request.headers.get("user-agent") || void 0;
2974
+ const userData = { ...body.event.userData || {} };
2975
+ delete userData.clientIp;
2976
+ delete userData.clientUserAgent;
2977
+ const event = { ...body.event, userData };
2978
+ const outcome = await dispatchServerTracking(event, config, {
2979
+ resolveSecrets: options.resolveSecrets,
2980
+ documentId: body.documentId,
2981
+ provider: body.provider,
2982
+ fetchFn: options.fetchFn,
2983
+ clientIp,
2984
+ clientUserAgent,
2985
+ onLog: options.onLog
2986
+ });
2987
+ const results = {};
2988
+ for (const [key, r] of Object.entries(outcome.results)) {
2989
+ results[key] = {
2990
+ provider: r.provider,
2991
+ success: r.success,
2992
+ ...r.status !== void 0 ? { status: r.status } : {},
2993
+ ...r.skipped ? { skipped: true } : {},
2994
+ ...r.reason ? { reason: r.reason } : {},
2995
+ ...r.error ? { error: r.error } : {}
2996
+ };
2997
+ }
2998
+ return json(
2999
+ {
3000
+ version: TRACKING_RELAY_PROTOCOL_VERSION,
3001
+ success: outcome.success,
3002
+ eventId: outcome.eventId,
3003
+ ...outcome.skipped ? { skipped: true } : {},
3004
+ ...outcome.reason ? { reason: outcome.reason } : {},
3005
+ results
3006
+ },
3007
+ 200
3008
+ );
3009
+ } catch (err) {
3010
+ options.onLog?.("[Tracking relay] internal error", err);
3011
+ return fail("INTERNAL_ERROR", "Tracking relay failed", 500);
3012
+ }
3013
+ };
3014
+ }
3015
+
2457
3016
  // src/io/exporter.ts
2458
3017
  import { zipSync, strToU8 } from "fflate";
2459
3018
  import {
2460
3019
  SCHEMA_NAME as SCHEMA_NAME4,
2461
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2
3020
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3
2462
3021
  } from "@kubuild/schema";
2463
3022
 
2464
3023
  // src/validation/validator.ts
@@ -3001,7 +3560,8 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3001
3560
  `${currentPath}/props`,
3002
3561
  effectiveNodeId,
3003
3562
  options,
3004
- errors
3563
+ errors,
3564
+ warnings
3005
3565
  );
3006
3566
  }
3007
3567
  }
@@ -3016,7 +3576,10 @@ function validateNodeRecursive(nodeObj, currentPath, parentNode, seenIds, visite
3016
3576
  }
3017
3577
  }
3018
3578
  }
3019
- function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3579
+ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors, warnings) {
3580
+ const checkAssets = options.checkAssetReferences !== false;
3581
+ const checkVariables = options.checkVariableBindings !== false;
3582
+ const checkActions = options.checkActionBindings !== false;
3020
3583
  for (const [key, value] of Object.entries(propsObj)) {
3021
3584
  const currentPath = `${propsPath}/${key}`;
3022
3585
  if (!value || typeof value !== "object") {
@@ -3030,7 +3593,8 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3030
3593
  `${currentPath}/${index}`,
3031
3594
  nodeId,
3032
3595
  options,
3033
- errors
3596
+ errors,
3597
+ warnings
3034
3598
  );
3035
3599
  }
3036
3600
  });
@@ -3038,7 +3602,9 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3038
3602
  }
3039
3603
  const record = value;
3040
3604
  if (record.type === "asset") {
3041
- if (typeof record.assetId !== "string" || record.assetId.trim().length === 0) {
3605
+ if (!checkAssets) continue;
3606
+ const assetId = typeof record.assetId === "string" && record.assetId.trim().length > 0 ? record.assetId : void 0;
3607
+ if (assetId === void 0) {
3042
3608
  errors.push({
3043
3609
  code: "INVALID_ASSET_REFERENCE",
3044
3610
  message: 'Asset reference must have a non-empty "assetId"',
@@ -3054,7 +3620,27 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3054
3620
  nodeId
3055
3621
  });
3056
3622
  }
3623
+ if (assetId !== void 0 && options.knownAssetIds && !isKnownId(options.knownAssetIds, assetId)) {
3624
+ if (typeof record.fallbackUrl === "string" && record.fallbackUrl.length > 0) {
3625
+ warnings.push({
3626
+ code: "UNRESOLVED_ASSET_REFERENCE",
3627
+ message: `Asset "${assetId}" is not among the known assets; its fallbackUrl will be used`,
3628
+ path: `${currentPath}/assetId`,
3629
+ nodeId,
3630
+ details: { assetId }
3631
+ });
3632
+ } else {
3633
+ errors.push({
3634
+ code: "INVALID_ASSET_REFERENCE",
3635
+ message: `Asset "${assetId}" is not among the known assets and has no fallbackUrl`,
3636
+ path: `${currentPath}/assetId`,
3637
+ nodeId,
3638
+ details: { assetId }
3639
+ });
3640
+ }
3641
+ }
3057
3642
  } else if (record.type === "variable") {
3643
+ if (!checkVariables) continue;
3058
3644
  if (typeof record.key !== "string" || record.key.trim().length === 0) {
3059
3645
  errors.push({
3060
3646
  code: "INVALID_VARIABLE_BINDING",
@@ -3064,6 +3650,7 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3064
3650
  });
3065
3651
  }
3066
3652
  } else if (key === "action" || typeof record.type === "string" && record.payload !== void 0) {
3653
+ if (!checkActions) continue;
3067
3654
  if (typeof record.type !== "string" || record.type.trim().length === 0) {
3068
3655
  errors.push({
3069
3656
  code: "INVALID_ACTION_BINDING",
@@ -3073,10 +3660,62 @@ function validatePropsBindings(propsObj, propsPath, nodeId, options, errors) {
3073
3660
  });
3074
3661
  }
3075
3662
  } else {
3076
- validatePropsBindings(record, currentPath, nodeId, options, errors);
3663
+ validatePropsBindings(record, currentPath, nodeId, options, errors, warnings);
3077
3664
  }
3078
3665
  }
3079
3666
  }
3667
+ function isKnownId(ids, id) {
3668
+ return Array.isArray(ids) ? ids.includes(id) : ids.has(id);
3669
+ }
3670
+
3671
+ // src/io/tracking-sanitizer.ts
3672
+ import { LEGACY_TRACKING_SECRET_KEYS } from "@kubuild/schema";
3673
+ function stripTrackingSecretsInPlace(tracking, basePath = "tracking") {
3674
+ const removed = [];
3675
+ if (!tracking || typeof tracking !== "object" || Array.isArray(tracking)) return removed;
3676
+ const providers = tracking.providers;
3677
+ if (!providers || typeof providers !== "object" || Array.isArray(providers)) return removed;
3678
+ for (const provider of Object.keys(LEGACY_TRACKING_SECRET_KEYS)) {
3679
+ const cfg = providers[provider];
3680
+ if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) continue;
3681
+ for (const key of LEGACY_TRACKING_SECRET_KEYS[provider]) {
3682
+ if (Object.prototype.hasOwnProperty.call(cfg, key)) {
3683
+ delete cfg[key];
3684
+ removed.push(`${basePath}.providers.${provider}.${key}`);
3685
+ }
3686
+ }
3687
+ }
3688
+ return removed;
3689
+ }
3690
+ function stripDocumentTrackingSecretsInPlace(doc) {
3691
+ const removed = [];
3692
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return removed;
3693
+ const record = doc;
3694
+ removed.push(...stripTrackingSecretsInPlace(record.tracking, "tracking"));
3695
+ const metadata = record.metadata;
3696
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
3697
+ removed.push(
3698
+ ...stripTrackingSecretsInPlace(metadata.tracking, "metadata.tracking")
3699
+ );
3700
+ }
3701
+ if (Array.isArray(record.artboards)) {
3702
+ record.artboards.forEach((artboard, index) => {
3703
+ if (artboard && typeof artboard === "object") {
3704
+ const inner = artboard.document;
3705
+ for (const path of stripDocumentTrackingSecretsInPlace(inner)) {
3706
+ removed.push(`artboards.${index}.document.${path}`);
3707
+ }
3708
+ }
3709
+ });
3710
+ }
3711
+ return removed;
3712
+ }
3713
+ function sanitizeDocumentTracking(doc) {
3714
+ if (!doc || typeof doc !== "object") return { document: doc, removed: [] };
3715
+ const copy = JSON.parse(JSON.stringify(doc));
3716
+ const removed = stripDocumentTrackingSecretsInPlace(copy);
3717
+ return { document: copy, removed };
3718
+ }
3080
3719
 
3081
3720
  // src/io/exporter.ts
3082
3721
  function sha256Sync(data) {
@@ -3297,6 +3936,7 @@ async function exportPackage(document, options = {}) {
3297
3936
  };
3298
3937
  }
3299
3938
  const pageDoc = JSON.parse(JSON.stringify(validation.data));
3939
+ stripDocumentTrackingSecretsInPlace(pageDoc);
3300
3940
  if (options.metadata) {
3301
3941
  pageDoc.metadata = {
3302
3942
  ...pageDoc.metadata || {
@@ -3421,7 +4061,7 @@ async function exportPackage(document, options = {}) {
3421
4061
  };
3422
4062
  const manifest = {
3423
4063
  schema: SCHEMA_NAME4,
3424
- schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION2,
4064
+ schemaVersion: pageDoc.version || CURRENT_SCHEMA_VERSION3,
3425
4065
  packageVersion: options.packageVersion || "1.0.0",
3426
4066
  builderCompatibility: options.builderCompatibility || ">=0.1.0",
3427
4067
  requiredComponents: requirements.requiredComponents,
@@ -3448,12 +4088,12 @@ var exportStoraPackage = exportPackage;
3448
4088
  import { unzipSync, strFromU8 } from "fflate";
3449
4089
  import {
3450
4090
  ManifestSchema,
3451
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4
4091
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION5
3452
4092
  } from "@kubuild/schema";
3453
4093
 
3454
4094
  // src/io/migration.ts
3455
4095
  import {
3456
- CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3,
4096
+ CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION4,
3457
4097
  SCHEMA_NAME as SCHEMA_NAME5
3458
4098
  } from "@kubuild/schema";
3459
4099
  var MigrationRegistry = class {
@@ -3608,17 +4248,36 @@ defaultMigrationRegistry.register({
3608
4248
  return migrated;
3609
4249
  }
3610
4250
  });
3611
- function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION3, registry = defaultMigrationRegistry) {
4251
+ defaultMigrationRegistry.register({
4252
+ fromVersion: "1.0.0",
4253
+ toVersion: "1.1.0",
4254
+ description: "Remove tracking secrets and server destinations (capiAccessToken, accessToken, measurementProtocolSecret, serverRelayUrl, custom endpointUrl/headers) from the document",
4255
+ migrate: (rawDoc, context) => {
4256
+ const migrated = deepClone(rawDoc);
4257
+ migrated.version = "1.1.0";
4258
+ const removed = stripDocumentTrackingSecretsInPlace(migrated);
4259
+ if (removed.length > 0) {
4260
+ context.warn({
4261
+ code: "TRACKING_SECRET_REMOVED",
4262
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are now stored by the host and resolved server-side).",
4263
+ step: "1.0.0->1.1.0",
4264
+ paths: removed
4265
+ });
4266
+ }
4267
+ return migrated;
4268
+ }
4269
+ });
4270
+ function canMigrate(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
3612
4271
  if (!sourceVersion || !targetVersion) return false;
3613
4272
  if (sourceVersion === targetVersion) return true;
3614
4273
  return registry.hasPath(sourceVersion, targetVersion);
3615
4274
  }
3616
- function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION3, registry = defaultMigrationRegistry) {
4275
+ function getMigrationPath(sourceVersion, targetVersion = CURRENT_SCHEMA_VERSION4, registry = defaultMigrationRegistry) {
3617
4276
  if (!sourceVersion || !targetVersion) return null;
3618
4277
  return registry.findPath(sourceVersion, targetVersion);
3619
4278
  }
3620
4279
  function migrateDocument(rawDocument, options = {}) {
3621
- const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION3;
4280
+ const targetVersion = options.targetVersion ?? CURRENT_SCHEMA_VERSION4;
3622
4281
  const dryRun = options.dryRun ?? false;
3623
4282
  const registry = options.registry ?? defaultMigrationRegistry;
3624
4283
  const validate = options.validate ?? true;
@@ -3646,6 +4305,14 @@ function migrateDocument(rawDocument, options = {}) {
3646
4305
  const sourceVersion = typeof doc.version === "string" ? doc.version.trim() : "unknown";
3647
4306
  if (sourceVersion === targetVersion) {
3648
4307
  const cloned = deepClone(doc);
4308
+ const removedSecrets = stripDocumentTrackingSecretsInPlace(cloned);
4309
+ const currentWarnings = removedSecrets.length > 0 ? [
4310
+ {
4311
+ code: "TRACKING_SECRET_REMOVED",
4312
+ message: "Tracking secret removed; re-link credential via credentialId (secrets are stored by the host and resolved server-side).",
4313
+ paths: removedSecrets
4314
+ }
4315
+ ] : [];
3649
4316
  if (validate) {
3650
4317
  const validation = validateDocument(cloned);
3651
4318
  if (!validation.valid) {
@@ -3677,7 +4344,8 @@ function migrateDocument(rawDocument, options = {}) {
3677
4344
  targetVersion,
3678
4345
  migrationPath: [sourceVersion],
3679
4346
  stepsApplied: 0,
3680
- dryRun
4347
+ dryRun,
4348
+ ...currentWarnings.length > 0 ? { warnings: currentWarnings } : {}
3681
4349
  }
3682
4350
  };
3683
4351
  }
@@ -3717,6 +4385,8 @@ function migrateDocument(rawDocument, options = {}) {
3717
4385
  }
3718
4386
  let currentDoc = deepClone(doc);
3719
4387
  let stepsApplied = 0;
4388
+ const warnings = [];
4389
+ const stepContext = { warn: (w) => warnings.push(w) };
3720
4390
  for (let i = 0; i < path.length - 1; i++) {
3721
4391
  const fromVer = path[i];
3722
4392
  const toVer = path[i + 1];
@@ -3742,7 +4412,7 @@ function migrateDocument(rawDocument, options = {}) {
3742
4412
  };
3743
4413
  }
3744
4414
  try {
3745
- currentDoc = step.migrate(currentDoc);
4415
+ currentDoc = step.migrate(currentDoc, stepContext);
3746
4416
  stepsApplied++;
3747
4417
  } catch (err) {
3748
4418
  const error = {
@@ -3798,7 +4468,8 @@ function migrateDocument(rawDocument, options = {}) {
3798
4468
  targetVersion,
3799
4469
  migrationPath: path,
3800
4470
  stepsApplied,
3801
- dryRun: false
4471
+ dryRun: false,
4472
+ ...warnings.length > 0 ? { warnings } : {}
3802
4473
  }
3803
4474
  };
3804
4475
  }
@@ -3833,7 +4504,7 @@ async function preflightPackage(archiveData, options = {}) {
3833
4504
  ...DEFAULT_SECURITY_LIMITS,
3834
4505
  ...options.securityLimits
3835
4506
  };
3836
- const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION4;
4507
+ const targetVersion = options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5;
3837
4508
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
3838
4509
  const diagnostics = [];
3839
4510
  let dependencyPolicy = options.dependencyPolicy || "cancel";
@@ -3996,6 +4667,16 @@ async function preflightPackage(archiveData, options = {}) {
3996
4667
  let rawPageDoc;
3997
4668
  try {
3998
4669
  rawPageDoc = JSON.parse(strFromU8(pageEntry));
4670
+ const secretScan = sanitizeDocumentTracking(rawPageDoc);
4671
+ if (secretScan.removed.length > 0) {
4672
+ diagnostics.push({
4673
+ code: "TRACKING_SECRET_REMOVED",
4674
+ severity: "warning",
4675
+ message: "Tracking secrets found in page.json will be removed on import; re-link credentials via credentialId.",
4676
+ path: "page.json",
4677
+ details: { paths: secretScan.removed }
4678
+ });
4679
+ }
3999
4680
  const pageProtoCheck = containsProhibitedKeys(rawPageDoc);
4000
4681
  if (pageProtoCheck.found) {
4001
4682
  diagnostics.push({
@@ -4242,7 +4923,7 @@ function buildReport(valid, canImport, fields) {
4242
4923
  return {
4243
4924
  valid,
4244
4925
  canImport,
4245
- targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION4,
4926
+ targetVersion: fields.targetVersion || CURRENT_SCHEMA_VERSION5,
4246
4927
  requiresMigration: fields.requiresMigration || false,
4247
4928
  missingComponents: fields.missingComponents || [],
4248
4929
  missingCapabilities: fields.missingCapabilities || [],
@@ -4315,7 +4996,7 @@ async function importPackage(archiveData, options = {}) {
4315
4996
  if (preflight.requiresMigration) {
4316
4997
  const migrationRegistry = options.migrationRegistry || defaultMigrationRegistry;
4317
4998
  const migrationRes = migrateDocument(pageRaw, {
4318
- targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION4,
4999
+ targetVersion: options.targetSchemaVersion || CURRENT_SCHEMA_VERSION5,
4319
5000
  registry: migrationRegistry,
4320
5001
  validate: true
4321
5002
  });
@@ -4337,13 +5018,24 @@ async function importPackage(archiveData, options = {}) {
4337
5018
  } else {
4338
5019
  finalDocument = pageRaw;
4339
5020
  }
5021
+ const importWarnings = [];
5022
+ const strippedSecrets = stripDocumentTrackingSecretsInPlace(finalDocument);
5023
+ if (strippedSecrets.length > 0) {
5024
+ importWarnings.push({
5025
+ code: "TRACKING_SECRET_REMOVED",
5026
+ severity: "warning",
5027
+ message: "Tracking secret removed from imported document; re-link credential via credentialId.",
5028
+ path: "page.json",
5029
+ details: { paths: strippedSecrets }
5030
+ });
5031
+ }
4340
5032
  let metadata = finalDocument.metadata || {
4341
5033
  title: "Imported Page",
4342
5034
  description: "",
4343
5035
  author: "",
4344
5036
  tags: [],
4345
5037
  category: "general",
4346
- version: finalDocument.version || CURRENT_SCHEMA_VERSION4
5038
+ version: finalDocument.version || CURRENT_SCHEMA_VERSION5
4347
5039
  };
4348
5040
  if (unzipped["metadata.json"]) {
4349
5041
  try {
@@ -4495,7 +5187,8 @@ async function importPackage(archiveData, options = {}) {
4495
5187
  metadata,
4496
5188
  extractedAssets,
4497
5189
  renamedAssets: Object.keys(renameMap).length > 0 ? renameMap : void 0,
4498
- preflight
5190
+ preflight,
5191
+ ...importWarnings.length > 0 ? { warnings: importWarnings } : {}
4499
5192
  };
4500
5193
  }
4501
5194
  var importStoraPackage = importPackage;
@@ -5638,10 +6331,12 @@ export {
5638
6331
  createPageArtboard,
5639
6332
  createRuntimeStore,
5640
6333
  createTemplateRecord,
6334
+ createTrackingRelayHandler,
5641
6335
  deepClone,
5642
6336
  defaultIdGenerator,
5643
6337
  defaultMigrationRegistry,
5644
6338
  defaultRenameAssetStrategy,
6339
+ dispatchServerTracking,
5645
6340
  duplicateNode,
5646
6341
  evaluateActionCondition,
5647
6342
  evaluateCondition,
@@ -5661,6 +6356,7 @@ export {
5661
6356
  findMissingComponentNodes,
5662
6357
  findNodeById,
5663
6358
  findNodeLocation,
6359
+ generateTrackingEventId,
5664
6360
  getActiveArtboard,
5665
6361
  getAncestorChain,
5666
6362
  getComponentArtboards,
@@ -5670,6 +6366,7 @@ export {
5670
6366
  getPageArtboards,
5671
6367
  getParentNodeId,
5672
6368
  hasTemplateExpressions,
6369
+ hashSha256,
5673
6370
  importPackage,
5674
6371
  importStoraPackage,
5675
6372
  insertNode,
@@ -5687,6 +6384,9 @@ export {
5687
6384
  loadProjectDocument,
5688
6385
  migrateDocument,
5689
6386
  moveNode,
6387
+ normalizeEmail,
6388
+ normalizePhone,
6389
+ normalizeText,
5690
6390
  preflightPackage,
5691
6391
  previewImportPackage,
5692
6392
  remapAssetReferences,
@@ -5698,12 +6398,19 @@ export {
5698
6398
  resolveBinding,
5699
6399
  resolveBindingValue,
5700
6400
  resolvePropertyPath,
6401
+ sanitizeDocumentTracking,
5701
6402
  sanitizeFilename,
5702
6403
  sanitizeHtml,
5703
6404
  sanitizeUrl,
5704
6405
  saveDraftAsTemplate,
6406
+ sendCustomWebhookEvent,
6407
+ sendGa4MeasurementEvent,
6408
+ sendMetaCapiEvent,
6409
+ sendTikTokEventsApi,
5705
6410
  setActiveArtboard,
5706
6411
  sha256Sync,
6412
+ stripDocumentTrackingSecretsInPlace,
6413
+ stripTrackingSecretsInPlace,
5707
6414
  ungroupNodeFrame,
5708
6415
  updateActions,
5709
6416
  updateAnimation,