@apps-in-toss/framework 1.9.3 → 1.10.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
@@ -29,7 +29,9 @@ import { useEffect as useEffect2 } from "react";
29
29
 
30
30
  // src/env.ts
31
31
  var env = {
32
- getDeploymentId: () => __DEV__ ? "local" : global.__appsInToss?.deploymentId
32
+ getDeploymentId: () => __DEV__ ? "local" : global.__appsInToss?.deploymentId,
33
+ getWebViewType: () => global.__appsInToss.webViewType,
34
+ getAppName: () => global.__granite.app.name
33
35
  };
34
36
 
35
37
  // src/hooks/useCaptureExitLog.ts
@@ -1419,12 +1421,13 @@ import {
1419
1421
  import * as appsInTossAsyncBridges from "@apps-in-toss/native-modules/async-bridges";
1420
1422
  import * as appsInTossConstantBridges from "@apps-in-toss/native-modules/constant-bridges";
1421
1423
  import * as appsInTossEventBridges from "@apps-in-toss/native-modules/event-bridges";
1424
+ import { afterDocumentLoad, beforeDocumentLoad } from "@apps-in-toss/user-scripts";
1422
1425
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "@granite-js/native/react-native-safe-area-context";
1423
1426
  import { getSchemeUri as getSchemeUri8 } from "@granite-js/react-native";
1424
1427
  import { ExternalWebViewScreen, tdsEvent } from "@toss/tds-react-native";
1425
1428
  import { useSafeAreaBottom, useSafeAreaTop as useSafeAreaTop3 } from "@toss/tds-react-native/private";
1426
1429
  import { useEffect as useEffect13, useMemo as useMemo7, useRef as useRef6, useState as useState6 } from "react";
1427
- import { BackHandler as BackHandler2, Linking, Platform as Platform6 } from "react-native";
1430
+ import { BackHandler as BackHandler2, Linking, NativeModules as NativeModules3, Platform as Platform6 } from "react-native";
1428
1431
 
1429
1432
  // src/components/GameWebView.tsx
1430
1433
  import { setIosSwipeGestureEnabled as setIosSwipeGestureEnabled3, getOperationalEnvironment as getOperationalEnvironment5 } from "@apps-in-toss/native-modules";
@@ -2671,7 +2674,9 @@ function WebView({ type, local, onMessage, ...props }) {
2671
2674
  /** TossAd */
2672
2675
  fetchTossAd_isSupported: fetchTossAd.isSupported,
2673
2676
  /** env */
2674
- getDeploymentId: env.getDeploymentId
2677
+ getDeploymentId: env.getDeploymentId,
2678
+ getWebViewType: env.getWebViewType,
2679
+ getAppName: env.getAppName
2675
2680
  },
2676
2681
  asyncHandlerMap: {
2677
2682
  ...appsInTossAsyncBridges,
@@ -2709,7 +2714,11 @@ function WebView({ type, local, onMessage, ...props }) {
2709
2714
  /** Toss Ads */
2710
2715
  tossAdEventLog,
2711
2716
  /** Private */
2712
- memoryDebugLog: webViewMemoryDebugLog
2717
+ memoryDebugLog: webViewMemoryDebugLog,
2718
+ debugLog: async (event) => {
2719
+ NativeModules3.AppsInTossModule?.eventLog(event);
2720
+ NativeModules3.TossCoreModule?.eventLog({ params: event });
2721
+ }
2713
2722
  }
2714
2723
  });
