@blux.ai/web-sdk 3.1.0 → 3.1.2
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/BluxClient.js
CHANGED
|
@@ -404,6 +404,12 @@ var BluxClient = class {
|
|
|
404
404
|
}
|
|
405
405
|
async init() {
|
|
406
406
|
this.handleUrlAndRef();
|
|
407
|
+
if (this.bridge) {
|
|
408
|
+
const bridge = this.bridge;
|
|
409
|
+
this.pageTransition$.subscribe(() => {
|
|
410
|
+
bridge.pageTransition();
|
|
411
|
+
});
|
|
412
|
+
}
|
|
407
413
|
if (PAGE_VISIT_ENABLED_APPLICATION_IDS.has(this.bluxApplicationId)) this.pageTransition$.subscribe(() => {
|
|
408
414
|
this.sendEvent(new AddPageVisitEvent({}));
|
|
409
415
|
});
|
package/dist/BluxClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BluxClient.js","names":["packageJson.version"],"sources":["../src/BluxClient.ts"],"sourcesContent":["import axios from \"axios\";\nimport ObjectId from \"bson-objectid\";\nimport {\n BehaviorSubject,\n bufferTime,\n catchError,\n combineLatest,\n concatMap,\n debounceTime,\n defer,\n distinctUntilChanged,\n filter,\n first,\n firstValueFrom,\n from,\n groupBy,\n map,\n merge,\n mergeMap,\n NEVER,\n repeat,\n skip,\n Subject,\n Subscription,\n switchMap,\n throwError,\n timeout,\n timer,\n} from \"rxjs\";\n\nimport packageJson from \"../package.json\";\nimport { DevicePlatform, type APIs } from \"./apis/APIs\";\nimport { createCrmEvent } from \"./apis/createCrmEvent\";\nimport { collectEvent } from \"./apis/createEvent\";\nimport { getItemRecommendations } from \"./apis/getItemRecommendations\";\nimport { getNotifications } from \"./apis/getNotifications\";\nimport { initialize } from \"./apis/initialize\";\nimport { notificationsUpdate } from \"./apis/notificationsUpdate\";\nimport { signIn } from \"./apis/signIn\";\nimport { signOut } from \"./apis/signOut\";\nimport { updateProperties } from \"./apis/updateProperties\";\nimport { AndroidBridge } from \"./bridges/AndroidBridge\";\nimport { Bridge, BridgePlatform } from \"./bridges/Bridge\";\nimport { FlutterBridge } from \"./bridges/FlutterBridge\";\nimport { IosBridge } from \"./bridges/IosBridge\";\nimport { RNBridge } from \"./bridges/RNBridge\";\nimport { BLUX_ATTRIBUTES } from \"./constants/BLUX_ATTRIBUTES\";\nimport { PAGE_VISIT_ENABLED_APPLICATION_IDS } from \"./constants/PAGE_VISIT_ENABLED_APPLICATION_IDS\";\nimport {\n AddClickEvent,\n AddPageVisitEvent,\n AddSectionViewEvent,\n VisitEvent,\n} from \"./events\";\nimport { BluxRecError } from \"./recs\";\nimport { isSameUrlIgnoringHash } from \"./utils/isSameUrlIgnoringHash\";\nimport { LocalStorage } from \"./utils/LocalStorage\";\nimport { Logger } from \"./utils/Logger\";\nimport { onlineAndVisible$ } from \"./utils/observables\";\nimport { filterNullable } from \"./utils/operators\";\nimport { SessionStorage } from \"./utils/SessionStorage\";\nimport { isObjectIdString } from \"./utils/zodSchemas\";\n\nimport type { Event as BluxEvent } from \"./events/Event\";\nimport type { EventRequest } from \"./events/types\";\nimport type { GetRecParams, RecResult } from \"./recs\";\nimport type { LogLevel } from \"./utils/Logger\";\nimport type { AxiosInstance } from \"axios\";\n\nexport type SdkInfo = {\n type: DevicePlatform;\n version: string;\n};\n\ntype BluxUser = {\n id: string;\n bluxAPIKey: string;\n applicationId: string;\n deviceId: string;\n userId?: string;\n};\n\ntype BridgeOptions =\n | {\n /**\n * @deprecated `bridgePlatform`을 사용하세요.\n */\n useRNBridge?: boolean;\n }\n | { bridgePlatform?: BridgePlatform };\n\ntype InappMessageResponse = NonNullable<\n APIs.bluxUsersCollectEventsV2.ResponseData[\"inapp\"]\n>;\n\ntype ApiRequestQueueJob<T> = {\n action: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (error: unknown) => void;\n};\n\ntype UserPropertiesQueueItem = {\n userProperties?: APIs.bluxUsersUpdateProperties.Body[\"user_properties\"];\n customUserProperties?: APIs.bluxUsersUpdateProperties.Body[\"custom_user_properties\"];\n resolve: () => void;\n reject: (error: unknown) => void;\n};\n\n/**\n * 인앱 메시지에서 BluxBridge.triggerAction() 호출 시 전달되는 이벤트\n */\nexport type InAppCustomActionEvent = {\n /** 액션 식별자 */\n actionId: string;\n /** 액션에 전달된 데이터 */\n data: Record<string, unknown>;\n};\n\n/**\n * 인앱 커스텀 액션 핸들러 타입\n */\nexport type InAppCustomActionHandler = (\n event: InAppCustomActionEvent,\n) => void | Promise<void>;\n\nexport type GetNotificationsResult = {\n notifications: {\n id: string;\n title: string;\n body: string;\n imageUrl?: string;\n url?: string;\n sentAt: string;\n }[];\n nextCursor?: string;\n};\n\nexport class BluxClient {\n private readonly api: AxiosInstance = axios.create();\n\n private readonly sdkInfo: SdkInfo = {\n type: DevicePlatform.browser,\n version: packageJson.version,\n };\n\n public readonly bluxUser$ = new BehaviorSubject<BluxUser | undefined>(\n undefined,\n );\n\n /**\n * 내부 async 작업을 1개씩 직렬로 처리하는 큐입니다.\n * `enqueueRequest()`로 넣으면, 호출자는 완료 시점까지 `await` 할 수 있습니다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public readonly apiRequestQueue$ = new Subject<ApiRequestQueueJob<any>>();\n public readonly inappQueue$ = new BehaviorSubject<\n InappMessageResponse | undefined\n >(undefined);\n public readonly eventQueue$ = new Subject<EventRequest>();\n public readonly userPropertiesQueue$ = new Subject<UserPropertiesQueueItem>();\n\n private readonly INITIAL_NEXT_POLL_DELAY_MS = 10 * 1000;\n private readonly nextPollDelayMs$ = new BehaviorSubject<number | null>(\n this.INITIAL_NEXT_POLL_DELAY_MS,\n );\n private cachedNextPollDelayMs: number | null =\n this.INITIAL_NEXT_POLL_DELAY_MS;\n private lastPollTimestamp: number = Date.now();\n\n private readonly bluxApplicationId: string;\n private readonly bluxAPIKey: string;\n private readonly customDeviceId?: string;\n\n private readonly bridge: Bridge | null;\n\n // Impression 관련 변수\n private impressedElements: Element[] = [];\n private intersectionObservingElements: Element[] = [];\n private readonly intersectionObserver = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n this.intersectionEntry$.next(entry);\n });\n },\n );\n\n private readonly intersectionEntry$ =\n new Subject<IntersectionObserverEntry>();\n\n // Hit 관련 변수\n private hitElements: Element[] = [];\n\n // 현재 표시 중인 인앱 메시지의 cleanup 함수\n private currentInappCleanup: (() => void) | null = null;\n\n // 인앱 커스텀 액션 핸들러 (복수 등록 가능)\n private inAppCustomActionHandlers: Set<InAppCustomActionHandler> = new Set();\n\n private resolveBridge(bridgeOptions: BridgeOptions): Bridge | null {\n if (\"useRNBridge\" in bridgeOptions && bridgeOptions.useRNBridge) {\n return new RNBridge();\n }\n\n if (\"bridgePlatform\" in bridgeOptions && bridgeOptions.bridgePlatform) {\n switch (bridgeOptions.bridgePlatform) {\n case \"none\":\n return null;\n case \"android\":\n return new AndroidBridge();\n case \"ios\":\n return new IosBridge();\n case \"react-native\":\n return new RNBridge();\n case \"flutter\":\n return new FlutterBridge();\n default: {\n const exhaustiveCheck: never = bridgeOptions.bridgePlatform;\n Logger.error(`Unknown bridge platform: ${exhaustiveCheck}`, {\n tags: { from: \"resolveBridge\" },\n });\n }\n }\n }\n\n return null;\n }\n\n constructor(\n {\n bluxApplicationId,\n bluxAPIKey,\n customDeviceId,\n ...bridgeOptions\n }: {\n bluxApplicationId: string;\n bluxAPIKey: string;\n customDeviceId?: string;\n } & BridgeOptions,\n completionCallback?: (args: {\n errorMessage?: string;\n success: boolean;\n }) => void,\n ) {\n this.bluxApplicationId = bluxApplicationId;\n this.bluxAPIKey = bluxAPIKey;\n this.customDeviceId = customDeviceId;\n\n const baseUrl = window?._BLUX_SDK_?.baseUrl ?? \"https://api.blux.ai/prod\";\n this.api.defaults.baseURL = baseUrl;\n\n this.bridge = this.resolveBridge(bridgeOptions);\n\n if (typeof window !== \"undefined\") {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).__bluxClient = this;\n }\n\n // api 요청 항상 하나씩 처리\n this.apiRequestQueue$\n .pipe(\n concatMap(async (job) => {\n try {\n const result = await job.action();\n job.resolve(result);\n } catch (error) {\n job.reject(error);\n }\n }),\n )\n .subscribe();\n\n /**\n * (1) 보내야할 이벤트를 100ms씩 뭉쳐서 보내고, 1개 이상 쌓여있을 때 api 요청\n * (2) collectEvent의 응답에서 온 nextPollDelayMs 값 만큼 이후에 빈 배열로 api 요청 (1)로 보내면 초기화됨\n */\n\n if (!this.bridge) {\n merge(\n this.eventQueue$.pipe(\n bufferTime(100),\n filter((requests) => requests.length > 0),\n ),\n // onlineAndVisible일 때만 polling, 남은 시간 계산하여 타이머 시작\n combineLatest([this.nextPollDelayMs$, onlineAndVisible$]).pipe(\n switchMap(([delay, isOnlineAndVisible]) => {\n if (!isOnlineAndVisible || delay == null) return NEVER;\n\n const targetTime = this.lastPollTimestamp + delay;\n const remainingDelay = Math.max(0, targetTime - Date.now());\n\n return timer(remainingDelay);\n }),\n map(() => [] as EventRequest[]),\n ),\n )\n .pipe(\n concatMap(async (requests) => {\n this.nextPollDelayMs$.next(null);\n try {\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const events = requests.map((request) => ({\n ...request,\n blux_id: bluxUser.id,\n device_id: bluxUser.deviceId,\n }));\n\n const {\n data: { nextPollDelayMs, inapp },\n } = await this.enqueueRequest(() =>\n collectEvent(\n this.api,\n {\n blux_user_id: bluxUser.id,\n application_id: bluxUser.applicationId,\n },\n {\n device_id: bluxUser.deviceId,\n events,\n },\n this.generateApiHeader(bluxUser),\n ),\n );\n\n this.lastPollTimestamp = Date.now();\n this.cachedNextPollDelayMs = nextPollDelayMs;\n this.nextPollDelayMs$.next(Math.max(nextPollDelayMs, 3000));\n\n if (inapp) {\n this.inappQueue$.next(inapp);\n }\n } catch (error) {\n Logger.error(error, { tags: { from: \"eventQueue\" } });\n this.lastPollTimestamp = Date.now();\n if (this.cachedNextPollDelayMs !== null) {\n const nextPollDelay =\n this.cachedNextPollDelayMs > 1000 * 60 * 60 * 24\n ? this.cachedNextPollDelayMs\n : this.cachedNextPollDelayMs * 2;\n this.cachedNextPollDelayMs = nextPollDelay;\n this.nextPollDelayMs$.next(nextPollDelay);\n }\n }\n }),\n )\n .subscribe();\n }\n\n // api 호출 수 절감을 위해 100ms씩 뭉쳐서 보내고, custom_user_properties와 user_properties를 합쳐서 보냄\n this.userPropertiesQueue$\n .pipe(\n bufferTime(100),\n filter((items) => items.length > 0),\n concatMap(async (items) => {\n try {\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const mergedUserProperties = items.reduce(\n (acc, item) => ({ ...acc, ...(item.userProperties ?? {}) }),\n {} as NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"user_properties\"]\n >,\n );\n\n const mergedCustomUserProperties = items.reduce(\n (acc, item) => ({ ...acc, ...(item.customUserProperties ?? {}) }),\n {} as NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"custom_user_properties\"]\n >,\n );\n\n const payload: APIs.bluxUsersUpdateProperties.Body = {};\n if (Object.keys(mergedUserProperties).length > 0) {\n payload.user_properties = mergedUserProperties;\n }\n if (Object.keys(mergedCustomUserProperties).length > 0) {\n payload.custom_user_properties = mergedCustomUserProperties;\n }\n\n if (Object.keys(payload).length === 0) {\n items.forEach((item) => item.resolve());\n return;\n }\n\n await this.enqueueRequest(() =>\n updateProperties(\n this.api,\n {\n application_id: bluxUser.applicationId,\n blux_user_id: bluxUser.id,\n },\n payload,\n this.generateApiHeader(bluxUser),\n ),\n );\n\n items.forEach((item) => item.resolve());\n } catch (error) {\n items.forEach((item) => item.reject(error));\n Logger.error(error, { tags: { from: \"userPropertiesQueue\" } });\n }\n }),\n )\n .subscribe();\n\n this.bluxUser$.subscribe((bluxUser) => {\n if (bluxUser) SessionStorage.setBluxUserId(bluxUser.id);\n });\n\n if (!this.bridge) {\n this.inappQueue$\n .pipe(filterNullable())\n .pipe(\n concatMap((inapp) =>\n onlineAndVisible$.pipe(\n first(Boolean),\n concatMap(() => from(this.displayInapp(inapp))),\n ),\n ),\n )\n .subscribe();\n }\n\n this.init()\n .then(() => {\n completionCallback?.({ success: true });\n })\n .catch((error) => {\n completionCallback?.({ success: false, errorMessage: error.message });\n });\n }\n\n private async enqueueRequest<T>(action: () => Promise<T>) {\n return new Promise<T>((resolve, reject) => {\n const item: ApiRequestQueueJob<T> = { action, resolve, reject };\n this.apiRequestQueue$.next(item);\n });\n }\n\n private generateApiHeader({ bluxAPIKey }: { bluxAPIKey: string }) {\n return {\n Authorization: bluxAPIKey,\n };\n }\n\n // blux_user_id가 누락된 서버 응답으로 bluxUser$가 오염되면 이후 모든 요청 경로가\n // {blux_user_id} 치환 실패로 깨지므로, 유효한 id가 아니면 상태를 갱신하지 않는다.\n private setBluxUser(bluxUser: BluxUser) {\n if (!isObjectIdString(bluxUser.id)) {\n Logger.warning(\"Skipped bluxUser update with invalid id\", {\n extra: { id: bluxUser.id },\n });\n return;\n }\n\n this.bluxUser$.next(bluxUser);\n }\n\n private async displayInapp(inapp: InappMessageResponse) {\n return new Promise((resolve) => {\n const bluxUser = this.bluxUser$.getValue();\n\n const hideTimestamp = LocalStorage.getInappHideTimestamp(inapp.inappId);\n const isShouldSkip = hideTimestamp !== null && Date.now() < hideTimestamp;\n\n if (isShouldSkip) return resolve(true);\n\n Logger.debug(`[inapp:${inapp.inappId}] displayInapp started`, {\n extra: { htmlLength: inapp.htmlString.length, baseUrl: inapp.baseUrl },\n });\n\n const iframeElement = document.createElement(\"iframe\");\n\n // <base> 태그로 외부 리소스 경로 설정\n // history.replaceState/pushState 에러 방지를 위한 polyfill 추가\n const historyPolyfill = `<script>\n (function() {\n var origReplaceState = history.replaceState;\n var origPushState = history.pushState;\n history.replaceState = function() {\n try { return origReplaceState.apply(this, arguments); }\n catch(e) { console.debug('[Blux] history.replaceState blocked'); }\n };\n history.pushState = function() {\n try { return origPushState.apply(this, arguments); }\n catch(e) { console.debug('[Blux] history.pushState blocked'); }\n };\n })();\n </script>`;\n const headInjection = `${historyPolyfill}<base href=\"${inapp.baseUrl}/\">`;\n const htmlWithBase = inapp.htmlString.includes(\"<head>\")\n ? inapp.htmlString.replace(\"<head>\", `<head>${headInjection}`)\n : inapp.htmlString;\n\n iframeElement.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-forms\",\n );\n iframeElement.style.position = \"fixed\";\n iframeElement.style.width = `${window.innerWidth}px`;\n iframeElement.style.height = `${window.innerHeight}px`;\n iframeElement.style.top = \"0\";\n iframeElement.style.left = \"0\";\n iframeElement.style.border = \"none\";\n iframeElement.style.pointerEvents = \"none\";\n\n iframeElement.style.zIndex = \"9999\";\n iframeElement.id = `blux-inapp-${inapp.inappId}`;\n\n document.body.appendChild(iframeElement);\n\n const iframeDocument = iframeElement.contentDocument;\n if (iframeDocument) {\n iframeDocument.open();\n iframeDocument.write(htmlWithBase);\n iframeDocument.close();\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] iframe content written with document.write`,\n );\n } else {\n Logger.error(\n `[inapp:${inapp.inappId}] iframe document is not accessible for document.write`,\n );\n }\n\n let cleanupTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n iframeElement.addEventListener(\"load\", () => {\n Logger.debug(`[inapp:${inapp.inappId}] iframe load event fired`);\n try {\n const doc = iframeElement.contentDocument;\n const bodyLength = doc?.body?.innerHTML?.length ?? 0;\n Logger.debug(\n `[inapp:${inapp.inappId}] contentDocument accessible: ${!!doc}, bodyLength: ${bodyLength}`,\n );\n if (doc === null) {\n // cross-origin으로 contentDocument 접근 차단 (일부 WebView는 throw 대신 null 반환)\n // load 이벤트는 발화했으므로 콘텐츠는 로드됨 → 활성화\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] contentDocument is null (cross-origin), pointer-events enabled`,\n );\n } else if (bodyLength > 0) {\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] pointer-events enabled via load event`,\n );\n } else {\n Logger.warning(\n `[inapp:${inapp.inappId}] iframe loaded but body is empty, cleaning up`,\n );\n cleanup();\n }\n } catch (e) {\n // contentDocument 접근 불가 (cross-origin 등) → 콘텐츠가 로딩은 됐으므로 활성화\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] contentDocument access blocked, pointer-events enabled`,\n { extra: { error: e } },\n );\n }\n });\n\n iframeElement.addEventListener(\"error\", (e) => {\n Logger.warning(`[inapp:${inapp.inappId}] iframe error event`, {\n extra: { error: e },\n });\n cleanup();\n });\n\n // iframe load 이벤트가 발생하지 않는 경우를 대비한 fallback\n const INAPP_IFRAME_TIMEOUT_MS = 10_000;\n cleanupTimeoutId = setTimeout(() => {\n if (iframeElement.style.pointerEvents === \"none\") {\n Logger.warning(\n `[inapp:${inapp.inappId}] iframe timeout - no load event within ${INAPP_IFRAME_TIMEOUT_MS}ms, cleaning up`,\n );\n cleanup();\n }\n }, INAPP_IFRAME_TIMEOUT_MS);\n\n let urlChangeSubscription: Subscription | null = null;\n\n const cleanup = () => {\n Logger.debug(`[inapp:${inapp.inappId}] cleanup called`);\n if (cleanupTimeoutId !== null) {\n clearTimeout(cleanupTimeoutId);\n cleanupTimeoutId = null;\n }\n iframeElement.remove();\n window.removeEventListener(\"message\", handleMessage);\n window.visualViewport?.removeEventListener(\n \"resize\",\n handleViewportChange,\n );\n window.visualViewport?.removeEventListener(\n \"scroll\",\n handleViewportChange,\n );\n urlChangeSubscription?.unsubscribe();\n this.currentInappCleanup = null;\n resolve(inapp.inappId);\n };\n\n // 현재 인앱 cleanup 함수 저장\n this.currentInappCleanup = cleanup;\n const handleViewportChange = () => {\n const width = window.visualViewport?.width ?? window.innerWidth;\n const height = window.visualViewport?.height ?? window.innerHeight;\n const left = window.visualViewport?.offsetLeft ?? 0;\n const top = window.visualViewport?.offsetTop ?? 0;\n\n iframeElement.style.width = `${width}px`;\n iframeElement.style.height = `${height}px`;\n iframeElement.style.left = `${left}px`;\n iframeElement.style.top = `${top}px`;\n };\n\n // cleanup을 self postMessage로 지연시키는 자가 신호. inapp_opened(iframe→parent)와\n // 동일 posted-message task source라 큐잉된 inapp_opened가 cleanup보다 먼저 처리된다.\n const INAPP_CLEANUP_SIGNAL = \"__blux_inapp_cleanup\" as const;\n\n const handleMessage = (\n event: MessageEvent<\n | {\n action: \"hide\";\n data: {\n days_to_hide: number;\n };\n }\n | {\n action: \"link\";\n data:\n | {\n is_blux_landing: false;\n url: string;\n open_in_new_tab: boolean;\n /**\n * Safari 팝업 차단 우회를 위해 iframe에서 직접 링크를 연 경우 true\n * 이 플래그가 true면 window.open을 스킵하고 이벤트만 찍음\n */\n opened_by_iframe?: boolean;\n }\n | {\n is_blux_landing: true;\n landing_id: string;\n };\n }\n | {\n action: \"resize\";\n data:\n | {\n height: number;\n location: \"top\" | \"bottom\";\n }\n | {\n width?: number;\n height?: number;\n top?: number;\n bottom?: number;\n left?: number;\n right?: number;\n };\n }\n | {\n action: \"custom_action\";\n data: {\n action_id: string;\n data: Record<string, unknown>;\n should_dismiss: boolean;\n };\n }\n | {\n action: \"inapp_opened\";\n data: {\n button_id: string;\n };\n }\n | {\n action: typeof INAPP_CLEANUP_SIGNAL;\n inappId: string;\n }\n >,\n ) => {\n if (event.source !== iframeElement.contentWindow) {\n // self cleanup 신호(source === window)만 처리. 그 외 발신자 메시지는 무시.\n if (\n event.source === window &&\n event.data?.action === INAPP_CLEANUP_SIGNAL &&\n event.data.inappId === inapp.inappId\n ) {\n cleanup();\n }\n return;\n }\n\n const message = event.data;\n\n if (message.action === \"resize\") {\n const data = message.data;\n\n if (\"location\" in data) {\n // 기존 배너용 resize (location 기반)\n iframeElement.style.width = \"90vw\";\n iframeElement.style.maxWidth = \"448px\";\n iframeElement.style.height = `${data.height}px`;\n iframeElement.style.left = \"50%\";\n iframeElement.style.transform = \"translateX(-50%)\";\n if (data.location === \"top\") {\n iframeElement.style.top = \"0\";\n iframeElement.style.bottom = \"auto\";\n } else {\n iframeElement.style.top = \"auto\";\n iframeElement.style.bottom = \"0\";\n }\n } else {\n // Custom HTML 절대 위치 지정\n if (data.width !== undefined) {\n iframeElement.style.width = `${data.width}px`;\n iframeElement.style.maxWidth = \"none\";\n }\n if (data.height !== undefined) {\n iframeElement.style.height = `${data.height}px`;\n }\n iframeElement.style.transform = \"none\";\n iframeElement.style.top =\n data.top !== undefined ? `${data.top}px` : \"auto\";\n iframeElement.style.bottom =\n data.bottom !== undefined ? `${data.bottom}px` : \"auto\";\n iframeElement.style.left =\n data.left !== undefined ? `${data.left}px` : \"auto\";\n iframeElement.style.right =\n data.right !== undefined ? `${data.right}px` : \"auto\";\n }\n\n window.visualViewport?.removeEventListener(\n \"resize\",\n handleViewportChange,\n );\n window.visualViewport?.removeEventListener(\n \"scroll\",\n handleViewportChange,\n );\n return; // resize는 cleanup하지 않음\n }\n\n if (message.action === \"hide\") {\n const { days_to_hide } = message.data;\n if (days_to_hide !== 0) {\n const expiresAt = Date.now() + days_to_hide * 24 * 60 * 60 * 1000;\n LocalStorage.setInappHideTimestamp(inapp.inappId, expiresAt);\n }\n } else if (message.action === \"link\") {\n // createCrmEvent 호출\n const crmEventPromise = bluxUser\n ? this.enqueueRequest(() =>\n createCrmEvent(\n this.api,\n {\n application_id: this.bluxApplicationId,\n },\n {\n notification_id: inapp.notificationId,\n crm_event_type: \"inapp_opened\",\n captured_at: new Date().toISOString(),\n },\n this.generateApiHeader(bluxUser),\n ),\n )\n : Promise.resolve();\n\n if (message.data.is_blux_landing) {\n // TODO: @all 스키마상에는 개인화 랜딩이 존재하나, 콘솔에는 필드가 없음 -> 추후 이에 따른 핸들링이 필요함\n } else {\n const { url, open_in_new_tab, opened_by_iframe } = message.data;\n const needsLocationChange = opened_by_iframe && !open_in_new_tab;\n\n // 하고하우스 특별 처리\n if (this.bluxApplicationId === \"6878af275ead5a75abbdacd7\") {\n crmEventPromise.finally(() => {\n window.top?.postMessage(\n {\n type: \"blux_inapp_message\",\n url,\n },\n window.location.origin,\n );\n });\n } else if (needsLocationChange) {\n // opened_by_iframe이고 현재 탭 이동인 경우: createCrmEvent 완료 후 이동\n crmEventPromise.finally(() => {\n window.location.href = url;\n });\n } else if (!opened_by_iframe) {\n // 기존 방식 (iframe에서 열지 않은 경우)\n if (open_in_new_tab) {\n window.open(url, \"_blank\");\n } else {\n window.location.href = url;\n }\n }\n // opened_by_iframe && open_in_new_tab인 경우: iframe에서 이미 새 탭 열었으므로 스킵\n }\n } else if (message.action === \"custom_action\") {\n const { action_id, data, should_dismiss } = message.data;\n\n // 등록된 모든 핸들러 호출\n for (const handler of this.inAppCustomActionHandlers) {\n try {\n handler({ actionId: action_id, data });\n } catch (error) {\n Logger.error(error, {\n tags: { from: \"inAppCustomActionHandler\" },\n });\n }\n }\n\n // should_dismiss가 false면 cleanup하지 않음\n if (!should_dismiss) {\n return;\n }\n } else if (message.action === \"inapp_opened\") {\n // data-blux-click 속성으로 인한 클릭 이벤트 추적\n if (bluxUser) {\n this.enqueueRequest(() =>\n createCrmEvent(\n this.api,\n {\n application_id: this.bluxApplicationId,\n },\n {\n notification_id: inapp.notificationId,\n crm_event_type: \"inapp_opened\",\n captured_at: new Date().toISOString(),\n },\n this.generateApiHeader(bluxUser),\n ),\n );\n }\n return; // 인앱메시지를 닫지 않음\n } else {\n // 인앱메시지가 보내지 않는 action은 무시한다. iOS Chrome은 모든 iframe에 스크립트를 넣어\n // 부모 창으로 registerAsChildFrameAck를 보내는데, 여기서 무시하지 않으면 아래 cleanup으로\n // 넘어가 인앱메시지가 뜨자마자 닫힌다.\n return;\n }\n\n // cleanup을 직접 호출하지 않고 self postMessage로 지연 (INAPP_CLEANUP_SIGNAL 분기에서 처리)\n window.postMessage(\n { action: INAPP_CLEANUP_SIGNAL, inappId: inapp.inappId },\n \"/\",\n );\n };\n\n // 페이지 이동 시 인앱메시지 닫기 (SPA 뒤로가기/앞으로가기 대응)\n urlChangeSubscription = this.pageTransition$\n .pipe(skip(1), first())\n .subscribe(() => {\n cleanup();\n });\n\n window.visualViewport?.addEventListener(\"resize\", handleViewportChange);\n window.visualViewport?.addEventListener(\"scroll\", handleViewportChange);\n window.addEventListener(\"message\", handleMessage);\n\n if (bluxUser) {\n this.enqueueRequest(() =>\n notificationsUpdate(\n this.api,\n {\n application_id: this.bluxApplicationId,\n notification_id: inapp.notificationId,\n },\n {\n status: \"received\",\n },\n this.generateApiHeader(bluxUser),\n ),\n );\n }\n });\n }\n\n private async init() {\n this.handleUrlAndRef();\n // handle page visit event\n if (PAGE_VISIT_ENABLED_APPLICATION_IDS.has(this.bluxApplicationId)) {\n this.pageTransition$.subscribe(() => {\n this.sendEvent(new AddPageVisitEvent({}));\n });\n }\n\n // 노출·클릭 성과는 sendEvent를 거치므로 브릿지 모드에서도 네이티브 SDK로 전달된다.\n this.registerHitTracker();\n\n // 페이지 이동 시 추천 지표 수집 관련 변수 초기화\n this.pageTransition$.subscribe(() => {\n this.impressedElements = [];\n });\n\n this.registerImpressionTracker();\n new MutationObserver(() => {\n this.registerImpressionTracker();\n }).observe(document, { subtree: true, childList: true });\n\n // 500ms 연속으로 엘리먼트 들어있으면 추천 지표 수집\n const MIN_IMPRESSION_TIME = 500;\n this.intersectionEntry$\n .pipe(\n groupBy((entry) => entry.target),\n mergeMap((group$) =>\n group$.pipe(\n debounceTime(MIN_IMPRESSION_TIME),\n filter((entry) => entry.isIntersecting),\n ),\n ),\n )\n .subscribe((entry) => {\n const trackingId = entry.target.getAttribute(\n BLUX_ATTRIBUTES.tracking_id,\n );\n if (!trackingId) {\n Logger.warning(\n `Tracking ID is not found in the parent elements. Please check the tracking ID(${BLUX_ATTRIBUTES.tracking_id}) is set correctly.`,\n );\n return;\n }\n if (this.impressedElements.includes(entry.target)) return;\n\n this.sendEvent(\n new AddSectionViewEvent({\n section: trackingId,\n tracking: {\n id: trackingId,\n type: \"impression\",\n },\n }),\n );\n this.impressedElements.push(entry.target);\n });\n\n if (this.bridge) {\n this.bridge.initialize({\n bluxApplicationId: this.bluxApplicationId,\n bluxAPIKey: this.bluxAPIKey,\n customDeviceId: this.customDeviceId,\n });\n return;\n }\n\n const blux_user_id = new URLSearchParams(window.location.search).get(\n \"blux_user_id\",\n );\n const sessionId =\n SessionStorage.getSessionId() ?? new ObjectId().toHexString();\n SessionStorage.setSessionId(sessionId);\n\n const isBluxUserIdExistInSessionStorage = SessionStorage.getBluxUserId();\n if (!isBluxUserIdExistInSessionStorage) this.sendEvent(new VisitEvent({}));\n\n Logger.debug(`Init with application id: ${this.bluxApplicationId}`);\n\n const deviceId = LocalStorage.getDeviceId();\n\n if (!deviceId) {\n Logger.debug(\"No saved Device ID, register a new one.\");\n }\n\n const { data } = await initialize(\n this.api,\n { application_id: this.bluxApplicationId },\n {\n session_id: sessionId ?? undefined,\n device_id: deviceId ?? undefined,\n country_code: navigator.language.split(\"-\")[1],\n device_model: navigator.userAgent,\n language_code: navigator.language.split(\"-\")[0],\n platform: DevicePlatform.browser,\n os_version: navigator.userAgent,\n sdk_version: this.sdkInfo.version,\n sdk_type: this.sdkInfo.type,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n blux_user_id: blux_user_id ?? undefined,\n custom_device_id: this.customDeviceId,\n },\n this.generateApiHeader({\n bluxAPIKey: this.bluxAPIKey,\n }),\n );\n\n if (data.device_id) LocalStorage.setDeviceId(data.device_id);\n\n this.setBluxUser({\n id: data.blux_user_id,\n bluxAPIKey: this.bluxAPIKey,\n applicationId: this.bluxApplicationId,\n deviceId: data.device_id,\n });\n }\n\n public setLogLevel(logLevel: LogLevel) {\n Logger.setLevel(logLevel);\n }\n\n /**\n * 요청에 쓸 BluxUser를 5초까지 기다린다.\n *\n * 브릿지 모드에서는 유저를 아는 쪽이 네이티브 SDK 하나다. 앱이 직접 부른 signIn·signOut을\n * 웹이 알 수 없으므로 값을 기억하지 않고 호출마다 묻는다. 받은 값은 `bluxUser$`에 넣지 않는다.\n */\n private async getBluxUserWithTimeout() {\n const bluxUser$ = this.bridge\n ? this.pollBridgeBluxUser(this.bridge)\n : this.bluxUser$;\n const timeoutMessage = this.bridge\n ? \"BluxUser was not received from the native SDK. Check the native SDK version and the bridge setup.\"\n : \"Application ID is not initialized. Please initialize BluxClient.\";\n\n const bluxUser = await firstValueFrom(\n bluxUser$.pipe(\n filterNullable(),\n timeout(5000),\n catchError((error) => {\n return throwError(() =>\n error.name === \"TimeoutError\" ? new Error(timeoutMessage) : error,\n );\n }),\n ),\n );\n\n return bluxUser;\n }\n\n /**\n * 네이티브 SDK에 BluxUser를 묻고, 못 받으면 앞 호출이 끝나고 500ms 뒤에 다시 묻는다.\n * 그래서 응답이 느려도 호출이 겹치지 않는다.\n */\n private pollBridgeBluxUser(bridge: Bridge) {\n const RETRY_DELAY_MS = 500;\n return defer(() => bridge.getBluxUser()).pipe(\n map(\n (payload): BluxUser | undefined =>\n payload && {\n id: payload.bluxUserId,\n deviceId: payload.deviceId,\n applicationId: this.bluxApplicationId,\n bluxAPIKey: this.bluxAPIKey,\n },\n ),\n repeat({ delay: RETRY_DELAY_MS }),\n );\n }\n\n public async signIn({ userId }: { userId: string }) {\n try {\n if (this.bridge) {\n this.bridge.signIn({ userId });\n return;\n }\n\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const {\n data: { blux_user_id: bluxIdInResponse },\n } = await this.enqueueRequest(() =>\n signIn(\n this.api,\n { application_id: bluxUser.applicationId, blux_user_id: bluxUser.id },\n { user_id: userId, device_id: bluxUser.deviceId },\n this.generateApiHeader(bluxUser),\n ),\n );\n\n if (bluxIdInResponse !== bluxUser.id) {\n Logger.debug(\"Blux id is changed in signIn.\");\n this.setBluxUser({\n ...bluxUser,\n id: bluxIdInResponse,\n userId,\n });\n }\n } catch (error) {\n Logger.error(error, { tags: { from: \"signIn\" } });\n }\n }\n\n public async signOut() {\n try {\n if (this.bridge) {\n this.bridge.signOut();\n return;\n }\n\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const {\n data: { blux_user_id: bluxIdInResponse },\n } = await this.enqueueRequest(() =>\n signOut(\n this.api,\n { application_id: bluxUser.applicationId, blux_user_id: bluxUser.id },\n { device_id: bluxUser.deviceId },\n this.generateApiHeader(bluxUser),\n ),\n );\n\n Logger.debug(\"Blux id is changed in signOut.\");\n this.setBluxUser({\n ...bluxUser,\n id: bluxIdInResponse,\n userId: undefined,\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"signOut\" } });\n }\n }\n\n public async setUserProperties({\n userProperties,\n }: {\n userProperties: NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"user_properties\"]\n >;\n }) {\n try {\n if (this.bridge) {\n this.bridge.setUserProperties(userProperties);\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n this.userPropertiesQueue$.next({\n userProperties,\n resolve,\n reject,\n });\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"setUserProperties\" } });\n }\n }\n\n public async setCustomUserProperties({\n customUserProperties,\n }: {\n customUserProperties: NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"custom_user_properties\"]\n >;\n }) {\n try {\n if (this.bridge) {\n this.bridge.setCustomUserProperties(customUserProperties);\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n this.userPropertiesQueue$.next({\n customUserProperties,\n resolve,\n reject,\n });\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"setCustomUserProperties\" } });\n }\n }\n\n public sendEvent(event: BluxEvent | BluxEvent[]) {\n try {\n const requests = (Array.isArray(event) ? event : [event]).map((e) => ({\n ...e.request,\n internal_event_properties: {\n url: this.urlAndRef$.value?.url,\n ref: this.urlAndRef$.value?.ref,\n },\n }));\n\n if (this.bridge) {\n this.bridge.sendEvent({ requests });\n return;\n }\n\n requests.forEach((request) => {\n this.eventQueue$.next(request);\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"sendEvent\" } });\n }\n }\n\n /**\n * 현재 표시 중인 인앱 메시지를 프로그래밍 방식으로 닫습니다.\n *\n * Custom HTML 인앱에서 async 핸들러 완료 후 수동으로 닫을 때 사용합니다.\n *\n * @example\n * ```typescript\n * // BluxBridge.triggerAction('share', { url: '...' }, false); // shouldDismiss: false\n * // 핸들러에서 async 작업 완료 후:\n * bluxClient.dismissInApp();\n * ```\n */\n public dismissInApp() {\n this.currentInappCleanup?.();\n }\n\n /**\n * Custom HTML 인앱에서 BluxBridge.triggerAction() 호출 시 실행될 핸들러를 등록합니다.\n * 여러 핸들러를 등록할 수 있으며, 등록 순서대로 실행됩니다.\n *\n * @param handler - 커스텀 액션 핸들러 함수\n * @returns 핸들러를 제거하는 unsubscribe 함수\n *\n * @example\n * ```typescript\n * const unsubscribe = bluxClient.addInAppCustomActionHandler(async (event) => {\n * if (event.actionId === 'share') {\n * await navigator.share({ url: event.data.url as string });\n * // async 작업 완료 후 수동으로 닫기 (shouldDismiss: false로 호출한 경우)\n * bluxClient.dismissInApp();\n * }\n * });\n *\n * // 핸들러 제거\n * unsubscribe();\n * ```\n */\n public addInAppCustomActionHandler(\n handler: InAppCustomActionHandler,\n ): () => void {\n this.inAppCustomActionHandlers.add(handler);\n return () => {\n this.inAppCustomActionHandlers.delete(handler);\n };\n }\n\n private urlAndRef$ = new BehaviorSubject<\n { url: string; ref: string | undefined } | undefined\n >(undefined);\n\n private readonly pageTransition$ = this.urlAndRef$.pipe(\n filterNullable(),\n distinctUntilChanged((previous, current) =>\n isSameUrlIgnoringHash(previous.url, current.url),\n ),\n );\n\n private handleUrlAndRef() {\n this.urlAndRef$.pipe(filterNullable()).subscribe(({ url }) => {\n LocalStorage.setUrl(url);\n });\n // init url and ref from local storage\n this.urlAndRef$.next({\n url: window.location.href,\n ref: LocalStorage.getUrl() ?? undefined,\n });\n\n // observe url change\n const handleUrlChange = () => {\n const prevUrl = this.urlAndRef$.value?.url;\n const currentUrl = window.location.href;\n if (prevUrl !== currentUrl) {\n this.urlAndRef$.next({\n url: currentUrl,\n ref: prevUrl ?? undefined,\n });\n }\n };\n\n // SPA: popstate (뒤로가기/앞으로가기)\n window.addEventListener(\"popstate\", handleUrlChange);\n // 현재 브라우저에는 URL상태를 추적하기 위한 리스너가 존재하지 않습니다.\n // 그래서 pushState와 replaceState를 직접 수정하여 추적합니다.\n // @reference https://stackoverflow.com/a/64927639\n // @reference https://github.com/amplitude/Amplitude-TypeScript/blob/main/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts\n // SPA: pushState/replaceState proxy\n history.pushState = new Proxy(history.pushState, {\n apply: (target, thisArg, [state, unused, url]) => {\n target.apply(thisArg, [state, unused, url]);\n handleUrlChange();\n },\n });\n history.replaceState = new Proxy(history.replaceState, {\n apply: (target, thisArg, [state, unused, url]) => {\n target.apply(thisArg, [state, unused, url]);\n handleUrlChange();\n },\n });\n }\n\n private registerImpressionTracker() {\n const targetElements = Array.from(\n document.querySelectorAll(`[${BLUX_ATTRIBUTES.tracking_id}]`),\n );\n\n const removed = this.intersectionObservingElements.filter(\n (e) => !targetElements.includes(e),\n );\n\n removed.forEach((e) => {\n this.intersectionObserver.unobserve(e);\n });\n\n const added = targetElements.filter(\n (element) => !this.intersectionObservingElements.includes(element),\n );\n\n added.forEach((element) => {\n this.intersectionObserver.observe(element);\n });\n\n this.intersectionObservingElements = targetElements;\n }\n\n private registerHitTracker() {\n const handleDelegatedClick = (event: Event) => {\n if (!(event.target instanceof Element)) return;\n\n const eventTarget = event.target;\n\n const targetItemElement = eventTarget.closest(\n `[${BLUX_ATTRIBUTES.item_id}]`,\n );\n if (!targetItemElement) return;\n\n const itemId = targetItemElement.getAttribute(BLUX_ATTRIBUTES.item_id);\n if (!itemId) return;\n\n const targetSectionElement = eventTarget.closest(\n `[${BLUX_ATTRIBUTES.tracking_id}]`,\n );\n const trackingId = targetSectionElement?.getAttribute(\n BLUX_ATTRIBUTES.tracking_id,\n );\n if (!trackingId) {\n Logger.warning(\n `Tracking ID is not found in the parent elements. Please check the tracking ID(${BLUX_ATTRIBUTES.tracking_id}) is set correctly.`,\n );\n return;\n }\n\n if (!this.hitElements.includes(targetItemElement)) {\n this.sendEvent(\n new AddClickEvent({\n itemId,\n tracking: {\n id: trackingId,\n type: \"hit\",\n },\n }),\n );\n this.hitElements.push(targetItemElement);\n }\n };\n document.addEventListener(\"click\", handleDelegatedClick);\n this.pageTransition$.subscribe(() => {\n this.hitElements = [];\n });\n }\n\n /**\n * 추천 슬롯을 호출해 추천 상품을 받아온다.\n *\n * 슬롯이 요구하는 입력만 넘긴다 — 호출 시 상품을 받는 슬롯은 `itemIds`, 카테고리·브랜드 조건을\n * 요청 값으로 받는 슬롯은 `categoryId`·`brandId`. 필요한 입력이 빠지면 서버가 거절한다.\n *\n * @throws {BluxRecError} 슬롯이 없거나 비활성이거나, 필수 입력이 빠졌거나, 네트워크 오류이거나, 클라이언트 초기화가 5초 안에 끝나지 않은 경우\n */\n public async getRec({\n slotKey,\n limit,\n offset,\n itemIds,\n categoryId,\n brandId,\n }: GetRecParams): Promise<RecResult> {\n try {\n const bluxUser = await this.getBluxUserWithTimeout();\n const { data } = await getItemRecommendations(\n this.api,\n {\n application_id: bluxUser.applicationId,\n blux_user_id: bluxUser.id,\n },\n {\n call_type: slotKey,\n limit,\n offset,\n item_ids: itemIds,\n category_id: categoryId,\n brand_id: brandId,\n },\n this.generateApiHeader(bluxUser),\n );\n\n return {\n itemIds: data.item_ids,\n metadata: data.metadata,\n };\n } catch (error) {\n throw BluxRecError.from(error);\n }\n }\n\n public async getNotifications(\n params: {\n limit?: number;\n fromSentAt?: string | Date;\n toSentAt?: string | Date;\n cursor?: string;\n } = {},\n ): Promise<GetNotificationsResult> {\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const toIso = (value: string | Date | undefined): string | undefined => {\n if (!value) return undefined;\n return value instanceof Date ? value.toISOString() : value;\n };\n\n const fromSentAtIso = toIso(params.fromSentAt);\n const toSentAtIso = toIso(params.toSentAt);\n const query: APIs.notificationsReadList.Query = {\n ...(params.limit !== undefined ? { limit: params.limit } : {}),\n ...(fromSentAtIso ? { from_sent_at: fromSentAtIso } : {}),\n ...(toSentAtIso ? { to_sent_at: toSentAtIso } : {}),\n ...(params.cursor ? { cursor: params.cursor } : {}),\n };\n\n const response = await getNotifications(\n this.api,\n {\n application_id: bluxUser.applicationId,\n blux_user_id: bluxUser.id,\n },\n query,\n this.generateApiHeader(bluxUser),\n );\n\n return {\n notifications: response.data.notifications.map((n) => ({\n id: n.id,\n title: n.title,\n body: n.body,\n imageUrl: n.image_url,\n url: n.url,\n sentAt: n.sent_at,\n })),\n nextCursor: response.data.next_cursor,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BluxClient.js","names":["packageJson.version"],"sources":["../src/BluxClient.ts"],"sourcesContent":["import axios from \"axios\";\nimport ObjectId from \"bson-objectid\";\nimport {\n BehaviorSubject,\n bufferTime,\n catchError,\n combineLatest,\n concatMap,\n debounceTime,\n defer,\n distinctUntilChanged,\n filter,\n first,\n firstValueFrom,\n from,\n groupBy,\n map,\n merge,\n mergeMap,\n NEVER,\n repeat,\n skip,\n Subject,\n Subscription,\n switchMap,\n throwError,\n timeout,\n timer,\n} from \"rxjs\";\n\nimport packageJson from \"../package.json\";\nimport { DevicePlatform, type APIs } from \"./apis/APIs\";\nimport { createCrmEvent } from \"./apis/createCrmEvent\";\nimport { collectEvent } from \"./apis/createEvent\";\nimport { getItemRecommendations } from \"./apis/getItemRecommendations\";\nimport { getNotifications } from \"./apis/getNotifications\";\nimport { initialize } from \"./apis/initialize\";\nimport { notificationsUpdate } from \"./apis/notificationsUpdate\";\nimport { signIn } from \"./apis/signIn\";\nimport { signOut } from \"./apis/signOut\";\nimport { updateProperties } from \"./apis/updateProperties\";\nimport { AndroidBridge } from \"./bridges/AndroidBridge\";\nimport { Bridge, BridgePlatform } from \"./bridges/Bridge\";\nimport { FlutterBridge } from \"./bridges/FlutterBridge\";\nimport { IosBridge } from \"./bridges/IosBridge\";\nimport { RNBridge } from \"./bridges/RNBridge\";\nimport { BLUX_ATTRIBUTES } from \"./constants/BLUX_ATTRIBUTES\";\nimport { PAGE_VISIT_ENABLED_APPLICATION_IDS } from \"./constants/PAGE_VISIT_ENABLED_APPLICATION_IDS\";\nimport {\n AddClickEvent,\n AddPageVisitEvent,\n AddSectionViewEvent,\n VisitEvent,\n} from \"./events\";\nimport { BluxRecError } from \"./recs\";\nimport { isSameUrlIgnoringHash } from \"./utils/isSameUrlIgnoringHash\";\nimport { LocalStorage } from \"./utils/LocalStorage\";\nimport { Logger } from \"./utils/Logger\";\nimport { onlineAndVisible$ } from \"./utils/observables\";\nimport { filterNullable } from \"./utils/operators\";\nimport { SessionStorage } from \"./utils/SessionStorage\";\nimport { isObjectIdString } from \"./utils/zodSchemas\";\n\nimport type { Event as BluxEvent } from \"./events/Event\";\nimport type { EventRequest } from \"./events/types\";\nimport type { GetRecParams, RecResult } from \"./recs\";\nimport type { LogLevel } from \"./utils/Logger\";\nimport type { AxiosInstance } from \"axios\";\n\nexport type SdkInfo = {\n type: DevicePlatform;\n version: string;\n};\n\ntype BluxUser = {\n id: string;\n bluxAPIKey: string;\n applicationId: string;\n deviceId: string;\n userId?: string;\n};\n\ntype BridgeOptions =\n | {\n /**\n * @deprecated `bridgePlatform`을 사용하세요.\n */\n useRNBridge?: boolean;\n }\n | { bridgePlatform?: BridgePlatform };\n\ntype InappMessageResponse = NonNullable<\n APIs.bluxUsersCollectEventsV2.ResponseData[\"inapp\"]\n>;\n\ntype ApiRequestQueueJob<T> = {\n action: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (error: unknown) => void;\n};\n\ntype UserPropertiesQueueItem = {\n userProperties?: APIs.bluxUsersUpdateProperties.Body[\"user_properties\"];\n customUserProperties?: APIs.bluxUsersUpdateProperties.Body[\"custom_user_properties\"];\n resolve: () => void;\n reject: (error: unknown) => void;\n};\n\n/**\n * 인앱 메시지에서 BluxBridge.triggerAction() 호출 시 전달되는 이벤트\n */\nexport type InAppCustomActionEvent = {\n /** 액션 식별자 */\n actionId: string;\n /** 액션에 전달된 데이터 */\n data: Record<string, unknown>;\n};\n\n/**\n * 인앱 커스텀 액션 핸들러 타입\n */\nexport type InAppCustomActionHandler = (\n event: InAppCustomActionEvent,\n) => void | Promise<void>;\n\nexport type GetNotificationsResult = {\n notifications: {\n id: string;\n title: string;\n body: string;\n imageUrl?: string;\n url?: string;\n sentAt: string;\n }[];\n nextCursor?: string;\n};\n\nexport class BluxClient {\n private readonly api: AxiosInstance = axios.create();\n\n private readonly sdkInfo: SdkInfo = {\n type: DevicePlatform.browser,\n version: packageJson.version,\n };\n\n public readonly bluxUser$ = new BehaviorSubject<BluxUser | undefined>(\n undefined,\n );\n\n /**\n * 내부 async 작업을 1개씩 직렬로 처리하는 큐입니다.\n * `enqueueRequest()`로 넣으면, 호출자는 완료 시점까지 `await` 할 수 있습니다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public readonly apiRequestQueue$ = new Subject<ApiRequestQueueJob<any>>();\n public readonly inappQueue$ = new BehaviorSubject<\n InappMessageResponse | undefined\n >(undefined);\n public readonly eventQueue$ = new Subject<EventRequest>();\n public readonly userPropertiesQueue$ = new Subject<UserPropertiesQueueItem>();\n\n private readonly INITIAL_NEXT_POLL_DELAY_MS = 10 * 1000;\n private readonly nextPollDelayMs$ = new BehaviorSubject<number | null>(\n this.INITIAL_NEXT_POLL_DELAY_MS,\n );\n private cachedNextPollDelayMs: number | null =\n this.INITIAL_NEXT_POLL_DELAY_MS;\n private lastPollTimestamp: number = Date.now();\n\n private readonly bluxApplicationId: string;\n private readonly bluxAPIKey: string;\n private readonly customDeviceId?: string;\n\n private readonly bridge: Bridge | null;\n\n // Impression 관련 변수\n private impressedElements: Element[] = [];\n private intersectionObservingElements: Element[] = [];\n private readonly intersectionObserver = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n this.intersectionEntry$.next(entry);\n });\n },\n );\n\n private readonly intersectionEntry$ =\n new Subject<IntersectionObserverEntry>();\n\n // Hit 관련 변수\n private hitElements: Element[] = [];\n\n // 현재 표시 중인 인앱 메시지의 cleanup 함수\n private currentInappCleanup: (() => void) | null = null;\n\n // 인앱 커스텀 액션 핸들러 (복수 등록 가능)\n private inAppCustomActionHandlers: Set<InAppCustomActionHandler> = new Set();\n\n private resolveBridge(bridgeOptions: BridgeOptions): Bridge | null {\n if (\"useRNBridge\" in bridgeOptions && bridgeOptions.useRNBridge) {\n return new RNBridge();\n }\n\n if (\"bridgePlatform\" in bridgeOptions && bridgeOptions.bridgePlatform) {\n switch (bridgeOptions.bridgePlatform) {\n case \"none\":\n return null;\n case \"android\":\n return new AndroidBridge();\n case \"ios\":\n return new IosBridge();\n case \"react-native\":\n return new RNBridge();\n case \"flutter\":\n return new FlutterBridge();\n default: {\n const exhaustiveCheck: never = bridgeOptions.bridgePlatform;\n Logger.error(`Unknown bridge platform: ${exhaustiveCheck}`, {\n tags: { from: \"resolveBridge\" },\n });\n }\n }\n }\n\n return null;\n }\n\n constructor(\n {\n bluxApplicationId,\n bluxAPIKey,\n customDeviceId,\n ...bridgeOptions\n }: {\n bluxApplicationId: string;\n bluxAPIKey: string;\n customDeviceId?: string;\n } & BridgeOptions,\n completionCallback?: (args: {\n errorMessage?: string;\n success: boolean;\n }) => void,\n ) {\n this.bluxApplicationId = bluxApplicationId;\n this.bluxAPIKey = bluxAPIKey;\n this.customDeviceId = customDeviceId;\n\n const baseUrl = window?._BLUX_SDK_?.baseUrl ?? \"https://api.blux.ai/prod\";\n this.api.defaults.baseURL = baseUrl;\n\n this.bridge = this.resolveBridge(bridgeOptions);\n\n if (typeof window !== \"undefined\") {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).__bluxClient = this;\n }\n\n // api 요청 항상 하나씩 처리\n this.apiRequestQueue$\n .pipe(\n concatMap(async (job) => {\n try {\n const result = await job.action();\n job.resolve(result);\n } catch (error) {\n job.reject(error);\n }\n }),\n )\n .subscribe();\n\n /**\n * (1) 보내야할 이벤트를 100ms씩 뭉쳐서 보내고, 1개 이상 쌓여있을 때 api 요청\n * (2) collectEvent의 응답에서 온 nextPollDelayMs 값 만큼 이후에 빈 배열로 api 요청 (1)로 보내면 초기화됨\n */\n\n if (!this.bridge) {\n merge(\n this.eventQueue$.pipe(\n bufferTime(100),\n filter((requests) => requests.length > 0),\n ),\n // onlineAndVisible일 때만 polling, 남은 시간 계산하여 타이머 시작\n combineLatest([this.nextPollDelayMs$, onlineAndVisible$]).pipe(\n switchMap(([delay, isOnlineAndVisible]) => {\n if (!isOnlineAndVisible || delay == null) return NEVER;\n\n const targetTime = this.lastPollTimestamp + delay;\n const remainingDelay = Math.max(0, targetTime - Date.now());\n\n return timer(remainingDelay);\n }),\n map(() => [] as EventRequest[]),\n ),\n )\n .pipe(\n concatMap(async (requests) => {\n this.nextPollDelayMs$.next(null);\n try {\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const events = requests.map((request) => ({\n ...request,\n blux_id: bluxUser.id,\n device_id: bluxUser.deviceId,\n }));\n\n const {\n data: { nextPollDelayMs, inapp },\n } = await this.enqueueRequest(() =>\n collectEvent(\n this.api,\n {\n blux_user_id: bluxUser.id,\n application_id: bluxUser.applicationId,\n },\n {\n device_id: bluxUser.deviceId,\n events,\n },\n this.generateApiHeader(bluxUser),\n ),\n );\n\n this.lastPollTimestamp = Date.now();\n this.cachedNextPollDelayMs = nextPollDelayMs;\n this.nextPollDelayMs$.next(Math.max(nextPollDelayMs, 3000));\n\n if (inapp) {\n this.inappQueue$.next(inapp);\n }\n } catch (error) {\n Logger.error(error, { tags: { from: \"eventQueue\" } });\n this.lastPollTimestamp = Date.now();\n if (this.cachedNextPollDelayMs !== null) {\n const nextPollDelay =\n this.cachedNextPollDelayMs > 1000 * 60 * 60 * 24\n ? this.cachedNextPollDelayMs\n : this.cachedNextPollDelayMs * 2;\n this.cachedNextPollDelayMs = nextPollDelay;\n this.nextPollDelayMs$.next(nextPollDelay);\n }\n }\n }),\n )\n .subscribe();\n }\n\n // api 호출 수 절감을 위해 100ms씩 뭉쳐서 보내고, custom_user_properties와 user_properties를 합쳐서 보냄\n this.userPropertiesQueue$\n .pipe(\n bufferTime(100),\n filter((items) => items.length > 0),\n concatMap(async (items) => {\n try {\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const mergedUserProperties = items.reduce(\n (acc, item) => ({ ...acc, ...(item.userProperties ?? {}) }),\n {} as NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"user_properties\"]\n >,\n );\n\n const mergedCustomUserProperties = items.reduce(\n (acc, item) => ({ ...acc, ...(item.customUserProperties ?? {}) }),\n {} as NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"custom_user_properties\"]\n >,\n );\n\n const payload: APIs.bluxUsersUpdateProperties.Body = {};\n if (Object.keys(mergedUserProperties).length > 0) {\n payload.user_properties = mergedUserProperties;\n }\n if (Object.keys(mergedCustomUserProperties).length > 0) {\n payload.custom_user_properties = mergedCustomUserProperties;\n }\n\n if (Object.keys(payload).length === 0) {\n items.forEach((item) => item.resolve());\n return;\n }\n\n await this.enqueueRequest(() =>\n updateProperties(\n this.api,\n {\n application_id: bluxUser.applicationId,\n blux_user_id: bluxUser.id,\n },\n payload,\n this.generateApiHeader(bluxUser),\n ),\n );\n\n items.forEach((item) => item.resolve());\n } catch (error) {\n items.forEach((item) => item.reject(error));\n Logger.error(error, { tags: { from: \"userPropertiesQueue\" } });\n }\n }),\n )\n .subscribe();\n\n this.bluxUser$.subscribe((bluxUser) => {\n if (bluxUser) SessionStorage.setBluxUserId(bluxUser.id);\n });\n\n if (!this.bridge) {\n this.inappQueue$\n .pipe(filterNullable())\n .pipe(\n concatMap((inapp) =>\n onlineAndVisible$.pipe(\n first(Boolean),\n concatMap(() => from(this.displayInapp(inapp))),\n ),\n ),\n )\n .subscribe();\n }\n\n this.init()\n .then(() => {\n completionCallback?.({ success: true });\n })\n .catch((error) => {\n completionCallback?.({ success: false, errorMessage: error.message });\n });\n }\n\n private async enqueueRequest<T>(action: () => Promise<T>) {\n return new Promise<T>((resolve, reject) => {\n const item: ApiRequestQueueJob<T> = { action, resolve, reject };\n this.apiRequestQueue$.next(item);\n });\n }\n\n private generateApiHeader({ bluxAPIKey }: { bluxAPIKey: string }) {\n return {\n Authorization: bluxAPIKey,\n };\n }\n\n // blux_user_id가 누락된 서버 응답으로 bluxUser$가 오염되면 이후 모든 요청 경로가\n // {blux_user_id} 치환 실패로 깨지므로, 유효한 id가 아니면 상태를 갱신하지 않는다.\n private setBluxUser(bluxUser: BluxUser) {\n if (!isObjectIdString(bluxUser.id)) {\n Logger.warning(\"Skipped bluxUser update with invalid id\", {\n extra: { id: bluxUser.id },\n });\n return;\n }\n\n this.bluxUser$.next(bluxUser);\n }\n\n private async displayInapp(inapp: InappMessageResponse) {\n return new Promise((resolve) => {\n const bluxUser = this.bluxUser$.getValue();\n\n const hideTimestamp = LocalStorage.getInappHideTimestamp(inapp.inappId);\n const isShouldSkip = hideTimestamp !== null && Date.now() < hideTimestamp;\n\n if (isShouldSkip) return resolve(true);\n\n Logger.debug(`[inapp:${inapp.inappId}] displayInapp started`, {\n extra: { htmlLength: inapp.htmlString.length, baseUrl: inapp.baseUrl },\n });\n\n const iframeElement = document.createElement(\"iframe\");\n\n // <base> 태그로 외부 리소스 경로 설정\n // history.replaceState/pushState 에러 방지를 위한 polyfill 추가\n const historyPolyfill = `<script>\n (function() {\n var origReplaceState = history.replaceState;\n var origPushState = history.pushState;\n history.replaceState = function() {\n try { return origReplaceState.apply(this, arguments); }\n catch(e) { console.debug('[Blux] history.replaceState blocked'); }\n };\n history.pushState = function() {\n try { return origPushState.apply(this, arguments); }\n catch(e) { console.debug('[Blux] history.pushState blocked'); }\n };\n })();\n </script>`;\n const headInjection = `${historyPolyfill}<base href=\"${inapp.baseUrl}/\">`;\n const htmlWithBase = inapp.htmlString.includes(\"<head>\")\n ? inapp.htmlString.replace(\"<head>\", `<head>${headInjection}`)\n : inapp.htmlString;\n\n iframeElement.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-forms\",\n );\n iframeElement.style.position = \"fixed\";\n iframeElement.style.width = `${window.innerWidth}px`;\n iframeElement.style.height = `${window.innerHeight}px`;\n iframeElement.style.top = \"0\";\n iframeElement.style.left = \"0\";\n iframeElement.style.border = \"none\";\n iframeElement.style.pointerEvents = \"none\";\n\n iframeElement.style.zIndex = \"9999\";\n iframeElement.id = `blux-inapp-${inapp.inappId}`;\n\n document.body.appendChild(iframeElement);\n\n const iframeDocument = iframeElement.contentDocument;\n if (iframeDocument) {\n iframeDocument.open();\n iframeDocument.write(htmlWithBase);\n iframeDocument.close();\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] iframe content written with document.write`,\n );\n } else {\n Logger.error(\n `[inapp:${inapp.inappId}] iframe document is not accessible for document.write`,\n );\n }\n\n let cleanupTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n iframeElement.addEventListener(\"load\", () => {\n Logger.debug(`[inapp:${inapp.inappId}] iframe load event fired`);\n try {\n const doc = iframeElement.contentDocument;\n const bodyLength = doc?.body?.innerHTML?.length ?? 0;\n Logger.debug(\n `[inapp:${inapp.inappId}] contentDocument accessible: ${!!doc}, bodyLength: ${bodyLength}`,\n );\n if (doc === null) {\n // cross-origin으로 contentDocument 접근 차단 (일부 WebView는 throw 대신 null 반환)\n // load 이벤트는 발화했으므로 콘텐츠는 로드됨 → 활성화\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] contentDocument is null (cross-origin), pointer-events enabled`,\n );\n } else if (bodyLength > 0) {\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] pointer-events enabled via load event`,\n );\n } else {\n Logger.warning(\n `[inapp:${inapp.inappId}] iframe loaded but body is empty, cleaning up`,\n );\n cleanup();\n }\n } catch (e) {\n // contentDocument 접근 불가 (cross-origin 등) → 콘텐츠가 로딩은 됐으므로 활성화\n iframeElement.style.pointerEvents = \"auto\";\n Logger.debug(\n `[inapp:${inapp.inappId}] contentDocument access blocked, pointer-events enabled`,\n { extra: { error: e } },\n );\n }\n });\n\n iframeElement.addEventListener(\"error\", (e) => {\n Logger.warning(`[inapp:${inapp.inappId}] iframe error event`, {\n extra: { error: e },\n });\n cleanup();\n });\n\n // iframe load 이벤트가 발생하지 않는 경우를 대비한 fallback\n const INAPP_IFRAME_TIMEOUT_MS = 10_000;\n cleanupTimeoutId = setTimeout(() => {\n if (iframeElement.style.pointerEvents === \"none\") {\n Logger.warning(\n `[inapp:${inapp.inappId}] iframe timeout - no load event within ${INAPP_IFRAME_TIMEOUT_MS}ms, cleaning up`,\n );\n cleanup();\n }\n }, INAPP_IFRAME_TIMEOUT_MS);\n\n let urlChangeSubscription: Subscription | null = null;\n\n const cleanup = () => {\n Logger.debug(`[inapp:${inapp.inappId}] cleanup called`);\n if (cleanupTimeoutId !== null) {\n clearTimeout(cleanupTimeoutId);\n cleanupTimeoutId = null;\n }\n iframeElement.remove();\n window.removeEventListener(\"message\", handleMessage);\n window.visualViewport?.removeEventListener(\n \"resize\",\n handleViewportChange,\n );\n window.visualViewport?.removeEventListener(\n \"scroll\",\n handleViewportChange,\n );\n urlChangeSubscription?.unsubscribe();\n this.currentInappCleanup = null;\n resolve(inapp.inappId);\n };\n\n // 현재 인앱 cleanup 함수 저장\n this.currentInappCleanup = cleanup;\n const handleViewportChange = () => {\n const width = window.visualViewport?.width ?? window.innerWidth;\n const height = window.visualViewport?.height ?? window.innerHeight;\n const left = window.visualViewport?.offsetLeft ?? 0;\n const top = window.visualViewport?.offsetTop ?? 0;\n\n iframeElement.style.width = `${width}px`;\n iframeElement.style.height = `${height}px`;\n iframeElement.style.left = `${left}px`;\n iframeElement.style.top = `${top}px`;\n };\n\n // cleanup을 self postMessage로 지연시키는 자가 신호. inapp_opened(iframe→parent)와\n // 동일 posted-message task source라 큐잉된 inapp_opened가 cleanup보다 먼저 처리된다.\n const INAPP_CLEANUP_SIGNAL = \"__blux_inapp_cleanup\" as const;\n\n const handleMessage = (\n event: MessageEvent<\n | {\n action: \"hide\";\n data: {\n days_to_hide: number;\n };\n }\n | {\n action: \"link\";\n data:\n | {\n is_blux_landing: false;\n url: string;\n open_in_new_tab: boolean;\n /**\n * Safari 팝업 차단 우회를 위해 iframe에서 직접 링크를 연 경우 true\n * 이 플래그가 true면 window.open을 스킵하고 이벤트만 찍음\n */\n opened_by_iframe?: boolean;\n }\n | {\n is_blux_landing: true;\n landing_id: string;\n };\n }\n | {\n action: \"resize\";\n data:\n | {\n height: number;\n location: \"top\" | \"bottom\";\n }\n | {\n width?: number;\n height?: number;\n top?: number;\n bottom?: number;\n left?: number;\n right?: number;\n };\n }\n | {\n action: \"custom_action\";\n data: {\n action_id: string;\n data: Record<string, unknown>;\n should_dismiss: boolean;\n };\n }\n | {\n action: \"inapp_opened\";\n data: {\n button_id: string;\n };\n }\n | {\n action: typeof INAPP_CLEANUP_SIGNAL;\n inappId: string;\n }\n >,\n ) => {\n if (event.source !== iframeElement.contentWindow) {\n // self cleanup 신호(source === window)만 처리. 그 외 발신자 메시지는 무시.\n if (\n event.source === window &&\n event.data?.action === INAPP_CLEANUP_SIGNAL &&\n event.data.inappId === inapp.inappId\n ) {\n cleanup();\n }\n return;\n }\n\n const message = event.data;\n\n if (message.action === \"resize\") {\n const data = message.data;\n\n if (\"location\" in data) {\n // 기존 배너용 resize (location 기반)\n iframeElement.style.width = \"90vw\";\n iframeElement.style.maxWidth = \"448px\";\n iframeElement.style.height = `${data.height}px`;\n iframeElement.style.left = \"50%\";\n iframeElement.style.transform = \"translateX(-50%)\";\n if (data.location === \"top\") {\n iframeElement.style.top = \"0\";\n iframeElement.style.bottom = \"auto\";\n } else {\n iframeElement.style.top = \"auto\";\n iframeElement.style.bottom = \"0\";\n }\n } else {\n // Custom HTML 절대 위치 지정\n if (data.width !== undefined) {\n iframeElement.style.width = `${data.width}px`;\n iframeElement.style.maxWidth = \"none\";\n }\n if (data.height !== undefined) {\n iframeElement.style.height = `${data.height}px`;\n }\n iframeElement.style.transform = \"none\";\n iframeElement.style.top =\n data.top !== undefined ? `${data.top}px` : \"auto\";\n iframeElement.style.bottom =\n data.bottom !== undefined ? `${data.bottom}px` : \"auto\";\n iframeElement.style.left =\n data.left !== undefined ? `${data.left}px` : \"auto\";\n iframeElement.style.right =\n data.right !== undefined ? `${data.right}px` : \"auto\";\n }\n\n window.visualViewport?.removeEventListener(\n \"resize\",\n handleViewportChange,\n );\n window.visualViewport?.removeEventListener(\n \"scroll\",\n handleViewportChange,\n );\n return; // resize는 cleanup하지 않음\n }\n\n if (message.action === \"hide\") {\n const { days_to_hide } = message.data;\n if (days_to_hide !== 0) {\n const expiresAt = Date.now() + days_to_hide * 24 * 60 * 60 * 1000;\n LocalStorage.setInappHideTimestamp(inapp.inappId, expiresAt);\n }\n } else if (message.action === \"link\") {\n // createCrmEvent 호출\n const crmEventPromise = bluxUser\n ? this.enqueueRequest(() =>\n createCrmEvent(\n this.api,\n {\n application_id: this.bluxApplicationId,\n },\n {\n notification_id: inapp.notificationId,\n crm_event_type: \"inapp_opened\",\n captured_at: new Date().toISOString(),\n },\n this.generateApiHeader(bluxUser),\n ),\n )\n : Promise.resolve();\n\n if (message.data.is_blux_landing) {\n // TODO: @all 스키마상에는 개인화 랜딩이 존재하나, 콘솔에는 필드가 없음 -> 추후 이에 따른 핸들링이 필요함\n } else {\n const { url, open_in_new_tab, opened_by_iframe } = message.data;\n const needsLocationChange = opened_by_iframe && !open_in_new_tab;\n\n // 하고하우스 특별 처리\n if (this.bluxApplicationId === \"6878af275ead5a75abbdacd7\") {\n crmEventPromise.finally(() => {\n window.top?.postMessage(\n {\n type: \"blux_inapp_message\",\n url,\n },\n window.location.origin,\n );\n });\n } else if (needsLocationChange) {\n // opened_by_iframe이고 현재 탭 이동인 경우: createCrmEvent 완료 후 이동\n crmEventPromise.finally(() => {\n window.location.href = url;\n });\n } else if (!opened_by_iframe) {\n // 기존 방식 (iframe에서 열지 않은 경우)\n if (open_in_new_tab) {\n window.open(url, \"_blank\");\n } else {\n window.location.href = url;\n }\n }\n // opened_by_iframe && open_in_new_tab인 경우: iframe에서 이미 새 탭 열었으므로 스킵\n }\n } else if (message.action === \"custom_action\") {\n const { action_id, data, should_dismiss } = message.data;\n\n // 등록된 모든 핸들러 호출\n for (const handler of this.inAppCustomActionHandlers) {\n try {\n handler({ actionId: action_id, data });\n } catch (error) {\n Logger.error(error, {\n tags: { from: \"inAppCustomActionHandler\" },\n });\n }\n }\n\n // should_dismiss가 false면 cleanup하지 않음\n if (!should_dismiss) {\n return;\n }\n } else if (message.action === \"inapp_opened\") {\n // data-blux-click 속성으로 인한 클릭 이벤트 추적\n if (bluxUser) {\n this.enqueueRequest(() =>\n createCrmEvent(\n this.api,\n {\n application_id: this.bluxApplicationId,\n },\n {\n notification_id: inapp.notificationId,\n crm_event_type: \"inapp_opened\",\n captured_at: new Date().toISOString(),\n },\n this.generateApiHeader(bluxUser),\n ),\n );\n }\n return; // 인앱메시지를 닫지 않음\n } else {\n // 인앱메시지가 보내지 않는 action은 무시한다. iOS Chrome은 모든 iframe에 스크립트를 넣어\n // 부모 창으로 registerAsChildFrameAck를 보내는데, 여기서 무시하지 않으면 아래 cleanup으로\n // 넘어가 인앱메시지가 뜨자마자 닫힌다.\n return;\n }\n\n // cleanup을 직접 호출하지 않고 self postMessage로 지연 (INAPP_CLEANUP_SIGNAL 분기에서 처리)\n window.postMessage(\n { action: INAPP_CLEANUP_SIGNAL, inappId: inapp.inappId },\n \"/\",\n );\n };\n\n // 페이지 이동 시 인앱메시지 닫기 (SPA 뒤로가기/앞으로가기 대응)\n urlChangeSubscription = this.pageTransition$\n .pipe(skip(1), first())\n .subscribe(() => {\n cleanup();\n });\n\n window.visualViewport?.addEventListener(\"resize\", handleViewportChange);\n window.visualViewport?.addEventListener(\"scroll\", handleViewportChange);\n window.addEventListener(\"message\", handleMessage);\n\n if (bluxUser) {\n this.enqueueRequest(() =>\n notificationsUpdate(\n this.api,\n {\n application_id: this.bluxApplicationId,\n notification_id: inapp.notificationId,\n },\n {\n status: \"received\",\n },\n this.generateApiHeader(bluxUser),\n ),\n );\n }\n });\n }\n\n private async init() {\n this.handleUrlAndRef();\n // 앱 웹뷰에서는 인앱 메시지를 네이티브 SDK가 띄우므로, 페이지가 바뀌면 네이티브 SDK에 알려 닫게 한다.\n // 처음 열린 페이지도 알려야 웹뷰가 새 문서를 열었을 때 이전 문서의 배너가 닫힌다.\n if (this.bridge) {\n const bridge = this.bridge;\n this.pageTransition$.subscribe(() => {\n bridge.pageTransition();\n });\n }\n // handle page visit event\n if (PAGE_VISIT_ENABLED_APPLICATION_IDS.has(this.bluxApplicationId)) {\n this.pageTransition$.subscribe(() => {\n this.sendEvent(new AddPageVisitEvent({}));\n });\n }\n\n // 노출·클릭 성과는 sendEvent를 거치므로 브릿지 모드에서도 네이티브 SDK로 전달된다.\n this.registerHitTracker();\n\n // 페이지 이동 시 추천 지표 수집 관련 변수 초기화\n this.pageTransition$.subscribe(() => {\n this.impressedElements = [];\n });\n\n this.registerImpressionTracker();\n new MutationObserver(() => {\n this.registerImpressionTracker();\n }).observe(document, { subtree: true, childList: true });\n\n // 500ms 연속으로 엘리먼트 들어있으면 추천 지표 수집\n const MIN_IMPRESSION_TIME = 500;\n this.intersectionEntry$\n .pipe(\n groupBy((entry) => entry.target),\n mergeMap((group$) =>\n group$.pipe(\n debounceTime(MIN_IMPRESSION_TIME),\n filter((entry) => entry.isIntersecting),\n ),\n ),\n )\n .subscribe((entry) => {\n const trackingId = entry.target.getAttribute(\n BLUX_ATTRIBUTES.tracking_id,\n );\n if (!trackingId) {\n Logger.warning(\n `Tracking ID is not found in the parent elements. Please check the tracking ID(${BLUX_ATTRIBUTES.tracking_id}) is set correctly.`,\n );\n return;\n }\n if (this.impressedElements.includes(entry.target)) return;\n\n this.sendEvent(\n new AddSectionViewEvent({\n section: trackingId,\n tracking: {\n id: trackingId,\n type: \"impression\",\n },\n }),\n );\n this.impressedElements.push(entry.target);\n });\n\n if (this.bridge) {\n this.bridge.initialize({\n bluxApplicationId: this.bluxApplicationId,\n bluxAPIKey: this.bluxAPIKey,\n customDeviceId: this.customDeviceId,\n });\n return;\n }\n\n const blux_user_id = new URLSearchParams(window.location.search).get(\n \"blux_user_id\",\n );\n const sessionId =\n SessionStorage.getSessionId() ?? new ObjectId().toHexString();\n SessionStorage.setSessionId(sessionId);\n\n const isBluxUserIdExistInSessionStorage = SessionStorage.getBluxUserId();\n if (!isBluxUserIdExistInSessionStorage) this.sendEvent(new VisitEvent({}));\n\n Logger.debug(`Init with application id: ${this.bluxApplicationId}`);\n\n const deviceId = LocalStorage.getDeviceId();\n\n if (!deviceId) {\n Logger.debug(\"No saved Device ID, register a new one.\");\n }\n\n const { data } = await initialize(\n this.api,\n { application_id: this.bluxApplicationId },\n {\n session_id: sessionId ?? undefined,\n device_id: deviceId ?? undefined,\n country_code: navigator.language.split(\"-\")[1],\n device_model: navigator.userAgent,\n language_code: navigator.language.split(\"-\")[0],\n platform: DevicePlatform.browser,\n os_version: navigator.userAgent,\n sdk_version: this.sdkInfo.version,\n sdk_type: this.sdkInfo.type,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n blux_user_id: blux_user_id ?? undefined,\n custom_device_id: this.customDeviceId,\n },\n this.generateApiHeader({\n bluxAPIKey: this.bluxAPIKey,\n }),\n );\n\n if (data.device_id) LocalStorage.setDeviceId(data.device_id);\n\n this.setBluxUser({\n id: data.blux_user_id,\n bluxAPIKey: this.bluxAPIKey,\n applicationId: this.bluxApplicationId,\n deviceId: data.device_id,\n });\n }\n\n public setLogLevel(logLevel: LogLevel) {\n Logger.setLevel(logLevel);\n }\n\n /**\n * 요청에 쓸 BluxUser를 5초까지 기다린다.\n *\n * 브릿지 모드에서는 유저를 아는 쪽이 네이티브 SDK 하나다. 앱이 직접 부른 signIn·signOut을\n * 웹이 알 수 없으므로 값을 기억하지 않고 호출마다 묻는다. 받은 값은 `bluxUser$`에 넣지 않는다.\n */\n private async getBluxUserWithTimeout() {\n const bluxUser$ = this.bridge\n ? this.pollBridgeBluxUser(this.bridge)\n : this.bluxUser$;\n const timeoutMessage = this.bridge\n ? \"BluxUser was not received from the native SDK. Check the native SDK version and the bridge setup.\"\n : \"Application ID is not initialized. Please initialize BluxClient.\";\n\n const bluxUser = await firstValueFrom(\n bluxUser$.pipe(\n filterNullable(),\n timeout(5000),\n catchError((error) => {\n return throwError(() =>\n error.name === \"TimeoutError\" ? new Error(timeoutMessage) : error,\n );\n }),\n ),\n );\n\n return bluxUser;\n }\n\n /**\n * 네이티브 SDK에 BluxUser를 묻고, 못 받으면 앞 호출이 끝나고 500ms 뒤에 다시 묻는다.\n * 그래서 응답이 느려도 호출이 겹치지 않는다.\n */\n private pollBridgeBluxUser(bridge: Bridge) {\n const RETRY_DELAY_MS = 500;\n return defer(() => bridge.getBluxUser()).pipe(\n map(\n (payload): BluxUser | undefined =>\n payload && {\n id: payload.bluxUserId,\n deviceId: payload.deviceId,\n applicationId: this.bluxApplicationId,\n bluxAPIKey: this.bluxAPIKey,\n },\n ),\n repeat({ delay: RETRY_DELAY_MS }),\n );\n }\n\n public async signIn({ userId }: { userId: string }) {\n try {\n if (this.bridge) {\n this.bridge.signIn({ userId });\n return;\n }\n\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const {\n data: { blux_user_id: bluxIdInResponse },\n } = await this.enqueueRequest(() =>\n signIn(\n this.api,\n { application_id: bluxUser.applicationId, blux_user_id: bluxUser.id },\n { user_id: userId, device_id: bluxUser.deviceId },\n this.generateApiHeader(bluxUser),\n ),\n );\n\n if (bluxIdInResponse !== bluxUser.id) {\n Logger.debug(\"Blux id is changed in signIn.\");\n this.setBluxUser({\n ...bluxUser,\n id: bluxIdInResponse,\n userId,\n });\n }\n } catch (error) {\n Logger.error(error, { tags: { from: \"signIn\" } });\n }\n }\n\n public async signOut() {\n try {\n if (this.bridge) {\n this.bridge.signOut();\n return;\n }\n\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const {\n data: { blux_user_id: bluxIdInResponse },\n } = await this.enqueueRequest(() =>\n signOut(\n this.api,\n { application_id: bluxUser.applicationId, blux_user_id: bluxUser.id },\n { device_id: bluxUser.deviceId },\n this.generateApiHeader(bluxUser),\n ),\n );\n\n Logger.debug(\"Blux id is changed in signOut.\");\n this.setBluxUser({\n ...bluxUser,\n id: bluxIdInResponse,\n userId: undefined,\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"signOut\" } });\n }\n }\n\n public async setUserProperties({\n userProperties,\n }: {\n userProperties: NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"user_properties\"]\n >;\n }) {\n try {\n if (this.bridge) {\n this.bridge.setUserProperties(userProperties);\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n this.userPropertiesQueue$.next({\n userProperties,\n resolve,\n reject,\n });\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"setUserProperties\" } });\n }\n }\n\n public async setCustomUserProperties({\n customUserProperties,\n }: {\n customUserProperties: NonNullable<\n APIs.bluxUsersUpdateProperties.Body[\"custom_user_properties\"]\n >;\n }) {\n try {\n if (this.bridge) {\n this.bridge.setCustomUserProperties(customUserProperties);\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n this.userPropertiesQueue$.next({\n customUserProperties,\n resolve,\n reject,\n });\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"setCustomUserProperties\" } });\n }\n }\n\n public sendEvent(event: BluxEvent | BluxEvent[]) {\n try {\n const requests = (Array.isArray(event) ? event : [event]).map((e) => ({\n ...e.request,\n internal_event_properties: {\n url: this.urlAndRef$.value?.url,\n ref: this.urlAndRef$.value?.ref,\n },\n }));\n\n if (this.bridge) {\n this.bridge.sendEvent({ requests });\n return;\n }\n\n requests.forEach((request) => {\n this.eventQueue$.next(request);\n });\n } catch (error) {\n Logger.error(error, { tags: { from: \"sendEvent\" } });\n }\n }\n\n /**\n * 현재 표시 중인 인앱 메시지를 프로그래밍 방식으로 닫습니다.\n *\n * Custom HTML 인앱에서 async 핸들러 완료 후 수동으로 닫을 때 사용합니다.\n *\n * @example\n * ```typescript\n * // BluxBridge.triggerAction('share', { url: '...' }, false); // shouldDismiss: false\n * // 핸들러에서 async 작업 완료 후:\n * bluxClient.dismissInApp();\n * ```\n */\n public dismissInApp() {\n this.currentInappCleanup?.();\n }\n\n /**\n * Custom HTML 인앱에서 BluxBridge.triggerAction() 호출 시 실행될 핸들러를 등록합니다.\n * 여러 핸들러를 등록할 수 있으며, 등록 순서대로 실행됩니다.\n *\n * @param handler - 커스텀 액션 핸들러 함수\n * @returns 핸들러를 제거하는 unsubscribe 함수\n *\n * @example\n * ```typescript\n * const unsubscribe = bluxClient.addInAppCustomActionHandler(async (event) => {\n * if (event.actionId === 'share') {\n * await navigator.share({ url: event.data.url as string });\n * // async 작업 완료 후 수동으로 닫기 (shouldDismiss: false로 호출한 경우)\n * bluxClient.dismissInApp();\n * }\n * });\n *\n * // 핸들러 제거\n * unsubscribe();\n * ```\n */\n public addInAppCustomActionHandler(\n handler: InAppCustomActionHandler,\n ): () => void {\n this.inAppCustomActionHandlers.add(handler);\n return () => {\n this.inAppCustomActionHandlers.delete(handler);\n };\n }\n\n private urlAndRef$ = new BehaviorSubject<\n { url: string; ref: string | undefined } | undefined\n >(undefined);\n\n private readonly pageTransition$ = this.urlAndRef$.pipe(\n filterNullable(),\n distinctUntilChanged((previous, current) =>\n isSameUrlIgnoringHash(previous.url, current.url),\n ),\n );\n\n private handleUrlAndRef() {\n this.urlAndRef$.pipe(filterNullable()).subscribe(({ url }) => {\n LocalStorage.setUrl(url);\n });\n // init url and ref from local storage\n this.urlAndRef$.next({\n url: window.location.href,\n ref: LocalStorage.getUrl() ?? undefined,\n });\n\n // observe url change\n const handleUrlChange = () => {\n const prevUrl = this.urlAndRef$.value?.url;\n const currentUrl = window.location.href;\n if (prevUrl !== currentUrl) {\n this.urlAndRef$.next({\n url: currentUrl,\n ref: prevUrl ?? undefined,\n });\n }\n };\n\n // SPA: popstate (뒤로가기/앞으로가기)\n window.addEventListener(\"popstate\", handleUrlChange);\n // 현재 브라우저에는 URL상태를 추적하기 위한 리스너가 존재하지 않습니다.\n // 그래서 pushState와 replaceState를 직접 수정하여 추적합니다.\n // @reference https://stackoverflow.com/a/64927639\n // @reference https://github.com/amplitude/Amplitude-TypeScript/blob/main/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts\n // SPA: pushState/replaceState proxy\n history.pushState = new Proxy(history.pushState, {\n apply: (target, thisArg, [state, unused, url]) => {\n target.apply(thisArg, [state, unused, url]);\n handleUrlChange();\n },\n });\n history.replaceState = new Proxy(history.replaceState, {\n apply: (target, thisArg, [state, unused, url]) => {\n target.apply(thisArg, [state, unused, url]);\n handleUrlChange();\n },\n });\n }\n\n private registerImpressionTracker() {\n const targetElements = Array.from(\n document.querySelectorAll(`[${BLUX_ATTRIBUTES.tracking_id}]`),\n );\n\n const removed = this.intersectionObservingElements.filter(\n (e) => !targetElements.includes(e),\n );\n\n removed.forEach((e) => {\n this.intersectionObserver.unobserve(e);\n });\n\n const added = targetElements.filter(\n (element) => !this.intersectionObservingElements.includes(element),\n );\n\n added.forEach((element) => {\n this.intersectionObserver.observe(element);\n });\n\n this.intersectionObservingElements = targetElements;\n }\n\n private registerHitTracker() {\n const handleDelegatedClick = (event: Event) => {\n if (!(event.target instanceof Element)) return;\n\n const eventTarget = event.target;\n\n const targetItemElement = eventTarget.closest(\n `[${BLUX_ATTRIBUTES.item_id}]`,\n );\n if (!targetItemElement) return;\n\n const itemId = targetItemElement.getAttribute(BLUX_ATTRIBUTES.item_id);\n if (!itemId) return;\n\n const targetSectionElement = eventTarget.closest(\n `[${BLUX_ATTRIBUTES.tracking_id}]`,\n );\n const trackingId = targetSectionElement?.getAttribute(\n BLUX_ATTRIBUTES.tracking_id,\n );\n if (!trackingId) {\n Logger.warning(\n `Tracking ID is not found in the parent elements. Please check the tracking ID(${BLUX_ATTRIBUTES.tracking_id}) is set correctly.`,\n );\n return;\n }\n\n if (!this.hitElements.includes(targetItemElement)) {\n this.sendEvent(\n new AddClickEvent({\n itemId,\n tracking: {\n id: trackingId,\n type: \"hit\",\n },\n }),\n );\n this.hitElements.push(targetItemElement);\n }\n };\n document.addEventListener(\"click\", handleDelegatedClick);\n this.pageTransition$.subscribe(() => {\n this.hitElements = [];\n });\n }\n\n /**\n * 추천 슬롯을 호출해 추천 상품을 받아온다.\n *\n * 슬롯이 요구하는 입력만 넘긴다 — 호출 시 상품을 받는 슬롯은 `itemIds`, 카테고리·브랜드 조건을\n * 요청 값으로 받는 슬롯은 `categoryId`·`brandId`. 필요한 입력이 빠지면 서버가 거절한다.\n *\n * @throws {BluxRecError} 슬롯이 없거나 비활성이거나, 필수 입력이 빠졌거나, 네트워크 오류이거나, 클라이언트 초기화가 5초 안에 끝나지 않은 경우\n */\n public async getRec({\n slotKey,\n limit,\n offset,\n itemIds,\n categoryId,\n brandId,\n }: GetRecParams): Promise<RecResult> {\n try {\n const bluxUser = await this.getBluxUserWithTimeout();\n const { data } = await getItemRecommendations(\n this.api,\n {\n application_id: bluxUser.applicationId,\n blux_user_id: bluxUser.id,\n },\n {\n call_type: slotKey,\n limit,\n offset,\n item_ids: itemIds,\n category_id: categoryId,\n brand_id: brandId,\n },\n this.generateApiHeader(bluxUser),\n );\n\n return {\n itemIds: data.item_ids,\n metadata: data.metadata,\n };\n } catch (error) {\n throw BluxRecError.from(error);\n }\n }\n\n public async getNotifications(\n params: {\n limit?: number;\n fromSentAt?: string | Date;\n toSentAt?: string | Date;\n cursor?: string;\n } = {},\n ): Promise<GetNotificationsResult> {\n const bluxUser = await this.getBluxUserWithTimeout();\n\n const toIso = (value: string | Date | undefined): string | undefined => {\n if (!value) return undefined;\n return value instanceof Date ? value.toISOString() : value;\n };\n\n const fromSentAtIso = toIso(params.fromSentAt);\n const toSentAtIso = toIso(params.toSentAt);\n const query: APIs.notificationsReadList.Query = {\n ...(params.limit !== undefined ? { limit: params.limit } : {}),\n ...(fromSentAtIso ? { from_sent_at: fromSentAtIso } : {}),\n ...(toSentAtIso ? { to_sent_at: toSentAtIso } : {}),\n ...(params.cursor ? { cursor: params.cursor } : {}),\n };\n\n const response = await getNotifications(\n this.api,\n {\n application_id: bluxUser.applicationId,\n blux_user_id: bluxUser.id,\n },\n query,\n this.generateApiHeader(bluxUser),\n );\n\n return {\n notifications: response.data.notifications.map((n) => ({\n id: n.id,\n title: n.title,\n body: n.body,\n imageUrl: n.image_url,\n url: n.url,\n sentAt: n.sent_at,\n })),\n nextCursor: response.data.next_cursor,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyIA,IAAa,aAAb,MAAwB;CACtB,AAAiB,MAAqB,MAAM,OAAO;CAEnD,AAAiB,UAAmB;EAClC;EACSA;CACX;CAEA,AAAgB,YAAY,IAAI,gBAC9B,MACF;;;;;CAOA,AAAgB,mBAAmB,IAAI,QAAiC;CACxE,AAAgB,cAAc,IAAI,gBAEhC,MAAS;CACX,AAAgB,cAAc,IAAI,QAAsB;CACxD,AAAgB,uBAAuB,IAAI,QAAiC;CAE5E,AAAiB,6BAA6B,KAAK;CACnD,AAAiB,mBAAmB,IAAI,gBACtC,KAAK,0BACP;CACA,AAAQ,wBACN,KAAK;CACP,AAAQ,oBAA4B,KAAK,IAAI;CAE7C,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,AAAiB;CAGjB,AAAQ,oBAA+B,CAAC;CACxC,AAAQ,gCAA2C,CAAC;CACpD,AAAiB,uBAAuB,IAAI,sBACzC,YAAY;EACX,QAAQ,SAAS,UAAU;GACzB,KAAK,mBAAmB,KAAK,KAAK;EACpC,CAAC;CACH,CACF;CAEA,AAAiB,qBACf,IAAI,QAAmC;CAGzC,AAAQ,cAAyB,CAAC;CAGlC,AAAQ,sBAA2C;CAGnD,AAAQ,4CAA2D,IAAI,IAAI;CAE3E,AAAQ,cAAc,eAA6C;EACjE,IAAI,iBAAiB,iBAAiB,cAAc,aAClD,OAAO,IAAI,SAAS;EAGtB,IAAI,oBAAoB,iBAAiB,cAAc,gBACrD,QAAQ,cAAc,gBAAtB;GACE,KAAK,QACH,OAAO;GACT,KAAK,WACH,OAAO,IAAI,cAAc;GAC3B,KAAK,OACH,OAAO,IAAI,UAAU;GACvB,KAAK,gBACH,OAAO,IAAI,SAAS;GACtB,KAAK,WACH,OAAO,IAAI,cAAc;GAC3B,SAAS;IACP,MAAM,kBAAyB,cAAc;IAC7C,OAAO,MAAM,4BAA4B,mBAAmB,EAC1D,MAAM,EAAE,MAAM,gBAAgB,EAChC,CAAC;GACH;EACF;EAGF,OAAO;CACT;CAEA,YACE,EACE,mBACA,YACA,gBACA,GAAG,iBAML,oBAIA;EACA,KAAK,oBAAoB;EACzB,KAAK,aAAa;EAClB,KAAK,iBAAiB;EAEtB,MAAM,UAAU,QAAQ,YAAY,WAAW;EAC/C,KAAK,IAAI,SAAS,UAAU;EAE5B,KAAK,SAAS,KAAK,cAAc,aAAa;EAE9C,IAAI,OAAO,WAAW,aAEpB,AAAC,OAAe,eAAe;EAIjC,KAAK,iBACF,KACC,UAAU,OAAO,QAAQ;GACvB,IAAI;IACF,MAAM,SAAS,MAAM,IAAI,OAAO;IAChC,IAAI,QAAQ,MAAM;GACpB,SAAS,OAAO;IACd,IAAI,OAAO,KAAK;GAClB;EACF,CAAC,CACH,CAAC,CACA,UAAU;;;;;EAOb,IAAI,CAAC,KAAK,QACR,MACE,KAAK,YAAY,KACf,WAAW,GAAG,GACd,QAAQ,aAAa,SAAS,SAAS,CAAC,CAC1C,GAEA,cAAc,CAAC,KAAK,kBAAkB,iBAAiB,CAAC,CAAC,CAAC,KACxD,WAAW,CAAC,OAAO,wBAAwB;GACzC,IAAI,CAAC,sBAAsB,SAAS,MAAM,OAAO;GAEjD,MAAM,aAAa,KAAK,oBAAoB;GAG5C,OAAO,MAFgB,KAAK,IAAI,GAAG,aAAa,KAAK,IAAI,CAE/B,CAAC;EAC7B,CAAC,GACD,UAAU,CAAC,CAAmB,CAChC,CACF,CAAC,CACE,KACC,UAAU,OAAO,aAAa;GAC5B,KAAK,iBAAiB,KAAK,IAAI;GAC/B,IAAI;IACF,MAAM,WAAW,MAAM,KAAK,uBAAuB;IAEnD,MAAM,SAAS,SAAS,KAAK,aAAa;KACxC,GAAG;KACH,SAAS,SAAS;KAClB,WAAW,SAAS;IACtB,EAAE;IAEF,MAAM,EACJ,MAAM,EAAE,iBAAiB,YACvB,MAAM,KAAK,qBACb,aACE,KAAK,KACL;KACE,cAAc,SAAS;KACvB,gBAAgB,SAAS;IAC3B,GACA;KACE,WAAW,SAAS;KACpB;IACF,GACA,KAAK,kBAAkB,QAAQ,CACjC,CACF;IAEA,KAAK,oBAAoB,KAAK,IAAI;IAClC,KAAK,wBAAwB;IAC7B,KAAK,iBAAiB,KAAK,KAAK,IAAI,iBAAiB,GAAI,CAAC;IAE1D,IAAI,OACF,KAAK,YAAY,KAAK,KAAK;GAE/B,SAAS,OAAO;IACd,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,EAAE,CAAC;IACpD,KAAK,oBAAoB,KAAK,IAAI;IAClC,IAAI,KAAK,0BAA0B,MAAM;KACvC,MAAM,gBACJ,KAAK,wBAAwB,MAAO,KAAK,KAAK,KAC1C,KAAK,wBACL,KAAK,wBAAwB;KACnC,KAAK,wBAAwB;KAC7B,KAAK,iBAAiB,KAAK,aAAa;IAC1C;GACF;EACF,CAAC,CACH,CAAC,CACA,UAAU;EAIf,KAAK,qBACF,KACC,WAAW,GAAG,GACd,QAAQ,UAAU,MAAM,SAAS,CAAC,GAClC,UAAU,OAAO,UAAU;GACzB,IAAI;IACF,MAAM,WAAW,MAAM,KAAK,uBAAuB;IAEnD,MAAM,uBAAuB,MAAM,QAChC,KAAK,UAAU;KAAE,GAAG;KAAK,GAAI,KAAK,kBAAkB,CAAC;IAAG,IACzD,CAAC,CAGH;IAEA,MAAM,6BAA6B,MAAM,QACtC,KAAK,UAAU;KAAE,GAAG;KAAK,GAAI,KAAK,wBAAwB,CAAC;IAAG,IAC/D,CAAC,CAGH;IAEA,MAAM,UAA+C,CAAC;IACtD,IAAI,OAAO,KAAK,oBAAoB,CAAC,CAAC,SAAS,GAC7C,QAAQ,kBAAkB;IAE5B,IAAI,OAAO,KAAK,0BAA0B,CAAC,CAAC,SAAS,GACnD,QAAQ,yBAAyB;IAGnC,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,GAAG;KACrC,MAAM,SAAS,SAAS,KAAK,QAAQ,CAAC;KACtC;IACF;IAEA,MAAM,KAAK,qBACT,iBACE,KAAK,KACL;KACE,gBAAgB,SAAS;KACzB,cAAc,SAAS;IACzB,GACA,SACA,KAAK,kBAAkB,QAAQ,CACjC,CACF;IAEA,MAAM,SAAS,SAAS,KAAK,QAAQ,CAAC;GACxC,SAAS,OAAO;IACd,MAAM,SAAS,SAAS,KAAK,OAAO,KAAK,CAAC;IAC1C,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,EAAE,CAAC;GAC/D;EACF,CAAC,CACH,CAAC,CACA,UAAU;EAEb,KAAK,UAAU,WAAW,aAAa;GACrC,IAAI,UAAU,eAAe,cAAc,SAAS,EAAE;EACxD,CAAC;EAED,IAAI,CAAC,KAAK,QACR,KAAK,YACF,KAAK,eAAe,CAAC,CAAC,CACtB,KACC,WAAW,UACT,kBAAkB,KAChB,MAAM,OAAO,GACb,gBAAgB,KAAK,KAAK,aAAa,KAAK,CAAC,CAAC,CAChD,CACF,CACF,CAAC,CACA,UAAU;EAGf,KAAK,KAAK,CAAC,CACR,WAAW;GACV,qBAAqB,EAAE,SAAS,KAAK,CAAC;EACxC,CAAC,CAAC,CACD,OAAO,UAAU;GAChB,qBAAqB;IAAE,SAAS;IAAO,cAAc,MAAM;GAAQ,CAAC;EACtE,CAAC;CACL;CAEA,MAAc,eAAkB,QAA0B;EACxD,OAAO,IAAI,SAAY,SAAS,WAAW;GACzC,MAAM,OAA8B;IAAE;IAAQ;IAAS;GAAO;GAC9D,KAAK,iBAAiB,KAAK,IAAI;EACjC,CAAC;CACH;CAEA,AAAQ,kBAAkB,EAAE,cAAsC;EAChE,OAAO,EACL,eAAe,WACjB;CACF;CAIA,AAAQ,YAAY,UAAoB;EACtC,IAAI,CAAC,iBAAiB,SAAS,EAAE,GAAG;GAClC,OAAO,QAAQ,2CAA2C,EACxD,OAAO,EAAE,IAAI,SAAS,GAAG,EAC3B,CAAC;GACD;EACF;EAEA,KAAK,UAAU,KAAK,QAAQ;CAC9B;CAEA,MAAc,aAAa,OAA6B;EACtD,OAAO,IAAI,SAAS,YAAY;GAC9B,MAAM,WAAW,KAAK,UAAU,SAAS;GAEzC,MAAM,gBAAgB,aAAa,sBAAsB,MAAM,OAAO;GAGtE,IAFqB,kBAAkB,QAAQ,KAAK,IAAI,IAAI,eAE1C,OAAO,QAAQ,IAAI;GAErC,OAAO,MAAM,UAAU,MAAM,QAAQ,yBAAyB,EAC5D,OAAO;IAAE,YAAY,MAAM,WAAW;IAAQ,SAAS,MAAM;GAAQ,EACvE,CAAC;GAED,MAAM,gBAAgB,SAAS,cAAc,QAAQ;GAkBrD,MAAM,gBAAgB;;;;;;;;;;;;;8BAAiC,MAAM,QAAQ;GACrE,MAAM,eAAe,MAAM,WAAW,SAAS,QAAQ,IACnD,MAAM,WAAW,QAAQ,UAAU,SAAS,eAAe,IAC3D,MAAM;GAEV,cAAc,QAAQ,IACpB,iBACA,qBACA,aACF;GACA,cAAc,MAAM,WAAW;GAC/B,cAAc,MAAM,QAAQ,GAAG,OAAO,WAAW;GACjD,cAAc,MAAM,SAAS,GAAG,OAAO,YAAY;GACnD,cAAc,MAAM,MAAM;GAC1B,cAAc,MAAM,OAAO;GAC3B,cAAc,MAAM,SAAS;GAC7B,cAAc,MAAM,gBAAgB;GAEpC,cAAc,MAAM,SAAS;GAC7B,cAAc,KAAK,cAAc,MAAM;GAEvC,SAAS,KAAK,YAAY,aAAa;GAEvC,MAAM,iBAAiB,cAAc;GACrC,IAAI,gBAAgB;IAClB,eAAe,KAAK;IACpB,eAAe,MAAM,YAAY;IACjC,eAAe,MAAM;IACrB,cAAc,MAAM,gBAAgB;IACpC,OAAO,MACL,UAAU,MAAM,QAAQ,6CAC1B;GACF,OACE,OAAO,MACL,UAAU,MAAM,QAAQ,uDAC1B;GAGF,IAAI,mBAAyD;GAE7D,cAAc,iBAAiB,cAAc;IAC3C,OAAO,MAAM,UAAU,MAAM,QAAQ,0BAA0B;IAC/D,IAAI;KACF,MAAM,MAAM,cAAc;KAC1B,MAAM,aAAa,KAAK,MAAM,WAAW,UAAU;KACnD,OAAO,MACL,UAAU,MAAM,QAAQ,gCAAgC,CAAC,CAAC,IAAI,gBAAgB,YAChF;KACA,IAAI,QAAQ,MAAM;MAGhB,cAAc,MAAM,gBAAgB;MACpC,OAAO,MACL,UAAU,MAAM,QAAQ,iEAC1B;KACF,OAAO,IAAI,aAAa,GAAG;MACzB,cAAc,MAAM,gBAAgB;MACpC,OAAO,MACL,UAAU,MAAM,QAAQ,wCAC1B;KACF,OAAO;MACL,OAAO,QACL,UAAU,MAAM,QAAQ,+CAC1B;MACA,QAAQ;KACV;IACF,SAAS,GAAG;KAEV,cAAc,MAAM,gBAAgB;KACpC,OAAO,MACL,UAAU,MAAM,QAAQ,2DACxB,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CACxB;IACF;GACF,CAAC;GAED,cAAc,iBAAiB,UAAU,MAAM;IAC7C,OAAO,QAAQ,UAAU,MAAM,QAAQ,uBAAuB,EAC5D,OAAO,EAAE,OAAO,EAAE,EACpB,CAAC;IACD,QAAQ;GACV,CAAC;GAGD,MAAM,0BAA0B;GAChC,mBAAmB,iBAAiB;IAClC,IAAI,cAAc,MAAM,kBAAkB,QAAQ;KAChD,OAAO,QACL,UAAU,MAAM,QAAQ,0CAA0C,wBAAwB,gBAC5F;KACA,QAAQ;IACV;GACF,GAAG,uBAAuB;GAE1B,IAAI,wBAA6C;GAEjD,MAAM,gBAAgB;IACpB,OAAO,MAAM,UAAU,MAAM,QAAQ,iBAAiB;IACtD,IAAI,qBAAqB,MAAM;KAC7B,aAAa,gBAAgB;KAC7B,mBAAmB;IACrB;IACA,cAAc,OAAO;IACrB,OAAO,oBAAoB,WAAW,aAAa;IACnD,OAAO,gBAAgB,oBACrB,UACA,oBACF;IACA,OAAO,gBAAgB,oBACrB,UACA,oBACF;IACA,uBAAuB,YAAY;IACnC,KAAK,sBAAsB;IAC3B,QAAQ,MAAM,OAAO;GACvB;GAGA,KAAK,sBAAsB;GAC3B,MAAM,6BAA6B;IACjC,MAAM,QAAQ,OAAO,gBAAgB,SAAS,OAAO;IACrD,MAAM,SAAS,OAAO,gBAAgB,UAAU,OAAO;IACvD,MAAM,OAAO,OAAO,gBAAgB,cAAc;IAClD,MAAM,MAAM,OAAO,gBAAgB,aAAa;IAEhD,cAAc,MAAM,QAAQ,GAAG,MAAM;IACrC,cAAc,MAAM,SAAS,GAAG,OAAO;IACvC,cAAc,MAAM,OAAO,GAAG,KAAK;IACnC,cAAc,MAAM,MAAM,GAAG,IAAI;GACnC;GAIA,MAAM,uBAAuB;GAE7B,MAAM,iBACJ,UA4DG;IACH,IAAI,MAAM,WAAW,cAAc,eAAe;KAEhD,IACE,MAAM,WAAW,UACjB,MAAM,MAAM,WAAW,wBACvB,MAAM,KAAK,YAAY,MAAM,SAE7B,QAAQ;KAEV;IACF;IAEA,MAAM,UAAU,MAAM;IAEtB,IAAI,QAAQ,WAAW,UAAU;KAC/B,MAAM,OAAO,QAAQ;KAErB,IAAI,cAAc,MAAM;MAEtB,cAAc,MAAM,QAAQ;MAC5B,cAAc,MAAM,WAAW;MAC/B,cAAc,MAAM,SAAS,GAAG,KAAK,OAAO;MAC5C,cAAc,MAAM,OAAO;MAC3B,cAAc,MAAM,YAAY;MAChC,IAAI,KAAK,aAAa,OAAO;OAC3B,cAAc,MAAM,MAAM;OAC1B,cAAc,MAAM,SAAS;MAC/B,OAAO;OACL,cAAc,MAAM,MAAM;OAC1B,cAAc,MAAM,SAAS;MAC/B;KACF,OAAO;MAEL,IAAI,KAAK,UAAU,QAAW;OAC5B,cAAc,MAAM,QAAQ,GAAG,KAAK,MAAM;OAC1C,cAAc,MAAM,WAAW;MACjC;MACA,IAAI,KAAK,WAAW,QAClB,cAAc,MAAM,SAAS,GAAG,KAAK,OAAO;MAE9C,cAAc,MAAM,YAAY;MAChC,cAAc,MAAM,MAClB,KAAK,QAAQ,SAAY,GAAG,KAAK,IAAI,MAAM;MAC7C,cAAc,MAAM,SAClB,KAAK,WAAW,SAAY,GAAG,KAAK,OAAO,MAAM;MACnD,cAAc,MAAM,OAClB,KAAK,SAAS,SAAY,GAAG,KAAK,KAAK,MAAM;MAC/C,cAAc,MAAM,QAClB,KAAK,UAAU,SAAY,GAAG,KAAK,MAAM,MAAM;KACnD;KAEA,OAAO,gBAAgB,oBACrB,UACA,oBACF;KACA,OAAO,gBAAgB,oBACrB,UACA,oBACF;KACA;IACF;IAEA,IAAI,QAAQ,WAAW,QAAQ;KAC7B,MAAM,EAAE,iBAAiB,QAAQ;KACjC,IAAI,iBAAiB,GAAG;MACtB,MAAM,YAAY,KAAK,IAAI,IAAI,eAAe,KAAK,KAAK,KAAK;MAC7D,aAAa,sBAAsB,MAAM,SAAS,SAAS;KAC7D;IACF,OAAO,IAAI,QAAQ,WAAW,QAAQ;KAEpC,MAAM,kBAAkB,WACpB,KAAK,qBACH,eACE,KAAK,KACL,EACE,gBAAgB,KAAK,kBACvB,GACA;MACE,iBAAiB,MAAM;MACvB,gBAAgB;MAChB,8BAAa,IAAI,KAAK,EAAC,CAAC,YAAY;KACtC,GACA,KAAK,kBAAkB,QAAQ,CACjC,CACF,IACA,QAAQ,QAAQ;KAEpB,IAAI,QAAQ,KAAK,iBAAiB,CAElC,OAAO;MACL,MAAM,EAAE,KAAK,iBAAiB,qBAAqB,QAAQ;MAC3D,MAAM,sBAAsB,oBAAoB,CAAC;MAGjD,IAAI,KAAK,sBAAsB,4BAC7B,gBAAgB,cAAc;OAC5B,OAAO,KAAK,YACV;QACE,MAAM;QACN;OACF,GACA,OAAO,SAAS,MAClB;MACF,CAAC;WACI,IAAI,qBAET,gBAAgB,cAAc;OAC5B,OAAO,SAAS,OAAO;MACzB,CAAC;WACI,IAAI,CAAC,kBAEV,IAAI,iBACF,OAAO,KAAK,KAAK,QAAQ;WAEzB,OAAO,SAAS,OAAO;KAI7B;IACF,OAAO,IAAI,QAAQ,WAAW,iBAAiB;KAC7C,MAAM,EAAE,WAAW,MAAM,mBAAmB,QAAQ;KAGpD,KAAK,MAAM,WAAW,KAAK,2BACzB,IAAI;MACF,QAAQ;OAAE,UAAU;OAAW;MAAK,CAAC;KACvC,SAAS,OAAO;MACd,OAAO,MAAM,OAAO,EAClB,MAAM,EAAE,MAAM,2BAA2B,EAC3C,CAAC;KACH;KAIF,IAAI,CAAC,gBACH;IAEJ,OAAO,IAAI,QAAQ,WAAW,gBAAgB;KAE5C,IAAI,UACF,KAAK,qBACH,eACE,KAAK,KACL,EACE,gBAAgB,KAAK,kBACvB,GACA;MACE,iBAAiB,MAAM;MACvB,gBAAgB;MAChB,8BAAa,IAAI,KAAK,EAAC,CAAC,YAAY;KACtC,GACA,KAAK,kBAAkB,QAAQ,CACjC,CACF;KAEF;IACF,OAIE;IAIF,OAAO,YACL;KAAE,QAAQ;KAAsB,SAAS,MAAM;IAAQ,GACvD,GACF;GACF;GAGA,wBAAwB,KAAK,gBAC1B,KAAK,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,CACtB,gBAAgB;IACf,QAAQ;GACV,CAAC;GAEH,OAAO,gBAAgB,iBAAiB,UAAU,oBAAoB;GACtE,OAAO,gBAAgB,iBAAiB,UAAU,oBAAoB;GACtE,OAAO,iBAAiB,WAAW,aAAa;GAEhD,IAAI,UACF,KAAK,qBACH,oBACE,KAAK,KACL;IACE,gBAAgB,KAAK;IACrB,iBAAiB,MAAM;GACzB,GACA,EACE,QAAQ,WACV,GACA,KAAK,kBAAkB,QAAQ,CACjC,CACF;EAEJ,CAAC;CACH;CAEA,MAAc,OAAO;EACnB,KAAK,gBAAgB;EAGrB,IAAI,KAAK,QAAQ;GACf,MAAM,SAAS,KAAK;GACpB,KAAK,gBAAgB,gBAAgB;IACnC,OAAO,eAAe;GACxB,CAAC;EACH;EAEA,IAAI,mCAAmC,IAAI,KAAK,iBAAiB,GAC/D,KAAK,gBAAgB,gBAAgB;GACnC,KAAK,UAAU,IAAI,kBAAkB,CAAC,CAAC,CAAC;EAC1C,CAAC;EAIH,KAAK,mBAAmB;EAGxB,KAAK,gBAAgB,gBAAgB;GACnC,KAAK,oBAAoB,CAAC;EAC5B,CAAC;EAED,KAAK,0BAA0B;EAC/B,IAAI,uBAAuB;GACzB,KAAK,0BAA0B;EACjC,CAAC,CAAC,CAAC,QAAQ,UAAU;GAAE,SAAS;GAAM,WAAW;EAAK,CAAC;EAGvD,MAAM,sBAAsB;EAC5B,KAAK,mBACF,KACC,SAAS,UAAU,MAAM,MAAM,GAC/B,UAAU,WACR,OAAO,KACL,aAAa,mBAAmB,GAChC,QAAQ,UAAU,MAAM,cAAc,CACxC,CACF,CACF,CAAC,CACA,WAAW,UAAU;GACpB,MAAM,aAAa,MAAM,OAAO,aAC9B,gBAAgB,WAClB;GACA,IAAI,CAAC,YAAY;IACf,OAAO,QACL,iFAAiF,gBAAgB,YAAY,oBAC/G;IACA;GACF;GACA,IAAI,KAAK,kBAAkB,SAAS,MAAM,MAAM,GAAG;GAEnD,KAAK,UACH,IAAI,oBAAoB;IACtB,SAAS;IACT,UAAU;KACR,IAAI;KACJ,MAAM;IACR;GACF,CAAC,CACH;GACA,KAAK,kBAAkB,KAAK,MAAM,MAAM;EAC1C,CAAC;EAEH,IAAI,KAAK,QAAQ;GACf,KAAK,OAAO,WAAW;IACrB,mBAAmB,KAAK;IACxB,YAAY,KAAK;IACjB,gBAAgB,KAAK;GACvB,CAAC;GACD;EACF;EAEA,MAAM,eAAe,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC,CAAC,IAC/D,cACF;EACA,MAAM,YACJ,eAAe,aAAa,KAAK,IAAI,SAAS,CAAC,CAAC,YAAY;EAC9D,eAAe,aAAa,SAAS;EAGrC,IAAI,CADsC,eAAe,cACpB,GAAG,KAAK,UAAU,IAAI,WAAW,CAAC,CAAC,CAAC;EAEzE,OAAO,MAAM,6BAA6B,KAAK,mBAAmB;EAElE,MAAM,WAAW,aAAa,YAAY;EAE1C,IAAI,CAAC,UACH,OAAO,MAAM,yCAAyC;EAGxD,MAAM,EAAE,SAAS,MAAM,WACrB,KAAK,KACL,EAAE,gBAAgB,KAAK,kBAAkB,GACzC;GACE,YAAY,aAAa;GACzB,WAAW,YAAY;GACvB,cAAc,UAAU,SAAS,MAAM,GAAG,CAAC,CAAC;GAC5C,cAAc,UAAU;GACxB,eAAe,UAAU,SAAS,MAAM,GAAG,CAAC,CAAC;GAC7C;GACA,YAAY,UAAU;GACtB,aAAa,KAAK,QAAQ;GAC1B,UAAU,KAAK,QAAQ;GACvB,UAAU,KAAK,eAAe,CAAC,CAAC,gBAAgB,CAAC,CAAC;GAClD,cAAc,gBAAgB;GAC9B,kBAAkB,KAAK;EACzB,GACA,KAAK,kBAAkB,EACrB,YAAY,KAAK,WACnB,CAAC,CACH;EAEA,IAAI,KAAK,WAAW,aAAa,YAAY,KAAK,SAAS;EAE3D,KAAK,YAAY;GACf,IAAI,KAAK;GACT,YAAY,KAAK;GACjB,eAAe,KAAK;GACpB,UAAU,KAAK;EACjB,CAAC;CACH;CAEA,AAAO,YAAY,UAAoB;EACrC,OAAO,SAAS,QAAQ;CAC1B;;;;;;;CAQA,MAAc,yBAAyB;EACrC,MAAM,YAAY,KAAK,SACnB,KAAK,mBAAmB,KAAK,MAAM,IACnC,KAAK;EACT,MAAM,iBAAiB,KAAK,SACxB,sGACA;EAcJ,OAAO,MAZgB,eACrB,UAAU,KACR,eAAe,GACf,QAAQ,GAAI,GACZ,YAAY,UAAU;GACpB,OAAO,iBACL,MAAM,SAAS,iBAAiB,IAAI,MAAM,cAAc,IAAI,KAC9D;EACF,CAAC,CACH,CACF;CAGF;;;;;CAMA,AAAQ,mBAAmB,QAAgB;EAEzC,OAAO,YAAY,OAAO,YAAY,CAAC,CAAC,CAAC,KACvC,KACG,YACC,WAAW;GACT,IAAI,QAAQ;GACZ,UAAU,QAAQ;GAClB,eAAe,KAAK;GACpB,YAAY,KAAK;EACnB,CACJ,GACA,OAAO,EAAE,OAAO,IAAe,CAAC,CAClC;CACF;CAEA,MAAa,OAAO,EAAE,UAA8B;EAClD,IAAI;GACF,IAAI,KAAK,QAAQ;IACf,KAAK,OAAO,OAAO,EAAE,OAAO,CAAC;IAC7B;GACF;GAEA,MAAM,WAAW,MAAM,KAAK,uBAAuB;GAEnD,MAAM,EACJ,MAAM,EAAE,cAAc,uBACpB,MAAM,KAAK,qBACb,OACE,KAAK,KACL;IAAE,gBAAgB,SAAS;IAAe,cAAc,SAAS;GAAG,GACpE;IAAE,SAAS;IAAQ,WAAW,SAAS;GAAS,GAChD,KAAK,kBAAkB,QAAQ,CACjC,CACF;GAEA,IAAI,qBAAqB,SAAS,IAAI;IACpC,OAAO,MAAM,+BAA+B;IAC5C,KAAK,YAAY;KACf,GAAG;KACH,IAAI;KACJ;IACF,CAAC;GACH;EACF,SAAS,OAAO;GACd,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,CAAC;EAClD;CACF;CAEA,MAAa,UAAU;EACrB,IAAI;GACF,IAAI,KAAK,QAAQ;IACf,KAAK,OAAO,QAAQ;IACpB;GACF;GAEA,MAAM,WAAW,MAAM,KAAK,uBAAuB;GAEnD,MAAM,EACJ,MAAM,EAAE,cAAc,uBACpB,MAAM,KAAK,qBACb,QACE,KAAK,KACL;IAAE,gBAAgB,SAAS;IAAe,cAAc,SAAS;GAAG,GACpE,EAAE,WAAW,SAAS,SAAS,GAC/B,KAAK,kBAAkB,QAAQ,CACjC,CACF;GAEA,OAAO,MAAM,gCAAgC;GAC7C,KAAK,YAAY;IACf,GAAG;IACH,IAAI;IACJ,QAAQ;GACV,CAAC;EACH,SAAS,OAAO;GACd,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,EAAE,CAAC;EACnD;CACF;CAEA,MAAa,kBAAkB,EAC7B,kBAKC;EACD,IAAI;GACF,IAAI,KAAK,QAAQ;IACf,KAAK,OAAO,kBAAkB,cAAc;IAC5C;GACF;GAEA,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,KAAK,qBAAqB,KAAK;KAC7B;KACA;KACA;IACF,CAAC;GACH,CAAC;EACH,SAAS,OAAO;GACd,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,EAAE,CAAC;EAC7D;CACF;CAEA,MAAa,wBAAwB,EACnC,wBAKC;EACD,IAAI;GACF,IAAI,KAAK,QAAQ;IACf,KAAK,OAAO,wBAAwB,oBAAoB;IACxD;GACF;GAEA,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,KAAK,qBAAqB,KAAK;KAC7B;KACA;KACA;IACF,CAAC;GACH,CAAC;EACH,SAAS,OAAO;GACd,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,EAAE,CAAC;EACnE;CACF;CAEA,AAAO,UAAU,OAAgC;EAC/C,IAAI;GACF,MAAM,YAAY,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAC,CAAE,KAAK,OAAO;IACpE,GAAG,EAAE;IACL,2BAA2B;KACzB,KAAK,KAAK,WAAW,OAAO;KAC5B,KAAK,KAAK,WAAW,OAAO;IAC9B;GACF,EAAE;GAEF,IAAI,KAAK,QAAQ;IACf,KAAK,OAAO,UAAU,EAAE,SAAS,CAAC;IAClC;GACF;GAEA,SAAS,SAAS,YAAY;IAC5B,KAAK,YAAY,KAAK,OAAO;GAC/B,CAAC;EACH,SAAS,OAAO;GACd,OAAO,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,EAAE,CAAC;EACrD;CACF;;;;;;;;;;;;;CAcA,AAAO,eAAe;EACpB,KAAK,sBAAsB;CAC7B;;;;;;;;;;;;;;;;;;;;;;CAuBA,AAAO,4BACL,SACY;EACZ,KAAK,0BAA0B,IAAI,OAAO;EAC1C,aAAa;GACX,KAAK,0BAA0B,OAAO,OAAO;EAC/C;CACF;CAEA,AAAQ,aAAa,IAAI,gBAEvB,MAAS;CAEX,AAAiB,kBAAkB,KAAK,WAAW,KACjD,eAAe,GACf,sBAAsB,UAAU,YAC9B,sBAAsB,SAAS,KAAK,QAAQ,GAAG,CACjD,CACF;CAEA,AAAQ,kBAAkB;EACxB,KAAK,WAAW,KAAK,eAAe,CAAC,CAAC,CAAC,WAAW,EAAE,UAAU;GAC5D,aAAa,OAAO,GAAG;EACzB,CAAC;EAED,KAAK,WAAW,KAAK;GACnB,KAAK,OAAO,SAAS;GACrB,KAAK,aAAa,OAAO,KAAK;EAChC,CAAC;EAGD,MAAM,wBAAwB;GAC5B,MAAM,UAAU,KAAK,WAAW,OAAO;GACvC,MAAM,aAAa,OAAO,SAAS;GACnC,IAAI,YAAY,YACd,KAAK,WAAW,KAAK;IACnB,KAAK;IACL,KAAK,WAAW;GAClB,CAAC;EAEL;EAGA,OAAO,iBAAiB,YAAY,eAAe;EAMnD,QAAQ,YAAY,IAAI,MAAM,QAAQ,WAAW,EAC/C,QAAQ,QAAQ,SAAS,CAAC,OAAO,QAAQ,SAAS;GAChD,OAAO,MAAM,SAAS;IAAC;IAAO;IAAQ;GAAG,CAAC;GAC1C,gBAAgB;EAClB,EACF,CAAC;EACD,QAAQ,eAAe,IAAI,MAAM,QAAQ,cAAc,EACrD,QAAQ,QAAQ,SAAS,CAAC,OAAO,QAAQ,SAAS;GAChD,OAAO,MAAM,SAAS;IAAC;IAAO;IAAQ;GAAG,CAAC;GAC1C,gBAAgB;EAClB,EACF,CAAC;CACH;CAEA,AAAQ,4BAA4B;EAClC,MAAM,iBAAiB,MAAM,KAC3B,SAAS,iBAAiB,IAAI,gBAAgB,YAAY,EAAE,CAC9D;EAMA,AAJgB,KAAK,8BAA8B,QAChD,MAAM,CAAC,eAAe,SAAS,CAAC,CAG7B,CAAC,CAAC,SAAS,MAAM;GACrB,KAAK,qBAAqB,UAAU,CAAC;EACvC,CAAC;EAMD,AAJc,eAAe,QAC1B,YAAY,CAAC,KAAK,8BAA8B,SAAS,OAAO,CAG/D,CAAC,CAAC,SAAS,YAAY;GACzB,KAAK,qBAAqB,QAAQ,OAAO;EAC3C,CAAC;EAED,KAAK,gCAAgC;CACvC;CAEA,AAAQ,qBAAqB;EAC3B,MAAM,wBAAwB,UAAiB;GAC7C,IAAI,EAAE,MAAM,kBAAkB,UAAU;GAExC,MAAM,cAAc,MAAM;GAE1B,MAAM,oBAAoB,YAAY,QACpC,IAAI,gBAAgB,QAAQ,EAC9B;GACA,IAAI,CAAC,mBAAmB;GAExB,MAAM,SAAS,kBAAkB,aAAa,gBAAgB,OAAO;GACrE,IAAI,CAAC,QAAQ;GAKb,MAAM,aAHuB,YAAY,QACvC,IAAI,gBAAgB,YAAY,EAEI,CAAC,EAAE,aACvC,gBAAgB,WAClB;GACA,IAAI,CAAC,YAAY;IACf,OAAO,QACL,iFAAiF,gBAAgB,YAAY,oBAC/G;IACA;GACF;GAEA,IAAI,CAAC,KAAK,YAAY,SAAS,iBAAiB,GAAG;IACjD,KAAK,UACH,IAAI,cAAc;KAChB;KACA,UAAU;MACR,IAAI;MACJ,MAAM;KACR;IACF,CAAC,CACH;IACA,KAAK,YAAY,KAAK,iBAAiB;GACzC;EACF;EACA,SAAS,iBAAiB,SAAS,oBAAoB;EACvD,KAAK,gBAAgB,gBAAgB;GACnC,KAAK,cAAc,CAAC;EACtB,CAAC;CACH;;;;;;;;;CAUA,MAAa,OAAO,EAClB,SACA,OACA,QACA,SACA,YACA,WACmC;EACnC,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,uBAAuB;GACnD,MAAM,EAAE,SAAS,MAAM,uBACrB,KAAK,KACL;IACE,gBAAgB,SAAS;IACzB,cAAc,SAAS;GACzB,GACA;IACE,WAAW;IACX;IACA;IACA,UAAU;IACV,aAAa;IACb,UAAU;GACZ,GACA,KAAK,kBAAkB,QAAQ,CACjC;GAEA,OAAO;IACL,SAAS,KAAK;IACd,UAAU,KAAK;GACjB;EACF,SAAS,OAAO;GACd,MAAM,aAAa,KAAK,KAAK;EAC/B;CACF;CAEA,MAAa,iBACX,SAKI,CAAC,GAC4B;EACjC,MAAM,WAAW,MAAM,KAAK,uBAAuB;EAEnD,MAAM,SAAS,UAAyD;GACtE,IAAI,CAAC,OAAO,OAAO;GACnB,OAAO,iBAAiB,OAAO,MAAM,YAAY,IAAI;EACvD;EAEA,MAAM,gBAAgB,MAAM,OAAO,UAAU;EAC7C,MAAM,cAAc,MAAM,OAAO,QAAQ;EACzC,MAAM,QAA0C;GAC9C,GAAI,OAAO,UAAU,SAAY,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;GAC5D,GAAI,gBAAgB,EAAE,cAAc,cAAc,IAAI,CAAC;GACvD,GAAI,cAAc,EAAE,YAAY,YAAY,IAAI,CAAC;GACjD,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;EACnD;EAEA,MAAM,WAAW,MAAM,iBACrB,KAAK,KACL;GACE,gBAAgB,SAAS;GACzB,cAAc,SAAS;EACzB,GACA,OACA,KAAK,kBAAkB,QAAQ,CACjC;EAEA,OAAO;GACL,eAAe,SAAS,KAAK,cAAc,KAAK,OAAO;IACrD,IAAI,EAAE;IACN,OAAO,EAAE;IACT,MAAM,EAAE;IACR,UAAU,EAAE;IACZ,KAAK,EAAE;IACP,QAAQ,EAAE;GACZ,EAAE;GACF,YAAY,SAAS,KAAK;EAC5B;CACF;AACF"}
|
package/dist/bridges/Bridge.js
CHANGED
|
@@ -35,6 +35,10 @@ var Bridge = class {
|
|
|
35
35
|
sendEvent(payload) {
|
|
36
36
|
this.postMessage("sendEvent", payload);
|
|
37
37
|
}
|
|
38
|
+
/** 웹뷰 안에서 페이지가 바뀌었음을 네이티브 SDK에 알린다. */
|
|
39
|
+
pageTransition() {
|
|
40
|
+
this.postMessage("pageTransition");
|
|
41
|
+
}
|
|
38
42
|
/**
|
|
39
43
|
* 네이티브 SDK가 지금 저장하고 있는 BluxUser를 묻는다. 값을 못 받으면 `undefined`다.
|
|
40
44
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Bridge.js","names":[],"sources":["../../src/bridges/Bridge.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { BluxClient } from \"../BluxClient\";\nimport { EventRequest } from \"../events/types\";\n\n// SSoT\nexport const BRIDGE_PLATFORMS = [\n \"none\",\n \"android\",\n \"ios\",\n \"react-native\",\n \"flutter\",\n] as const;\nexport type BridgePlatform = (typeof BRIDGE_PLATFORMS)[number];\n\nexport type BridgeAction =\n | \"initialize\"\n | \"signIn\"\n | \"signOut\"\n | \"setUserProperties\"\n | \"setCustomUserProperties\"\n | \"sendEvent\"\n | \"getBluxUser\";\n\nconst _bridgePayloadMap = {\n initialize: {\n bluxApplicationId: \"\",\n bluxAPIKey: \"\",\n customDeviceId: undefined as string | undefined,\n },\n signIn: {} as Parameters<BluxClient[\"signIn\"]>[0],\n signOut: {},\n setUserProperties: {} as Parameters<\n BluxClient[\"setUserProperties\"]\n >[0][\"userProperties\"],\n setCustomUserProperties: {} as Parameters<\n BluxClient[\"setCustomUserProperties\"]\n >[0][\"customUserProperties\"],\n sendEvent: { requests: [] as EventRequest[] },\n getBluxUser: undefined,\n} satisfies Record<BridgeAction, unknown>;\n\nexport type BridgePayloadMap = typeof _bridgePayloadMap;\n\nexport type BridgePayload = BridgePayloadMap[keyof BridgePayloadMap];\n\n/**\n * `getBluxUser` action에 앱이 돌려주는 응답. 네이티브 SDK가 저장하고 있는\n * 블럭스 유저 ID와 디바이스 ID다. 둘 중 하나라도 없으면 앱은 `null`을 돌려준다.\n */\nexport const bluxUserPayloadSchema = z.object({\n bluxUserId: z.string(),\n deviceId: z.string(),\n});\n\nexport type BluxUserPayload = z.infer<typeof bluxUserPayloadSchema>;\n\nexport abstract class Bridge {\n abstract readonly platform: BridgePlatform;\n\n protected abstract postMessage(\n action: BridgeAction,\n payload?: BridgePayload,\n ): void;\n\n initialize(payload: BridgePayloadMap[\"initialize\"]) {\n this.postMessage(\"initialize\", payload);\n }\n\n signIn(payload: BridgePayloadMap[\"signIn\"]) {\n this.postMessage(\"signIn\", payload);\n }\n\n signOut() {\n this.postMessage(\"signOut\");\n }\n\n setUserProperties(payload: BridgePayloadMap[\"setUserProperties\"]) {\n this.postMessage(\"setUserProperties\", payload);\n }\n\n setCustomUserProperties(\n payload: BridgePayloadMap[\"setCustomUserProperties\"],\n ) {\n this.postMessage(\"setCustomUserProperties\", payload);\n }\n\n sendEvent(payload: BridgePayloadMap[\"sendEvent\"]) {\n this.postMessage(\"sendEvent\", payload);\n }\n\n /**\n * 네이티브 SDK가 지금 저장하고 있는 BluxUser를 묻는다. 값을 못 받으면 `undefined`다.\n *\n * 앱이 웹으로 값을 돌려주는 경로는 flutter_inappwebview의 핸들러 반환값뿐이다.\n * 다른 웹뷰의 브릿지는 웹에서 앱으로만 가므로 항상 `undefined`다.\n */\n getBluxUser(): Promise<BluxUserPayload | undefined> {\n return Promise.resolve(undefined);\n }\n}\n"],"mappings":";;;AAMA,MAAa,mBAAmB;CAC9B;CACA;CACA;CACA;CACA;AACF;;;;;
|
|
1
|
+
{"version":3,"file":"Bridge.js","names":[],"sources":["../../src/bridges/Bridge.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { BluxClient } from \"../BluxClient\";\nimport { EventRequest } from \"../events/types\";\n\n// SSoT\nexport const BRIDGE_PLATFORMS = [\n \"none\",\n \"android\",\n \"ios\",\n \"react-native\",\n \"flutter\",\n] as const;\nexport type BridgePlatform = (typeof BRIDGE_PLATFORMS)[number];\n\nexport type BridgeAction =\n | \"initialize\"\n | \"signIn\"\n | \"signOut\"\n | \"setUserProperties\"\n | \"setCustomUserProperties\"\n | \"sendEvent\"\n | \"getBluxUser\"\n | \"pageTransition\";\n\nconst _bridgePayloadMap = {\n initialize: {\n bluxApplicationId: \"\",\n bluxAPIKey: \"\",\n customDeviceId: undefined as string | undefined,\n },\n signIn: {} as Parameters<BluxClient[\"signIn\"]>[0],\n signOut: {},\n setUserProperties: {} as Parameters<\n BluxClient[\"setUserProperties\"]\n >[0][\"userProperties\"],\n setCustomUserProperties: {} as Parameters<\n BluxClient[\"setCustomUserProperties\"]\n >[0][\"customUserProperties\"],\n sendEvent: { requests: [] as EventRequest[] },\n getBluxUser: undefined,\n pageTransition: undefined,\n} satisfies Record<BridgeAction, unknown>;\n\nexport type BridgePayloadMap = typeof _bridgePayloadMap;\n\nexport type BridgePayload = BridgePayloadMap[keyof BridgePayloadMap];\n\n/**\n * `getBluxUser` action에 앱이 돌려주는 응답. 네이티브 SDK가 저장하고 있는\n * 블럭스 유저 ID와 디바이스 ID다. 둘 중 하나라도 없으면 앱은 `null`을 돌려준다.\n */\nexport const bluxUserPayloadSchema = z.object({\n bluxUserId: z.string(),\n deviceId: z.string(),\n});\n\nexport type BluxUserPayload = z.infer<typeof bluxUserPayloadSchema>;\n\nexport abstract class Bridge {\n abstract readonly platform: BridgePlatform;\n\n protected abstract postMessage(\n action: BridgeAction,\n payload?: BridgePayload,\n ): void;\n\n initialize(payload: BridgePayloadMap[\"initialize\"]) {\n this.postMessage(\"initialize\", payload);\n }\n\n signIn(payload: BridgePayloadMap[\"signIn\"]) {\n this.postMessage(\"signIn\", payload);\n }\n\n signOut() {\n this.postMessage(\"signOut\");\n }\n\n setUserProperties(payload: BridgePayloadMap[\"setUserProperties\"]) {\n this.postMessage(\"setUserProperties\", payload);\n }\n\n setCustomUserProperties(\n payload: BridgePayloadMap[\"setCustomUserProperties\"],\n ) {\n this.postMessage(\"setCustomUserProperties\", payload);\n }\n\n sendEvent(payload: BridgePayloadMap[\"sendEvent\"]) {\n this.postMessage(\"sendEvent\", payload);\n }\n\n /** 웹뷰 안에서 페이지가 바뀌었음을 네이티브 SDK에 알린다. */\n pageTransition() {\n this.postMessage(\"pageTransition\");\n }\n\n /**\n * 네이티브 SDK가 지금 저장하고 있는 BluxUser를 묻는다. 값을 못 받으면 `undefined`다.\n *\n * 앱이 웹으로 값을 돌려주는 경로는 flutter_inappwebview의 핸들러 반환값뿐이다.\n * 다른 웹뷰의 브릿지는 웹에서 앱으로만 가므로 항상 `undefined`다.\n */\n getBluxUser(): Promise<BluxUserPayload | undefined> {\n return Promise.resolve(undefined);\n }\n}\n"],"mappings":";;;AAMA,MAAa,mBAAmB;CAC9B;CACA;CACA;CACA;CACA;AACF;;;;;AAwCA,MAAa,wBAAwB,EAAE,OAAO;CAC5C,YAAY,EAAE,OAAO;CACrB,UAAU,EAAE,OAAO;AACrB,CAAC;AAID,IAAsB,SAAtB,MAA6B;CAQ3B,WAAW,SAAyC;EAClD,KAAK,YAAY,cAAc,OAAO;CACxC;CAEA,OAAO,SAAqC;EAC1C,KAAK,YAAY,UAAU,OAAO;CACpC;CAEA,UAAU;EACR,KAAK,YAAY,SAAS;CAC5B;CAEA,kBAAkB,SAAgD;EAChE,KAAK,YAAY,qBAAqB,OAAO;CAC/C;CAEA,wBACE,SACA;EACA,KAAK,YAAY,2BAA2B,OAAO;CACrD;CAEA,UAAU,SAAwC;EAChD,KAAK,YAAY,aAAa,OAAO;CACvC;;CAGA,iBAAiB;EACf,KAAK,YAAY,gBAAgB;CACnC;;;;;;;CAQA,cAAoD;EAClD,OAAO,QAAQ,QAAQ,MAAS;CAClC;AACF"}
|
package/dist/package.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { combineLatest, distinctUntilChanged, fromEvent, map, merge, of, shareReplay, startWith } from "rxjs";
|
|
1
|
+
import { combineLatest, defer, distinctUntilChanged, fromEvent, map, merge, of, shareReplay, startWith } from "rxjs";
|
|
2
2
|
|
|
3
3
|
//#region src/utils/observables.ts
|
|
4
|
-
const visible$ = fromEvent(document, "visibilitychange").pipe(startWith(0), map(() => document.visibilityState === "visible"), distinctUntilChanged(), shareReplay({
|
|
4
|
+
const visible$ = defer(() => fromEvent(document, "visibilitychange")).pipe(startWith(0), map(() => document.visibilityState === "visible"), distinctUntilChanged(), shareReplay({
|
|
5
5
|
bufferSize: 1,
|
|
6
6
|
refCount: true
|
|
7
7
|
}));
|
|
8
|
-
const online$ = merge(fromEvent(window, "online").pipe(map(() => true)), fromEvent(window, "offline").pipe(map(() => false)), of(navigator.onLine)).pipe(distinctUntilChanged(), shareReplay({
|
|
8
|
+
const online$ = merge(defer(() => fromEvent(window, "online")).pipe(map(() => true)), defer(() => fromEvent(window, "offline")).pipe(map(() => false)), defer(() => of(navigator.onLine))).pipe(distinctUntilChanged(), shareReplay({
|
|
9
9
|
bufferSize: 1,
|
|
10
10
|
refCount: true
|
|
11
11
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observables.js","names":[],"sources":["../../src/utils/observables.ts"],"sourcesContent":["import {\n fromEvent,\n startWith,\n map,\n distinctUntilChanged,\n shareReplay,\n merge,\n of,\n combineLatest,\n} from \"rxjs\";\n\nconst visible$ = fromEvent(document, \"visibilitychange\").pipe(\n startWith(0),\n map(() => document.visibilityState === \"visible\"),\n distinctUntilChanged(),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst online$ = merge(\n fromEvent(window, \"online\").pipe(map(() => true)),\n fromEvent(window, \"offline\").pipe(map(() => false)),\n of(navigator.onLine),\n).pipe(distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));\n\nexport const onlineAndVisible$ = combineLatest([visible$, online$]).pipe(\n map(([v, o]) => v && o),\n distinctUntilChanged(),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"observables.js","names":[],"sources":["../../src/utils/observables.ts"],"sourcesContent":["import {\n defer,\n fromEvent,\n startWith,\n map,\n distinctUntilChanged,\n shareReplay,\n merge,\n of,\n combineLatest,\n} from \"rxjs\";\n\n// 서버에는 document·window가 없고, Node 20 이하에는 navigator도 없으므로 구독할 때 읽는다.\nconst visible$ = defer(() => fromEvent(document, \"visibilitychange\")).pipe(\n startWith(0),\n map(() => document.visibilityState === \"visible\"),\n distinctUntilChanged(),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n\nconst online$ = merge(\n defer(() => fromEvent(window, \"online\")).pipe(map(() => true)),\n defer(() => fromEvent(window, \"offline\")).pipe(map(() => false)),\n defer(() => of(navigator.onLine)),\n).pipe(distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));\n\nexport const onlineAndVisible$ = combineLatest([visible$, online$]).pipe(\n map(([v, o]) => v && o),\n distinctUntilChanged(),\n shareReplay({ bufferSize: 1, refCount: true }),\n);\n"],"mappings":";;;AAaA,MAAM,WAAW,YAAY,UAAU,UAAU,kBAAkB,CAAC,CAAC,CAAC,KACpE,UAAU,CAAC,GACX,UAAU,SAAS,oBAAoB,SAAS,GAChD,qBAAqB,GACrB,YAAY;CAAE,YAAY;CAAG,UAAU;AAAK,CAAC,CAC/C;AAEA,MAAM,UAAU,MACd,YAAY,UAAU,QAAQ,QAAQ,CAAC,CAAC,CAAC,KAAK,UAAU,IAAI,CAAC,GAC7D,YAAY,UAAU,QAAQ,SAAS,CAAC,CAAC,CAAC,KAAK,UAAU,KAAK,CAAC,GAC/D,YAAY,GAAG,UAAU,MAAM,CAAC,CAClC,CAAC,CAAC,KAAK,qBAAqB,GAAG,YAAY;CAAE,YAAY;CAAG,UAAU;AAAK,CAAC,CAAC;AAE7E,MAAa,oBAAoB,cAAc,CAAC,UAAU,OAAO,CAAC,CAAC,CAAC,KAClE,KAAK,CAAC,GAAG,OAAO,KAAK,CAAC,GACtB,qBAAqB,GACrB,YAAY;CAAE,YAAY;CAAG,UAAU;AAAK,CAAC,CAC/C"}
|