2715
2724
  const headerPropForExternalWebView = useMemo7(() => {
@@ -2741,7 +2750,6 @@ function WebView({ type, local, onMessage, ...props }) {
2741
2750
  BackHandler2.addEventListener("hardwareBackPress", callback);
2742
2751
  return () => BackHandler2.removeEventListener("hardwareBackPress", callback);
2743
2752
  }, [webBackHandler]);
2744
- const globalScripts = useGlobalScripts();
2745
2753
  const handleWebViewProcessDidTerminate = useHandleWebViewProcessDidTerminate(webViewRef);
2746
2754
  return /* @__PURE__ */ jsx19(
2747
2755
  BaseWebView,
@@ -2770,8 +2778,8 @@ function WebView({ type, local, onMessage, ...props }) {
2770
2778
  webviewDebuggingEnabled: webViewDebuggingEnabled,
2771
2779
  thirdPartyCookiesEnabled: true,
2772
2780
  onMessage: handler.onMessage,
2773
- injectedJavaScript: globalScripts.afterLoad,
2774
- injectedJavaScriptBeforeContentLoaded: mergeScripts(handler.injectedJavaScript, globalScripts.beforeLoad),
2781
+ injectedJavaScript: afterDocumentLoad,
2782
+ injectedJavaScriptBeforeContentLoaded: [handler.injectedJavaScript, beforeDocumentLoad].join("\n"),
2775
2783
  decelerationRate: Platform6.OS === "ios" ? 1 : void 0,
2776
2784
  allowsBackForwardNavigationGestures,
2777
2785
  onShouldStartLoadWithRequest: (event) => {
@@ -2802,73 +2810,1271 @@ function WebView({ type, local, onMessage, ...props }) {
2802
2810
  }
2803
2811
  );
2804
2812
  }
2805
- function useGlobalScripts() {
2806
- const global2 = getAppsInTossGlobals();
2807
- const disableTextSelectionCSS = `
2808
- const style = document.createElement('style');
2809
- style.textContent = '*:not(input):not(textarea) { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-touch-callout: none; }';
2810
- document.head.appendChild(style);
2811
- `;
2812
- const applyGameResourcesCache = `
2813
- (function () {
2814
- if (typeof caches === 'undefined') {
2815
- return;
2813
+
2814
+ // src/index.ts
2815
+ export * from "@apps-in-toss/analytics";
2816
+ import { useOverlay as useOverlay3, OverlayProvider } from "@toss/tds-react-native/private";
2817
+ export * from "@apps-in-toss/native-modules";
2818
+ export * from "@apps-in-toss/types";
2819
+
2820
+ // src/ads/inlineAd/InlineAd.tsx
2821
+ import { ImpressionArea, useVisibilityChange as useVisibilityChange2 } from "@granite-js/react-native";
2822
+ import { useEffect as useEffect14, useRef as useRef9, useState as useState7 } from "react";
2823
+ import { StyleSheet as StyleSheet3, View as View7, useColorScheme } from "react-native";
2824
+
2825
+ // src/ads/inlineAd/constants.ts
2826
+ var SDK_ID = "106";
2827
+ var LIST_BANNER_STYLE_ID = "1";
2828
+ var NATIVE_IMAGE_STYLE_ID = "2";
2829
+ var FEED_BANNER_STYLE_ID = NATIVE_IMAGE_STYLE_ID;
2830
+ var AVAILABLE_STYLE_IDS = [LIST_BANNER_STYLE_ID, NATIVE_IMAGE_STYLE_ID];
2831
+ var DEFAULT_INLINE_AD_THEME = "auto";
2832
+ var DEFAULT_INLINE_AD_TONE = "blackAndWhite";
2833
+ var DEFAULT_INLINE_AD_VARIANT = "expanded";
2834
+ var DEFAULT_INLINE_AD_PADDING_BANNER = "16px 20px";
2835
+ var DEFAULT_INLINE_AD_PADDING_NATIVE_IMAGE = "20px";
2836
+ var ERROR_CODES = {
2837
+ NETWORK_ERROR: 1001,
2838
+ INVALID_REQUEST: 1002,
2839
+ NO_AD: 1003,
2840
+ ERROR: 1004,
2841
+ INVALID_SPACE: 1005,
2842
+ SDK_NOT_INITIALIZED: 1007,
2843
+ HTTP_TIMEOUT: 1008,
2844
+ EXECUTION_FAIL: 1009,
2845
+ INTERRUPTED: 1010,
2846
+ FAIL: 1011,
2847
+ BLOCKED: 1012,
2848
+ TIMEOUT: 1013,
2849
+ LIMITED_AD: 1014,
2850
+ CONSENT_REQUIRED: 1015,
2851
+ TEST_MODE: 1016,
2852
+ INTERNAL_ERROR: 1017
2853
+ };
2854
+ var ERROR_MESSAGES = {
2855
+ [ERROR_CODES.NETWORK_ERROR]: "Network error occurred",
2856
+ [ERROR_CODES.INVALID_REQUEST]: "Invalid request parameters",
2857
+ [ERROR_CODES.NO_AD]: "No ads available",
2858
+ [ERROR_CODES.ERROR]: "Server error occurred",
2859
+ [ERROR_CODES.INVALID_SPACE]: "Invalid or disabled inventory",
2860
+ [ERROR_CODES.SDK_NOT_INITIALIZED]: "SDK is not initialized",
2861
+ [ERROR_CODES.HTTP_TIMEOUT]: "Request timed out",
2862
+ [ERROR_CODES.EXECUTION_FAIL]: "Request execution failed",
2863
+ [ERROR_CODES.INTERRUPTED]: "Request interrupted",
2864
+ [ERROR_CODES.FAIL]: "Request failed",
2865
+ [ERROR_CODES.BLOCKED]: "Ad blocked by policy",
2866
+ [ERROR_CODES.TIMEOUT]: "SSP processing timed out",
2867
+ [ERROR_CODES.LIMITED_AD]: "Ad limited by privacy",
2868
+ [ERROR_CODES.CONSENT_REQUIRED]: "Consent required to show ads",
2869
+ [ERROR_CODES.TEST_MODE]: "Test mode only",
2870
+ [ERROR_CODES.INTERNAL_ERROR]: "Unknown error occurred"
2871
+ };
2872
+ var API_RESULT_ERROR_MAP = {
2873
+ HTTP_TIMEOUT: {
2874
+ code: ERROR_CODES.HTTP_TIMEOUT,
2875
+ message: ERROR_MESSAGES[ERROR_CODES.HTTP_TIMEOUT]
2876
+ },
2877
+ NETWORK_ERROR: {
2878
+ code: ERROR_CODES.NETWORK_ERROR,
2879
+ message: ERROR_MESSAGES[ERROR_CODES.NETWORK_ERROR]
2880
+ },
2881
+ EXECUTION_FAIL: {
2882
+ code: ERROR_CODES.EXECUTION_FAIL,
2883
+ message: ERROR_MESSAGES[ERROR_CODES.EXECUTION_FAIL]
2884
+ },
2885
+ INTERRUPTED: {
2886
+ code: ERROR_CODES.INTERRUPTED,
2887
+ message: ERROR_MESSAGES[ERROR_CODES.INTERRUPTED]
2888
+ },
2889
+ INTERNAL_ERROR: {
2890
+ code: ERROR_CODES.ERROR,
2891
+ message: ERROR_MESSAGES[ERROR_CODES.ERROR]
2892
+ },
2893
+ FAIL: {
2894
+ code: ERROR_CODES.FAIL,
2895
+ message: ERROR_MESSAGES[ERROR_CODES.FAIL]
2896
+ }
2897
+ };
2898
+ var AD_STATUS_ERROR_MAP = {
2899
+ NO_AD: {
2900
+ code: ERROR_CODES.NO_AD,
2901
+ message: ERROR_MESSAGES[ERROR_CODES.NO_AD]
2902
+ },
2903
+ BLOCKED: {
2904
+ code: ERROR_CODES.BLOCKED,
2905
+ message: ERROR_MESSAGES[ERROR_CODES.BLOCKED]
2906
+ },
2907
+ ERROR: {
2908
+ code: ERROR_CODES.ERROR,
2909
+ message: ERROR_MESSAGES[ERROR_CODES.ERROR]
2910
+ },
2911
+ TIMEOUT: {
2912
+ code: ERROR_CODES.TIMEOUT,
2913
+ message: ERROR_MESSAGES[ERROR_CODES.TIMEOUT]
2914
+ },
2915
+ INVALID_SPACE: {
2916
+ code: ERROR_CODES.INVALID_SPACE,
2917
+ message: ERROR_MESSAGES[ERROR_CODES.INVALID_SPACE]
2918
+ },
2919
+ INVALID_REQUEST: {
2920
+ code: ERROR_CODES.INVALID_REQUEST,
2921
+ message: ERROR_MESSAGES[ERROR_CODES.INVALID_REQUEST]
2922
+ },
2923
+ LIMITED_AD: {
2924
+ code: ERROR_CODES.LIMITED_AD,
2925
+ message: ERROR_MESSAGES[ERROR_CODES.LIMITED_AD]
2926
+ },
2927
+ CONSENT_REQUIRED: {
2928
+ code: ERROR_CODES.CONSENT_REQUIRED,
2929
+ message: ERROR_MESSAGES[ERROR_CODES.CONSENT_REQUIRED]
2930
+ },
2931
+ TEST_MODE: {
2932
+ code: ERROR_CODES.TEST_MODE,
2933
+ message: ERROR_MESSAGES[ERROR_CODES.TEST_MODE]
2934
+ }
2935
+ };
2936
+
2937
+ // src/ads/inlineAd/loadAd.ts
2938
+ function createError(code, message) {
2939
+ return {
2940
+ code,
2941
+ message: message || ERROR_MESSAGES[code] || "Unknown error",
2942
+ domain: "@apps-in-toss/framework"
2943
+ };
2944
+ }
2945
+ function isAdError(error) {
2946
+ return Boolean(
2947
+ error && typeof error === "object" && "code" in error && typeof error.code === "number" && "message" in error && typeof error.message === "string"
2948
+ );
2949
+ }
2950
+ function fetchTossAdPromise(options) {
2951
+ return new Promise((resolve, reject) => {
2952
+ if (!fetchTossAd.isSupported()) {
2953
+ reject(new Error("fetchTossAd is not supported in this environment."));
2954
+ return;
2955
+ }
2956
+ fetchTossAd({
2957
+ options,
2958
+ onEvent: resolve,
2959
+ onError: reject
2960
+ });
2961
+ });
2962
+ }
2963
+ function isApiResponse(payload) {
2964
+ return Boolean(payload && typeof payload === "object" && "resultType" in payload);
2965
+ }
2966
+ function isAdResponse(payload) {
2967
+ return Boolean(payload && typeof payload === "object" && "ads" in payload);
2968
+ }
2969
+ function normalizeAdResponse(adResponse) {
2970
+ const ads = Array.isArray(adResponse.ads) ? adResponse.ads.map((ad) => ({
2971
+ ...ad,
2972
+ requestId: ad.requestId ?? adResponse.requestId
2973
+ })).filter((ad) => AVAILABLE_STYLE_IDS.includes(String(ad.styleId))) : [];
2974
+ return {
2975
+ requestId: adResponse.requestId ?? "",
2976
+ status: adResponse.status ?? "OK",
2977
+ ads,
2978
+ ext: adResponse.ext
2979
+ };
2980
+ }
2981
+ function normalizeApiResponse(raw) {
2982
+ if (isApiResponse(raw)) {
2983
+ if (raw.resultType !== "SUCCESS") {
2984
+ return raw;
2985
+ }
2986
+ if (!raw.success) {
2987
+ return {
2988
+ resultType: "FAIL",
2989
+ error: { reason: "fetchTossAd returned SUCCESS without payload" }
2990
+ };
2991
+ }
2992
+ return { ...raw, success: normalizeAdResponse(raw.success) };
2993
+ }
2994
+ if (isAdResponse(raw)) {
2995
+ return {
2996
+ resultType: "SUCCESS",
2997
+ success: normalizeAdResponse(raw)
2998
+ };
2999
+ }
3000
+ return {
3001
+ resultType: "FAIL",
3002
+ error: { reason: "Invalid response from fetchTossAd" }
3003
+ };
3004
+ }
3005
+ function isStructurallyValidAd(ad) {
3006
+ return Boolean(ad && ad.creative && typeof ad.creative === "object");
3007
+ }
3008
+ async function loadAd(adGroupId) {
3009
+ if (!adGroupId) {
3010
+ return {
3011
+ type: "error",
3012
+ error: createError(ERROR_CODES.INVALID_SPACE, "Invalid adGroupId - adGroupId must be provided")
3013
+ };
3014
+ }
3015
+ if (!fetchTossAd.isSupported()) {
3016
+ return {
3017
+ type: "error",
3018
+ error: createError(ERROR_CODES.SDK_NOT_INITIALIZED, "This feature is not supported in the current environment")
3019
+ };
3020
+ }
3021
+ let response;
3022
+ try {
3023
+ const raw = await fetchTossAdPromise({
3024
+ adGroupId,
3025
+ sdkId: SDK_ID,
3026
+ availableStyleIds: AVAILABLE_STYLE_IDS
3027
+ });
3028
+ response = normalizeApiResponse(raw);
3029
+ } catch (error) {
3030
+ if (isAdError(error)) {
3031
+ return { type: "error", error };
3032
+ }
3033
+ const message = error instanceof Error ? error.message : "Network error";
3034
+ return {
3035
+ type: "error",
3036
+ error: createError(ERROR_CODES.NETWORK_ERROR, message)
3037
+ };
3038
+ }
3039
+ if (response.resultType !== "SUCCESS") {
3040
+ const mappedError = API_RESULT_ERROR_MAP[response.resultType];
3041
+ const errorMessage = response.error?.reason ?? mappedError?.message ?? ERROR_MESSAGES[ERROR_CODES.INTERNAL_ERROR];
3042
+ const errorCode = mappedError?.code ?? ERROR_CODES.INTERNAL_ERROR;
3043
+ return {
3044
+ type: "error",
3045
+ error: createError(errorCode, errorMessage)
3046
+ };
3047
+ }
3048
+ if (!response.success) {
3049
+ return {
3050
+ type: "error",
3051
+ error: createError(ERROR_CODES.INTERNAL_ERROR, "Missing ad response payload")
3052
+ };
3053
+ }
3054
+ const adResponse = response.success;
3055
+ if (adResponse.status !== "OK" && adResponse.status !== "TEST_MODE") {
3056
+ const statusError = AD_STATUS_ERROR_MAP[adResponse.status];
3057
+ const errorMessage = statusError?.message ?? `Ad response status: ${adResponse.status}`;
3058
+ const errorCode = statusError?.code ?? ERROR_CODES.INTERNAL_ERROR;
3059
+ const error = createError(errorCode, errorMessage);
3060
+ if (error.code === ERROR_CODES.NO_AD) {
3061
+ return { type: "noFill", error, response: adResponse };
3062
+ }
3063
+ return { type: "error", error, response: adResponse };
3064
+ }
3065
+ const topPriorityAd = adResponse.ads[0];
3066
+ const selectedAd = isStructurallyValidAd(topPriorityAd) ? topPriorityAd : adResponse.ads.find(isStructurallyValidAd) ?? null;
3067
+ if (!selectedAd) {
3068
+ return {
3069
+ type: "error",
3070
+ error: createError(ERROR_CODES.INTERNAL_ERROR, "Unsupported styleId in response"),
3071
+ response: adResponse
3072
+ };
3073
+ }
3074
+ return {
3075
+ type: "success",
3076
+ ad: selectedAd,
3077
+ responseExt: adResponse.ext ?? null,
3078
+ requestId: adResponse.requestId,
3079
+ response: adResponse
3080
+ };
3081
+ }
3082
+
3083
+ // src/ads/inlineAd/opener.ts
3084
+ import { openURL as openURL6 } from "@granite-js/react-native";
3085
+ function isSafeUrl(url) {
3086
+ try {
3087
+ const parsed = new URL(url);
3088
+ const dangerous = [
3089
+ "javascript:",
3090
+ "data:",
3091
+ "vbscript:",
3092
+ "file:",
3093
+ "about:",
3094
+ "blob:",
3095
+ "filesystem:",
3096
+ "chrome:",
3097
+ "chrome-extension:",
3098
+ "ftp:",
3099
+ "ftps:",
3100
+ "sftp:",
3101
+ "telnet:",
3102
+ "ssh:",
3103
+ "intent:"
3104
+ ];
3105
+ return !dangerous.includes(parsed.protocol.toLowerCase());
3106
+ } catch {
3107
+ return false;
3108
+ }
3109
+ }
3110
+ function openLandingUrl(url) {
3111
+ if (!isSafeUrl(url)) {
3112
+ console.warn("[InlineAd] Blocked unsafe landing URL:", url);
3113
+ return;
3114
+ }
3115
+ try {
3116
+ openURL6(getWebSchemeOrUri(url));
3117
+ } catch (error) {
3118
+ console.error("[InlineAd] Failed to open landing URL:", error);
3119
+ }
3120
+ }
3121
+ function getWebSchemeOrUri(uri) {
3122
+ const isHttp = ["http://", "https://"].some((protocol) => uri.startsWith(protocol));
3123
+ return isHttp ? supertossWeb(uri) : uri;
3124
+ }
3125
+ function supertossWeb(uri) {
3126
+ return `supertoss://web?url=${encodeURIComponent(uri)}&external=true`;
3127
+ }
3128
+
3129
+ // src/ads/inlineAd/tracking.ts
3130
+ var GlobalEventDeduplicator = class _GlobalEventDeduplicator {
3131
+ static MAX_EVENTS = 1e4;
3132
+ trackedEvents = /* @__PURE__ */ new Set();
3133
+ hasTracked(requestId, creativeId, eventType) {
3134
+ return this.trackedEvents.has(this.buildKey(requestId, creativeId, eventType));
3135
+ }
3136
+ markTracked(requestId, creativeId, eventType) {
3137
+ if (this.trackedEvents.size >= _GlobalEventDeduplicator.MAX_EVENTS) {
3138
+ const firstKey = this.trackedEvents.keys().next().value;
3139
+ if (firstKey) {
3140
+ this.trackedEvents.delete(firstKey);
2816
3141
  }
2817
- const cacheKeyPrefix = '@apps-in-toss/caches/';
2818
- const cacheKey = \`\${cacheKeyPrefix}${global2.deploymentId}\`;
2819
- window.addEventListener('load', async () => {
2820
- const keys = await caches.keys();
2821
- for (const key of keys) {
2822
- if (key.startsWith(cacheKeyPrefix) && key !== cacheKey) {
2823
- await caches.delete(key);
2824
- }
3142
+ }
3143
+ this.trackedEvents.add(this.buildKey(requestId, creativeId, eventType));
3144
+ }
3145
+ buildKey(requestId, creativeId, eventType) {
3146
+ return `${requestId}:${creativeId}:${eventType}`;
3147
+ }
3148
+ };
3149
+ var globalEventDeduplicator = new GlobalEventDeduplicator();
3150
+ var EventTracker = class {
3151
+ constructor(eventTrackingUrls = [], eventTypes = [], eventPayload = "", requestId, creativeId, requestHeaders) {
3152
+ this.eventTrackingUrls = eventTrackingUrls;
3153
+ this.eventPayload = eventPayload;
3154
+ this.requestId = requestId;
3155
+ this.creativeId = creativeId;
3156
+ this.requestHeaders = requestHeaders;
3157
+ this.eventTypes = new Set(eventTypes);
3158
+ this.repeatableEvents = /* @__PURE__ */ new Set(["VIEW_MUTE", "VIEW_UNMUTE"]);
3159
+ }
3160
+ eventTypes;
3161
+ repeatableEvents;
3162
+ async track(eventType) {
3163
+ if (!this.eventTypes.has(eventType)) {
3164
+ return;
3165
+ }
3166
+ if (!this.repeatableEvents.has(eventType)) {
3167
+ if (this.requestId && this.creativeId) {
3168
+ if (globalEventDeduplicator.hasTracked(this.requestId, this.creativeId, eventType)) {
3169
+ return;
2825
3170
  }
2826
- });
2827
- window.fetch = new Proxy(window.fetch, {
2828
- async apply(originalFetch, thisArg, args) {
2829
- const request = new Request(args[0], args[1]);
2830
- if (!/\\.(data|wasm|framework\\.js)(?:\\.gz|\\.br|\\.unityweb)?$/.test(request.url)) {
2831
- return await originalFetch.call(thisArg, request);
2832
- }
2833
- const cache = await caches.open(cacheKey);
2834
- const cached = await cache.match(request);
2835
- if (cached) {
2836
- const eTag = cached.headers.get('ETag');
2837
- const lastModified = cached.headers.get('Last-Modified');
2838
- if (eTag) {
2839
- request.headers.set('If-None-Match', eTag);
3171
+ }
3172
+ }
3173
+ if (this.eventTrackingUrls.length === 0) {
3174
+ return;
3175
+ }
3176
+ const payload = {
3177
+ type: eventType,
3178
+ data: this.eventPayload
3179
+ };
3180
+ await Promise.allSettled(this.eventTrackingUrls.map((url) => this.sendTrackingRequest(url, payload)));
3181
+ if (!this.repeatableEvents.has(eventType) && this.requestId && this.creativeId) {
3182
+ globalEventDeduplicator.markTracked(this.requestId, this.creativeId, eventType);
3183
+ }
3184
+ }
3185
+ async sendTrackingRequest(url, payload) {
3186
+ const headers = {
3187
+ "Content-Type": "application/json",
3188
+ ...this.requestHeaders ?? {}
3189
+ };
3190
+ if (this.requestId) {
3191
+ headers["X-Toss-RequestId"] = this.requestId;
3192
+ }
3193
+ await fetch(url, {
3194
+ method: "POST",
3195
+ headers,
3196
+ body: JSON.stringify(payload)
3197
+ });
3198
+ }
3199
+ };
3200
+
3201
+ // src/ads/inlineAd/ui/FeedBannerAdView.tsx
3202
+ import { Path, Svg } from "@granite-js/native/react-native-svg";
3203
+ import { useRef as useRef7 } from "react";
3204
+ import {
3205
+ Animated,
3206
+ Easing,
3207
+ Image,
3208
+ Pressable as Pressable2,
3209
+ StyleSheet,
3210
+ Text,
3211
+ View as View5
3212
+ } from "react-native";
3213
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
3214
+ var ARROW_PATH = "m7.5 20.4c-.5-.5-.5-1.2 0-1.7l6.7-6.7-6.8-6.7c-.5-.5-.5-1.2 0-1.7s1.2-.5 1.7 0l7.5 7.5c.5.5.5 1.2 0 1.7l-7.5 7.5c-.2.3-.5.4-.8.4s-.6-.1-.8-.3z";
3215
+ function FeedBannerAdView({
3216
+ brandName,
3217
+ brandLogoUri,
3218
+ title,
3219
+ subtitle,
3220
+ mainImageUri,
3221
+ ctaText,
3222
+ ctaTextColor,
3223
+ ctaBackgroundColor,
3224
+ adClearanceText,
3225
+ colors,
3226
+ isAdBadgeEnabled,
3227
+ paddingStyle,
3228
+ onPress
3229
+ }) {
3230
+ const scale = useRef7(new Animated.Value(1)).current;
3231
+ const animateScale = (toValue) => {
3232
+ Animated.timing(scale, {
3233
+ toValue,
3234
+ duration: 100,
3235
+ easing: Easing.inOut(Easing.ease),
3236
+ useNativeDriver: true
3237
+ }).start();
3238
+ };
3239
+ const handlePressIn = () => {
3240
+ animateScale(0.98);
3241
+ };
3242
+ const handlePressOut = () => {
3243
+ animateScale(1);
3244
+ };
3245
+ const resolvedCtaBackground = ctaBackgroundColor ?? "#3081F9";
3246
+ const resolvedCtaTextColor = ctaTextColor ?? "#ffffff";
3247
+ return /* @__PURE__ */ jsx20(
3248
+ Pressable2,
3249
+ {
3250
+ accessibilityRole: "button",
3251
+ onPress: () => onPress(null),
3252
+ onPressIn: handlePressIn,
3253
+ onPressOut: handlePressOut,
3254
+ style: styles.pressable,
3255
+ children: /* @__PURE__ */ jsxs9(Animated.View, { style: [styles.container, paddingStyle, { transform: [{ scale }] }], children: [
3256
+ /* @__PURE__ */ jsxs9(View5, { style: styles.profileContainer, children: [
3257
+ /* @__PURE__ */ jsxs9(View5, { style: styles.brandArea, children: [
3258
+ /* @__PURE__ */ jsx20(
3259
+ Pressable2,
3260
+ {
3261
+ accessibilityRole: "button",
3262
+ onPress: () => onPress("202"),
3263
+ onPressIn: handlePressIn,
3264
+ onPressOut: handlePressOut,
3265
+ style: styles.logoContainer,
3266
+ children: /* @__PURE__ */ jsxs9(View5, { style: [styles.logoWrapper, { backgroundColor: colors.brandLogoBg }], children: [
3267
+ brandLogoUri ? /* @__PURE__ */ jsx20(Image, { source: { uri: brandLogoUri }, style: styles.logoImage, resizeMode: "cover" }) : null,
3268
+ /* @__PURE__ */ jsx20(View5, { style: [styles.logoOverlay, { borderColor: colors.brandLogoBorder }] })
3269
+ ] })
3270
+ }
3271
+ ),
3272
+ /* @__PURE__ */ jsxs9(View5, { style: styles.brandTextContainer, children: [
3273
+ /* @__PURE__ */ jsx20(
3274
+ Pressable2,
3275
+ {
3276
+ accessibilityRole: "button",
3277
+ onPress: () => onPress("103"),
3278
+ onPressIn: handlePressIn,
3279
+ onPressOut: handlePressOut,
3280
+ children: /* @__PURE__ */ jsx20(Text, { numberOfLines: 1, style: [styles.brandName, { color: colors.brandName }], children: brandName })
3281
+ }
3282
+ ),
3283
+ isAdBadgeEnabled ? /* @__PURE__ */ jsx20(Text, { numberOfLines: 1, style: [styles.adBadge, { color: colors.adBadge }], children: "\uAD11\uACE0" }) : null
3284
+ ] })
3285
+ ] }),
3286
+ /* @__PURE__ */ jsxs9(View5, { style: styles.textArea, children: [
3287
+ /* @__PURE__ */ jsx20(
3288
+ Pressable2,
3289
+ {
3290
+ accessibilityRole: "button",
3291
+ onPress: () => onPress("101"),
3292
+ onPressIn: handlePressIn,
3293
+ onPressOut: handlePressOut,
3294
+ children: /* @__PURE__ */ jsx20(Text, { style: [styles.title, { color: colors.title }], children: title })
3295
+ }
3296
+ ),
3297
+ /* @__PURE__ */ jsx20(
3298
+ Pressable2,
3299
+ {
3300
+ accessibilityRole: "button",
3301
+ onPress: () => onPress("102"),
3302
+ onPressIn: handlePressIn,
3303
+ onPressOut: handlePressOut,
3304
+ children: /* @__PURE__ */ jsx20(Text, { style: [styles.subtitle, { color: colors.subtitle }], children: subtitle })
3305
+ }
3306
+ )
3307
+ ] })
3308
+ ] }),
3309
+ /* @__PURE__ */ jsxs9(View5, { style: styles.card, children: [
3310
+ /* @__PURE__ */ jsx20(
3311
+ Pressable2,
3312
+ {
3313
+ accessibilityRole: "button",
3314
+ onPress: () => onPress("201"),
3315
+ onPressIn: handlePressIn,
3316
+ onPressOut: handlePressOut,
3317
+ style: styles.imageButton,
3318
+ children: /* @__PURE__ */ jsx20(View5, { style: styles.imageContainer, children: mainImageUri ? /* @__PURE__ */ jsx20(Image, { source: { uri: mainImageUri }, style: styles.mainImage, resizeMode: "cover" }) : null })
2840
3319
  }
2841
- if (lastModified) {
2842
- request.headers.set('If-Modified-Since', lastModified);
3320
+ ),
3321
+ /* @__PURE__ */ jsxs9(
3322
+ Pressable2,
3323
+ {
3324
+ accessibilityRole: "button",
3325
+ onPress: () => onPress("0"),
3326
+ onPressIn: handlePressIn,
3327
+ onPressOut: handlePressOut,
3328
+ style: [styles.cta, { backgroundColor: resolvedCtaBackground }],
3329
+ children: [
3330
+ /* @__PURE__ */ jsx20(Text, { style: [styles.ctaText, { color: resolvedCtaTextColor }], children: ctaText }),
3331
+ /* @__PURE__ */ jsx20(Svg, { width: 20, height: 20, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx20(Path, { d: ARROW_PATH, fill: resolvedCtaTextColor }) })
3332
+ ]
2843
3333
  }
2844
- const revalidated = await originalFetch.call(thisArg, request);
2845
- if (revalidated.status === 304) {
2846
- return cached;
3334
+ ),
3335
+ /* @__PURE__ */ jsx20(View5, { style: [styles.cardOverlay, { borderColor: colors.imageOverlayBorder }] })
3336
+ ] }),
3337
+ adClearanceText ? /* @__PURE__ */ jsx20(Text, { style: [styles.adClearance, { color: colors.adClearance }], children: adClearanceText }) : null
3338
+ ] })
3339
+ }
3340
+ );
3341
+ }
3342
+ var styles = StyleSheet.create({
3343
+ pressable: {
3344
+ width: "100%"
3345
+ },
3346
+ container: {
3347
+ flexDirection: "column",
3348
+ padding: 0,
3349
+ width: "100%"
3350
+ },
3351
+ profileContainer: {
3352
+ flexDirection: "column",
3353
+ paddingBottom: 8,
3354
+ gap: 12
3355
+ },
3356
+ brandArea: {
3357
+ flexDirection: "row",
3358
+ alignItems: "center",
3359
+ gap: 8
3360
+ },
3361
+ logoContainer: {
3362
+ width: 40,
3363
+ height: 40,
3364
+ alignItems: "center",
3365
+ justifyContent: "center"
3366
+ },
3367
+ logoWrapper: {
3368
+ width: 36,
3369
+ height: 36,
3370
+ borderRadius: 18,
3371
+ overflow: "hidden",
3372
+ alignItems: "center",
3373
+ justifyContent: "center"
3374
+ },
3375
+ logoImage: {
3376
+ width: 36,
3377
+ height: 36,
3378
+ borderRadius: 18
3379
+ },
3380
+ logoOverlay: {
3381
+ ...StyleSheet.absoluteFillObject,
3382
+ borderWidth: 1,
3383
+ borderRadius: 18
3384
+ },
3385
+ brandTextContainer: {
3386
+ flexDirection: "column"
3387
+ },
3388
+ brandName: {
3389
+ fontSize: 15,
3390
+ fontWeight: "600",
3391
+ lineHeight: 22.5
3392
+ },
3393
+ adBadge: {
3394
+ fontSize: 13,
3395
+ fontWeight: "400",
3396
+ lineHeight: 19.5
3397
+ },
3398
+ textArea: {
3399
+ flexDirection: "column",
3400
+ alignItems: "flex-start"
3401
+ },
3402
+ title: {
3403
+ fontSize: 15,
3404
+ fontWeight: "600",
3405
+ lineHeight: 22.5
3406
+ },
3407
+ subtitle: {
3408
+ fontSize: 15,
3409
+ fontWeight: "400",
3410
+ lineHeight: 22.5
3411
+ },
3412
+ card: {
3413
+ borderRadius: 12,
3414
+ overflow: "hidden",
3415
+ position: "relative",
3416
+ width: "100%"
3417
+ },
3418
+ imageButton: {
3419
+ width: "100%"
3420
+ },
3421
+ imageContainer: {
3422
+ width: "100%",
3423
+ aspectRatio: 16 / 9,
3424
+ overflow: "hidden"
3425
+ },
3426
+ mainImage: {
3427
+ width: "100%",
3428
+ height: "100%"
3429
+ },
3430
+ cta: {
3431
+ height: 50,
3432
+ paddingLeft: 20,
3433
+ paddingRight: 16,
3434
+ alignItems: "center",
3435
+ justifyContent: "space-between",
3436
+ flexDirection: "row"
3437
+ },
3438
+ ctaText: {
3439
+ fontSize: 15,
3440
+ fontWeight: "600",
3441
+ lineHeight: 22.5
3442
+ },
3443
+ cardOverlay: {
3444
+ ...StyleSheet.absoluteFillObject,
3445
+ borderWidth: 2,
3446
+ borderRadius: 12,
3447
+ pointerEvents: "none"
3448
+ },
3449
+ adClearance: {
3450
+ marginTop: 10,
3451
+ fontSize: 8,
3452
+ fontWeight: "400"
3453
+ }
3454
+ });
3455
+
3456
+ // src/ads/inlineAd/ui/ListBannerAdView.tsx
3457
+ import { ClipPath, Defs, Image as SvgImage, Path as Path2, Svg as Svg2 } from "@granite-js/native/react-native-svg";
3458
+ import { useRef as useRef8 } from "react";
3459
+ import { Animated as Animated2, Easing as Easing2, Pressable as Pressable3, StyleSheet as StyleSheet2, Text as Text2, View as View6 } from "react-native";
3460
+ import { jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
3461
+ var SQUIRCLE_PATH = "M0 17.352C0 3.564 3.564 0 17.352 0H18.648C32.436 0 36 3.564 36 17.352V18.648C36 32.436 32.436 36 18.648 36H17.352C3.564 36 0 32.436 0 18.648V17.352Z";
3462
+ function ListBannerAdView({
3463
+ title,
3464
+ subtitle,
3465
+ adClearanceText,
3466
+ adClearanceFontSize,
3467
+ imageUri,
3468
+ paddingStyle,
3469
+ colors,
3470
+ onPress
3471
+ }) {
3472
+ const scale = useRef8(new Animated2.Value(1)).current;
3473
+ const clipIdRef = useRef8(`clip-${Math.random().toString(36).slice(2)}`);
3474
+ const animateScale = (toValue) => {
3475
+ Animated2.timing(scale, {
3476
+ toValue,
3477
+ duration: 100,
3478
+ easing: Easing2.inOut(Easing2.ease),
3479
+ useNativeDriver: true
3480
+ }).start();
3481
+ };
3482
+ const handlePressIn = () => {
3483
+ animateScale(0.96);
3484
+ };
3485
+ const handlePressOut = () => {
3486
+ animateScale(1);
3487
+ };
3488
+ return /* @__PURE__ */ jsx21(
3489
+ Pressable3,
3490
+ {
3491
+ accessibilityRole: "button",
3492
+ onPress: () => onPress(null),
3493
+ onPressIn: handlePressIn,
3494
+ onPressOut: handlePressOut,
3495
+ style: styles2.pressable,
3496
+ children: /* @__PURE__ */ jsxs10(Animated2.View, { style: [styles2.container, paddingStyle, { transform: [{ scale }] }], children: [
3497
+ /* @__PURE__ */ jsxs10(View6, { style: styles2.titleRow, children: [
3498
+ /* @__PURE__ */ jsx21(
3499
+ Pressable3,
3500
+ {
3501
+ accessibilityRole: "button",
3502
+ onPress: () => onPress("202"),
3503
+ onPressIn: handlePressIn,
3504
+ onPressOut: handlePressOut,
3505
+ style: styles2.iconWrapper,
3506
+ children: /* @__PURE__ */ jsxs10(Svg2, { width: 36, height: 36, viewBox: "0 0 36 36", children: [
3507
+ /* @__PURE__ */ jsx21(Defs, { children: /* @__PURE__ */ jsx21(ClipPath, { id: clipIdRef.current, children: /* @__PURE__ */ jsx21(Path2, { d: SQUIRCLE_PATH }) }) }),
3508
+ /* @__PURE__ */ jsx21(Path2, { d: SQUIRCLE_PATH, fill: colors.iconBg }),
3509
+ imageUri ? /* @__PURE__ */ jsx21(
3510
+ SvgImage,
3511
+ {
3512
+ href: { uri: imageUri },
3513
+ width: 36,
3514
+ height: 36,
3515
+ preserveAspectRatio: "xMidYMid slice",
3516
+ clipPath: `url(#${clipIdRef.current})`
3517
+ }
3518
+ ) : null,
3519
+ /* @__PURE__ */ jsx21(
3520
+ Path2,
3521
+ {
3522
+ d: SQUIRCLE_PATH,
3523
+ fill: "none",
3524
+ stroke: colors.iconBorder,
3525
+ strokeWidth: 2,
3526
+ clipPath: `url(#${clipIdRef.current})`
3527
+ }
3528
+ )
3529
+ ] })
2847
3530
  }
2848
- cache.put(request, revalidated.clone());
2849
- return revalidated;
3531
+ ),
3532
+ /* @__PURE__ */ jsxs10(View6, { style: styles2.textWrapper, children: [
3533
+ /* @__PURE__ */ jsx21(
3534
+ Pressable3,
3535
+ {
3536
+ accessibilityRole: "button",
3537
+ onPress: () => onPress("101"),
3538
+ onPressIn: handlePressIn,
3539
+ onPressOut: handlePressOut,
3540
+ children: /* @__PURE__ */ jsx21(Text2, { style: [styles2.title, { color: colors.title }], children: title })
3541
+ }
3542
+ ),
3543
+ /* @__PURE__ */ jsx21(
3544
+ Pressable3,
3545
+ {
3546
+ accessibilityRole: "button",
3547
+ onPress: () => onPress("102"),
3548
+ onPressIn: handlePressIn,
3549
+ onPressOut: handlePressOut,
3550
+ children: /* @__PURE__ */ jsx21(Text2, { style: [styles2.subtitle, { color: colors.subtitle }], children: subtitle })
3551
+ }
3552
+ )
3553
+ ] })
3554
+ ] }),
3555
+ adClearanceText ? /* @__PURE__ */ jsx21(
3556
+ Text2,
3557
+ {
3558
+ style: [
3559
+ styles2.adClearance,
3560
+ {
3561
+ color: colors.adClearance,
3562
+ fontSize: adClearanceFontSize,
3563
+ lineHeight: adClearanceFontSize * 1.2
3564
+ }
3565
+ ],
3566
+ children: adClearanceText
2850
3567
  }
2851
- const response = await originalFetch.call(thisArg, request);
2852
- cache.put(request, response.clone());
2853
- return response;
2854
- },
2855
- });
2856
- })();
2857
- `;
3568
+ ) : null
3569
+ ] })
3570
+ }
3571
+ );
3572
+ }
3573
+ var styles2 = StyleSheet2.create({
3574
+ pressable: {
3575
+ width: "100%"
3576
+ },
3577
+ container: {
3578
+ flexDirection: "column",
3579
+ padding: 0,
3580
+ width: "100%"
3581
+ },
3582
+ titleRow: {
3583
+ flexDirection: "row",
3584
+ alignItems: "center"
3585
+ },
3586
+ iconWrapper: {
3587
+ width: 36,
3588
+ height: 36,
3589
+ overflow: "hidden",
3590
+ alignItems: "center",
3591
+ justifyContent: "center",
3592
+ marginRight: 12
3593
+ },
3594
+ textWrapper: {
3595
+ flex: 1,
3596
+ flexDirection: "column",
3597
+ alignItems: "flex-start",
3598
+ justifyContent: "center",
3599
+ width: "100%"
3600
+ },
3601
+ title: {
3602
+ fontSize: 17,
3603
+ fontWeight: "700",
3604
+ lineHeight: 25.5
3605
+ },
3606
+ subtitle: {
3607
+ fontSize: 13,
3608
+ fontWeight: "500",
3609
+ flexShrink: 1,
3610
+ lineHeight: 15.6
3611
+ },
3612
+ adClearance: {
3613
+ marginTop: 8,
3614
+ paddingLeft: 48,
3615
+ width: "100%"
3616
+ }
3617
+ });
3618
+
3619
+ // src/ads/inlineAd/utils/ids.ts
3620
+ var slotCounter = 0;
3621
+ function createSlotId() {
3622
+ slotCounter += 1;
3623
+ return `banner-slot-${Date.now()}-${slotCounter}`;
3624
+ }
3625
+
3626
+ // src/ads/inlineAd/utils/padding.ts
3627
+ function resolvePaddingStyle(padding) {
3628
+ if (padding === void 0) {
3629
+ return void 0;
3630
+ }
3631
+ if (typeof padding === "number") {
3632
+ return { padding };
3633
+ }
3634
+ const trimmed = padding.trim();
3635
+ if (!trimmed) {
3636
+ return void 0;
3637
+ }
3638
+ const parts = trimmed.split(/\s+/);
3639
+ if (parts.length < 1 || parts.length > 4) {
3640
+ return void 0;
3641
+ }
3642
+ const values = parts.map((part) => {
3643
+ const normalized = part.endsWith("px") ? part.slice(0, -2).trim() : part;
3644
+ const numeric = Number(normalized);
3645
+ return Number.isFinite(numeric) ? numeric : null;
3646
+ });
3647
+ if (values.some((value) => value === null)) {
3648
+ return void 0;
3649
+ }
3650
+ const [top, right, bottom, left] = normalizePaddingValues(values);
2858
3651
  return {
2859
- beforeLoad: mergeScripts(global2.webViewType === "game" && applyGameResourcesCache),
2860
- afterLoad: mergeScripts(disableTextSelectionCSS)
3652
+ paddingTop: top,
3653
+ paddingRight: right,
3654
+ paddingBottom: bottom,
3655
+ paddingLeft: left
2861
3656
  };
2862
3657
  }
2863
- function mergeScripts(...scripts) {
2864
- return scripts.filter((script) => typeof script === "string").join("\n");
3658
+ function normalizePaddingValues(values) {
3659
+ const [top = 0, right = top, bottom = top, left = right] = values;
3660
+ return [top, right, bottom, left];
2865
3661
  }
2866
3662
 
3663
+ // src/ads/inlineAd/utils/payload.ts
3664
+ function buildBannerEventPayload(slotId, adGroupId, ad) {
3665
+ return {
3666
+ slotId,
3667
+ adGroupId,
3668
+ adMetadata: {
3669
+ creativeId: ad.creative?.id ?? "",
3670
+ requestId: ad.requestId ?? "",
3671
+ styleId: ad.styleId ?? LIST_BANNER_STYLE_ID
3672
+ }
3673
+ };
3674
+ }
3675
+ function buildBannerErrorPayload(slotId, adGroupId, error) {
3676
+ return {
3677
+ slotId,
3678
+ adGroupId,
3679
+ adMetadata: {},
3680
+ error
3681
+ };
3682
+ }
3683
+ function buildNoFillPayload(slotId, adGroupId) {
3684
+ return {
3685
+ slotId,
3686
+ adGroupId,
3687
+ adMetadata: {}
3688
+ };
3689
+ }
3690
+
3691
+ // src/ads/inlineAd/InlineAd.tsx
3692
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
3693
+ var themePalette = {
3694
+ light: {
3695
+ title: "#4e5968",
3696
+ subtitle: "#6b7684",
3697
+ adClearance: "#b0b8c1",
3698
+ iconBg: "rgba(2,32,71,0.05)",
3699
+ iconBorder: "rgba(2,32,71,0.05)"
3700
+ },
3701
+ dark: {
3702
+ title: "rgba(253,253,255,0.75)",
3703
+ subtitle: "rgba(248,248,255,0.6)",
3704
+ adClearance: "rgba(242,242,255,0.47)",
3705
+ iconBg: "#ffffff",
3706
+ iconBorder: "rgba(217,217,255,0.11)"
3707
+ }
3708
+ };
3709
+ var feedThemePalette = {
3710
+ light: {
3711
+ brandLogoBg: "rgba(2,32,71,0.05)",
3712
+ brandLogoBorder: "rgba(2,32,71,0.05)",
3713
+ brandName: "#6b7684",
3714
+ adBadge: "#8b95a1",
3715
+ title: "#333d4b",
3716
+ subtitle: "#4e5968",
3717
+ adClearance: "#8b95a1",
3718
+ imageOverlayBorder: "rgba(2,32,71,0.05)"
3719
+ },
3720
+ dark: {
3721
+ brandLogoBg: "#ffffff",
3722
+ brandLogoBorder: "rgba(217,217,255,0.11)",
3723
+ brandName: "#9e9ea4",
3724
+ adBadge: "#7e7e87",
3725
+ title: "#e4e4e5",
3726
+ subtitle: "#c3c3c6",
3727
+ adClearance: "#7e7e87",
3728
+ imageOverlayBorder: "rgba(217,217,255,0.11)"
3729
+ }
3730
+ };
3731
+ function createError2(code, message) {
3732
+ return {
3733
+ code,
3734
+ message: message || ERROR_MESSAGES[code] || "Unknown error",
3735
+ domain: "@apps-in-toss/framework"
3736
+ };
3737
+ }
3738
+ function isListBannerCreative(creative) {
3739
+ return Boolean(
3740
+ creative && typeof creative.title === "string" && typeof creative.subTitle === "string" && typeof creative.landingUrl === "string" && typeof creative.imageUrl === "string"
3741
+ );
3742
+ }
3743
+ function isFeedBannerCreative(creative) {
3744
+ if (!creative || typeof creative !== "object") {
3745
+ return false;
3746
+ }
3747
+ if (!("brandName" in creative)) {
3748
+ return false;
3749
+ }
3750
+ const candidate = creative;
3751
+ return Boolean(
3752
+ typeof candidate.brandName === "string" && typeof candidate.brandLogoUrl === "string" && typeof candidate.title === "string" && typeof candidate.subTitle === "string" && typeof candidate.mainImageUrl === "string" && typeof candidate.ctaText === "string" && typeof candidate.landingUrl === "string"
3753
+ );
3754
+ }
3755
+ function isValidCreative(ad) {
3756
+ const creative = ad.creative;
3757
+ const styleId = String(ad.styleId);
3758
+ if (styleId === LIST_BANNER_STYLE_ID) {
3759
+ return isListBannerCreative(creative);
3760
+ }
3761
+ if (styleId === FEED_BANNER_STYLE_ID) {
3762
+ return isFeedBannerCreative(creative);
3763
+ }
3764
+ return false;
3765
+ }
3766
+ function InlineAd(props) {
3767
+ const {
3768
+ adGroupId,
3769
+ theme,
3770
+ tone,
3771
+ variant,
3772
+ impressFallbackOnMount,
3773
+ onAdRendered,
3774
+ onAdViewable,
3775
+ onAdClicked,
3776
+ onAdImpression,
3777
+ onAdFailedToRender,
3778
+ onNoFill
3779
+ } = props;
3780
+ const slotIdRef = useRef9(createSlotId());
3781
+ const [ad, setAd] = useState7(null);
3782
+ const [isAdBadgeEnabled, setIsAdBadgeEnabled] = useState7(true);
3783
+ const eventTrackerRef = useRef9(null);
3784
+ const eventPayloadRef = useRef9(null);
3785
+ const hasRenderedRef = useRef9(false);
3786
+ const hasLoggedImp1pxRef = useRef9(false);
3787
+ const hasLoggedImp100pRef = useRef9(false);
3788
+ const hasNotifiedViewableRef = useRef9(false);
3789
+ const viewableTimerRef = useRef9(null);
3790
+ const refetchIntervalMsRef = useRef9(null);
3791
+ const lastImp1pxAtRef = useRef9(null);
3792
+ const loadingRef = useRef9(false);
3793
+ const loadRef = useRef9(null);
3794
+ const callbacksRef = useRef9({
3795
+ onAdRendered,
3796
+ onAdViewable,
3797
+ onAdClicked,
3798
+ onAdImpression,
3799
+ onAdFailedToRender,
3800
+ onNoFill
3801
+ });
3802
+ const isMountedRef = useRef9(false);
3803
+ const colorScheme = useColorScheme();
3804
+ const selectedTheme = theme ?? DEFAULT_INLINE_AD_THEME;
3805
+ const resolvedTheme = selectedTheme === "auto" ? colorScheme === "dark" ? "dark" : "light" : selectedTheme;
3806
+ const resolvedTone = tone ?? DEFAULT_INLINE_AD_TONE;
3807
+ const resolvedVariant = variant ?? DEFAULT_INLINE_AD_VARIANT;
3808
+ const colors = themePalette[resolvedTheme];
3809
+ const feedColors = feedThemePalette[resolvedTheme];
3810
+ const backgroundColor = resolvedTone === "grey" ? resolvedTheme === "dark" ? "#101013" : "#f2f4f7" : resolvedTheme === "dark" ? "#17171c" : "#ffffff";
3811
+ callbacksRef.current = {
3812
+ onAdRendered,
3813
+ onAdViewable,
3814
+ onAdClicked,
3815
+ onAdImpression,
3816
+ onAdFailedToRender,
3817
+ onNoFill
3818
+ };
3819
+ useEffect14(() => {
3820
+ isMountedRef.current = true;
3821
+ return () => {
3822
+ isMountedRef.current = false;
3823
+ if (viewableTimerRef.current) {
3824
+ clearTimeout(viewableTimerRef.current);
3825
+ viewableTimerRef.current = null;
3826
+ }
3827
+ };
3828
+ }, []);
3829
+ useEffect14(() => {
3830
+ const loadAdRequest = () => {
3831
+ if (loadingRef.current) {
3832
+ return;
3833
+ }
3834
+ if (!adGroupId) {
3835
+ const error = createError2(
3836
+ ERROR_CODES.INVALID_SPACE,
3837
+ `${ERROR_MESSAGES[ERROR_CODES.INVALID_SPACE]} - adGroupId must be provided`
3838
+ );
3839
+ callbacksRef.current.onAdFailedToRender?.(buildBannerErrorPayload(slotIdRef.current, adGroupId, error));
3840
+ return;
3841
+ }
3842
+ loadingRef.current = true;
3843
+ loadAd(adGroupId).then((result) => {
3844
+ if (!isMountedRef.current) return;
3845
+ if (result.type === "noFill") {
3846
+ callbacksRef.current.onNoFill?.(buildNoFillPayload(slotIdRef.current, adGroupId));
3847
+ return;
3848
+ }
3849
+ if (result.type === "error") {
3850
+ callbacksRef.current.onAdFailedToRender?.(
3851
+ buildBannerErrorPayload(slotIdRef.current, adGroupId, result.error)
3852
+ );
3853
+ return;
3854
+ }
3855
+ if (!isValidCreative(result.ad)) {
3856
+ const invalidError = createError2(ERROR_CODES.INTERNAL_ERROR, "Invalid creative payload");
3857
+ callbacksRef.current.onAdFailedToRender?.(
3858
+ buildBannerErrorPayload(slotIdRef.current, adGroupId, invalidError)
3859
+ );
3860
+ return;
3861
+ }
3862
+ if (result.responseExt?.refetchSeconds !== void 0) {
3863
+ refetchIntervalMsRef.current = typeof result.responseExt.refetchSeconds === "number" && result.responseExt.refetchSeconds > 0 ? result.responseExt.refetchSeconds * 1e3 : null;
3864
+ }
3865
+ hasRenderedRef.current = false;
3866
+ hasLoggedImp1pxRef.current = false;
3867
+ hasLoggedImp100pRef.current = false;
3868
+ hasNotifiedViewableRef.current = false;
3869
+ lastImp1pxAtRef.current = null;
3870
+ if (viewableTimerRef.current) {
3871
+ clearTimeout(viewableTimerRef.current);
3872
+ viewableTimerRef.current = null;
3873
+ }
3874
+ const payload = buildBannerEventPayload(slotIdRef.current, adGroupId, result.ad);
3875
+ eventPayloadRef.current = payload;
3876
+ eventTrackerRef.current = new EventTracker(
3877
+ result.ad.eventTrackingUrls ?? [],
3878
+ result.ad.eventTypes ?? [],
3879
+ result.ad.eventPayload ?? "",
3880
+ result.requestId,
3881
+ result.ad.creative?.id
3882
+ );
3883
+ setIsAdBadgeEnabled(result.responseExt?.isAdBadgeEnabled !== false);
3884
+ setAd(result.ad);
3885
+ }).catch((error) => {
3886
+ if (!isMountedRef.current) return;
3887
+ const adError = error instanceof Error ? createError2(ERROR_CODES.INTERNAL_ERROR, error.message) : createError2(ERROR_CODES.INTERNAL_ERROR, "Unknown error");
3888
+ callbacksRef.current.onAdFailedToRender?.(buildBannerErrorPayload(slotIdRef.current, adGroupId, adError));
3889
+ }).finally(() => {
3890
+ loadingRef.current = false;
3891
+ });
3892
+ };
3893
+ loadRef.current = loadAdRequest;
3894
+ loadAdRequest();
3895
+ return () => {
3896
+ loadRef.current = null;
3897
+ };
3898
+ }, [adGroupId]);
3899
+ useVisibilityChange2((documentVisibility) => {
3900
+ if (documentVisibility !== "visible") {
3901
+ return;
3902
+ }
3903
+ const interval = refetchIntervalMsRef.current;
3904
+ const lastImp1pxAt = lastImp1pxAtRef.current;
3905
+ if (!interval || !lastImp1pxAt || loadingRef.current) {
3906
+ return;
3907
+ }
3908
+ if (Date.now() - lastImp1pxAt >= interval) {
3909
+ loadRef.current?.();
3910
+ }
3911
+ });
3912
+ useEffect14(() => {
3913
+ if (!ad || hasRenderedRef.current) {
3914
+ return;
3915
+ }
3916
+ hasRenderedRef.current = true;
3917
+ const payload = eventPayloadRef.current;
3918
+ if (payload) {
3919
+ callbacksRef.current.onAdRendered?.(payload);
3920
+ }
3921
+ }, [ad]);
3922
+ if (!ad) {
3923
+ return null;
3924
+ }
3925
+ const impressionKey = `${ad.requestId ?? "request"}:${ad.creative?.id ?? "creative"}`;
3926
+ const resolvedStyleId = ad.styleId || LIST_BANNER_STYLE_ID;
3927
+ const defaultPaddingValue = resolvedStyleId === LIST_BANNER_STYLE_ID ? DEFAULT_INLINE_AD_PADDING_BANNER : DEFAULT_INLINE_AD_PADDING_NATIVE_IMAGE;
3928
+ const paddingStyle = resolvePaddingStyle(defaultPaddingValue);
3929
+ const isListBanner = String(resolvedStyleId) === LIST_BANNER_STYLE_ID;
3930
+ const isFeedBanner = String(resolvedStyleId) === FEED_BANNER_STYLE_ID;
3931
+ const listCreative = isListBanner ? ad.creative : null;
3932
+ const feedCreative = isFeedBanner ? ad.creative : null;
3933
+ const subtitleText = listCreative && isAdBadgeEnabled ? `${listCreative.subTitle} \u2022 AD` : listCreative?.subTitle ?? "";
3934
+ const adClearanceText = listCreative?.adClearanceText ?? feedCreative?.adClearanceText ?? void 0;
3935
+ const adClearanceFontSize = adClearanceText && adClearanceText.length >= 60 ? 6 : 8;
3936
+ const imageUri = listCreative?.imageUrl && isSafeUrl(listCreative.imageUrl) ? listCreative.imageUrl : null;
3937
+ const brandLogoUri = feedCreative?.brandLogoUrl && isSafeUrl(feedCreative.brandLogoUrl) ? feedCreative.brandLogoUrl : null;
3938
+ const mainImageUri = feedCreative?.mainImageUrl && isSafeUrl(feedCreative.mainImageUrl) ? feedCreative.mainImageUrl : null;
3939
+ const basePayload = eventPayloadRef.current;
3940
+ const trackEvent = (eventType) => {
3941
+ eventTrackerRef.current?.track(eventType).catch((error) => {
3942
+ console.error("[InlineAd] Failed to track event:", error);
3943
+ });
3944
+ };
3945
+ const handleImpression1px = () => {
3946
+ if (hasLoggedImp1pxRef.current) return;
3947
+ hasLoggedImp1pxRef.current = true;
3948
+ lastImp1pxAtRef.current = Date.now();
3949
+ trackEvent("IMP_1PX");
3950
+ if (basePayload) {
3951
+ void tossAdEventLog({
3952
+ log_name: "display_ads_all::impression__1px_banner",
3953
+ log_type: "event",
3954
+ params: {
3955
+ event_type: "impression",
3956
+ schema_id: 1812034,
3957
+ request_id: basePayload.adMetadata.requestId ?? ""
3958
+ }
3959
+ });
3960
+ callbacksRef.current.onAdImpression?.(basePayload);
3961
+ }
3962
+ };
3963
+ const handleImpression100p = () => {
3964
+ if (hasLoggedImp100pRef.current) return;
3965
+ hasLoggedImp100pRef.current = true;
3966
+ trackEvent("IMP_100P");
3967
+ };
3968
+ const handleViewableStart = () => {
3969
+ if (hasNotifiedViewableRef.current || viewableTimerRef.current) return;
3970
+ viewableTimerRef.current = setTimeout(() => {
3971
+ hasNotifiedViewableRef.current = true;
3972
+ viewableTimerRef.current = null;
3973
+ trackEvent("VIMP");
3974
+ if (basePayload) {
3975
+ callbacksRef.current.onAdViewable?.(basePayload);
3976
+ }
3977
+ }, 1e3);
3978
+ };
3979
+ const handleViewableEnd = () => {
3980
+ if (viewableTimerRef.current) {
3981
+ clearTimeout(viewableTimerRef.current);
3982
+ viewableTimerRef.current = null;
3983
+ }
3984
+ };
3985
+ const handleClick = (slotKey) => {
3986
+ trackEvent("CLICK");
3987
+ if (slotKey != null) {
3988
+ trackEvent(`CLICK_${slotKey}`);
3989
+ }
3990
+ if (isSafeUrl(ad.creative.landingUrl)) {
3991
+ openLandingUrl(ad.creative.landingUrl);
3992
+ }
3993
+ if (basePayload) {
3994
+ callbacksRef.current.onAdClicked?.(basePayload);
3995
+ }
3996
+ };
3997
+ return /* @__PURE__ */ jsx22(
3998
+ ImpressionArea,
3999
+ {
4000
+ style: styles3.impressionArea,
4001
+ onImpressionStart: handleImpression1px,
4002
+ areaThreshold: 0,
4003
+ UNSAFE__impressFallbackOnMount: Boolean(impressFallbackOnMount),
4004
+ children: /* @__PURE__ */ jsx22(
4005
+ ImpressionArea,
4006
+ {
4007
+ onImpressionStart: handleImpression100p,
4008
+ areaThreshold: 1,
4009
+ UNSAFE__impressFallbackOnMount: Boolean(impressFallbackOnMount),
4010
+ children: /* @__PURE__ */ jsx22(
4011
+ ImpressionArea,
4012
+ {
4013
+ onImpressionStart: handleViewableStart,
4014
+ onImpressionEnd: handleViewableEnd,
4015
+ areaThreshold: 0.5,
4016
+ UNSAFE__impressFallbackOnMount: Boolean(impressFallbackOnMount),
4017
+ children: /* @__PURE__ */ jsx22(View7, { style: [styles3.wrapper, resolvedVariant === "card" && styles3.cardWrapper], children: /* @__PURE__ */ jsxs11(View7, { style: [styles3.surface, { backgroundColor }, resolvedVariant === "card" && styles3.cardSurface], children: [
4018
+ isListBanner && listCreative ? /* @__PURE__ */ jsx22(
4019
+ ListBannerAdView,
4020
+ {
4021
+ title: listCreative.title,
4022
+ subtitle: subtitleText,
4023
+ adClearanceText,
4024
+ adClearanceFontSize,
4025
+ imageUri,
4026
+ paddingStyle,
4027
+ colors,
4028
+ onPress: handleClick
4029
+ }
4030
+ ) : null,
4031
+ isFeedBanner && feedCreative ? /* @__PURE__ */ jsx22(
4032
+ FeedBannerAdView,
4033
+ {
4034
+ brandName: feedCreative.brandName,
4035
+ brandLogoUri,
4036
+ title: feedCreative.title,
4037
+ subtitle: feedCreative.subTitle,
4038
+ mainImageUri,
4039
+ ctaText: feedCreative.ctaText,
4040
+ ctaTextColor: feedCreative.ctaTextColor,
4041
+ ctaBackgroundColor: feedCreative.ctaBackgroundColor,
4042
+ adClearanceText,
4043
+ colors: feedColors,
4044
+ isAdBadgeEnabled,
4045
+ paddingStyle,
4046
+ onPress: handleClick
4047
+ }
4048
+ ) : null
4049
+ ] }) })
4050
+ }
4051
+ )
4052
+ }
4053
+ )
4054
+ },
4055
+ impressionKey
4056
+ );
4057
+ }
4058
+ var styles3 = StyleSheet3.create({
4059
+ impressionArea: {
4060
+ width: "100%"
4061
+ },
4062
+ wrapper: {
4063
+ width: "100%"
4064
+ },
4065
+ cardWrapper: {
4066
+ paddingHorizontal: 10
4067
+ },
4068
+ surface: {
4069
+ width: "100%",
4070
+ overflow: "hidden"
4071
+ },
4072
+ cardSurface: {
4073
+ borderRadius: 16
4074
+ }
4075
+ });
4076
+
2867
4077
  // src/index.ts
2868
- export * from "@apps-in-toss/analytics";
2869
- import { useOverlay as useOverlay3, OverlayProvider } from "@toss/tds-react-native/private";
2870
- export * from "@apps-in-toss/native-modules";
2871
- export * from "@apps-in-toss/types";
2872
4078
  var Analytics2 = {
2873
4079
  init: InternalAnalytics.init,
2874
4080
  Impression: InternalAnalytics.Impression,
@@ -2879,6 +4085,7 @@ export {
2879
4085
  Analytics2 as Analytics,
2880
4086
  AppsInToss,
2881
4087
  INTERNAL__onVisibilityChangedByTransparentServiceWeb,
4088
+ InlineAd,
2882
4089
  OverlayProvider,
2883
4090
  WebView,
2884
4091
  env,