@apps-in-toss/framework 1.4.5 → 1.4.7

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.
Files changed (3) hide show
  1. package/dist/index.cjs +157 -50
  2. package/dist/index.js +145 -38
  3. package/package.json +7 -7
package/dist/index.cjs CHANGED
@@ -1377,10 +1377,11 @@ var import_native_modules18 = require("@apps-in-toss/native-modules");
1377
1377
  var appsInTossAsyncBridges = __toESM(require("@apps-in-toss/native-modules/async-bridges"), 1);
1378
1378
  var appsInTossConstantBridges = __toESM(require("@apps-in-toss/native-modules/constant-bridges"), 1);
1379
1379
  var appsInTossEventBridges = __toESM(require("@apps-in-toss/native-modules/event-bridges"), 1);
1380
+ var import_react_native_safe_area_context4 = require("@granite-js/native/react-native-safe-area-context");
1380
1381
  var import_react_native32 = require("@granite-js/react-native");
1381
1382
  var import_tds_react_native14 = require("@toss/tds-react-native");
1382
1383
  var import_private9 = require("@toss/tds-react-native/private");
1383
- var import_react28 = require("react");
1384
+ var import_react29 = require("react");
1384
1385
  var import_react_native33 = require("react-native");
1385
1386
 
1386
1387
  // src/components/GameWebView.tsx
@@ -1594,26 +1595,24 @@ function useBridgeHandler({
1594
1595
  onMessage,
1595
1596
  constantHandlerMap,
1596
1597
  asyncHandlerMap,
1597
- eventListenerMap,
1598
- injectedJavaScript: originalInjectedJavaScript
1598
+ eventListenerMap
1599
1599
  }) {
1600
1600
  const ref = (0, import_react23.useRef)(null);
1601
1601
  const injectedJavaScript = (0, import_react23.useMemo)(
1602
- () => [
1603
- `window.__CONSTANT_HANDLER_MAP = ${JSON.stringify(
1604
- Object.entries(constantHandlerMap).reduce(
1605
- (acc, [key, value]) => {
1606
- acc[key] = typeof value === "function" ? value() : value;
1607
- return acc;
1608
- },
1609
- {}
1610
- )
1611
- )};`,
1612
- originalInjectedJavaScript,
1613
- "true"
1614
- ].join("\n"),
1615
- [constantHandlerMap, originalInjectedJavaScript]
1602
+ () => `window.__CONSTANT_HANDLER_MAP = ${JSON.stringify(
1603
+ Object.entries(constantHandlerMap).reduce(
1604
+ (acc, [key, value]) => {
1605
+ acc[key] = typeof value === "function" ? value() : value;
1606
+ return acc;
1607
+ },
1608
+ {}
1609
+ )
1610
+ )};`,
1611
+ [constantHandlerMap]
1616
1612
  );
1613
+ (0, import_react23.useEffect)(() => {
1614
+ ref.current?.injectJavaScript(injectedJavaScript);
1615
+ }, [injectedJavaScript]);
1617
1616
  const createHandleOnEvent = (functionName, eventId) => (response) => {
1618
1617
  ref.current?.injectJavaScript(`
1619
1618
  window.__GRANITE_NATIVE_EMITTER.emit('${functionName}/onEvent/${eventId}', ${JSON.stringify(response, null, 0)});
@@ -1628,7 +1627,7 @@ function useBridgeHandler({
1628
1627
  const $onMessage = (0, import_react23.useCallback)(
1629
1628
  async (e) => {
1630
1629
  onMessage?.(e);
1631
- const data = JSON.parse(e.nativeEvent.data);
1630
+ const data = parseNativeEventData(e.nativeEvent.data);
1632
1631
  if (typeof data !== "object" || data === null || typeof data.functionName !== "string" || typeof data.eventId !== "string" || typeof data.type !== "string" || !["addEventListener", "removeEventListener", "method"].includes(data.type)) {
1633
1632
  return;
1634
1633
  }
@@ -1674,15 +1673,59 @@ function useBridgeHandler({
1674
1673
  onMessage: $onMessage
1675
1674
  };
1676
1675
  }
1676
+ function parseNativeEventData(data) {
1677
+ try {
1678
+ return JSON.parse(data);
1679
+ } catch (error) {
1680
+ console.error(error);
1681
+ return null;
1682
+ }
1683
+ }
1684
+
1685
+ // src/core/hooks/useSafeAreaInsetsEmitter.tsx
1686
+ var import_react_native_safe_area_context3 = require("@granite-js/native/react-native-safe-area-context");
1687
+ var import_react24 = require("react");
1688
+ var EventEmitter = class {
1689
+ listeners = {};
1690
+ on(event, listener) {
1691
+ if (!this.listeners[event]) {
1692
+ this.listeners[event] = [];
1693
+ }
1694
+ this.listeners[event].push(listener);
1695
+ }
1696
+ emit(event, ...args) {
1697
+ if (!this.listeners[event]) {
1698
+ return;
1699
+ }
1700
+ this.listeners[event].forEach((listener) => listener(...args));
1701
+ }
1702
+ off(event, listener) {
1703
+ if (!this.listeners[event]) {
1704
+ return;
1705
+ }
1706
+ this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
1707
+ }
1708
+ };
1709
+ function useSafeAreaInsetsEmitter() {
1710
+ const insets = (0, import_react_native_safe_area_context3.useSafeAreaInsets)();
1711
+ const emitter = (0, import_react24.useMemo)(() => new EventEmitter(), []);
1712
+ (0, import_react24.useEffect)(() => {
1713
+ emitter.emit("safeAreaInsetsChange", insets);
1714
+ return () => {
1715
+ emitter.off("safeAreaInsetsChange", (listener) => listener(insets));
1716
+ };
1717
+ }, [emitter, insets]);
1718
+ return emitter;
1719
+ }
1677
1720
 
1678
1721
  // src/core/hooks/useWebBackHandler.tsx
1679
1722
  var import_react_native27 = require("@granite-js/react-native");
1680
1723
  var import_tds_react_native13 = require("@toss/tds-react-native");
1681
1724
  var import_es_hangul5 = require("es-hangul");
1682
- var import_react25 = require("react");
1725
+ var import_react26 = require("react");
1683
1726
 
1684
1727
  // src/hooks/useWebviewHistoryStack.tsx
1685
- var import_react24 = require("react");
1728
+ var import_react25 = require("react");
1686
1729
  var INITIAL_STATE = { stack: [], index: -1 };
1687
1730
  function reducer(state, action) {
1688
1731
  switch (action.type) {
@@ -1713,11 +1756,11 @@ function reducer(state, action) {
1713
1756
  }
1714
1757
  }
1715
1758
  function useWebViewHistory() {
1716
- const [state, dispatch] = (0, import_react24.useReducer)(reducer, INITIAL_STATE);
1717
- const onNavigationStateChange = (0, import_react24.useCallback)(({ url, canGoForward: canGoForward2 }) => {
1759
+ const [state, dispatch] = (0, import_react25.useReducer)(reducer, INITIAL_STATE);
1760
+ const onNavigationStateChange = (0, import_react25.useCallback)(({ url, canGoForward: canGoForward2 }) => {
1718
1761
  dispatch({ type: "NAVIGATION_CHANGE", url, canGoForward: canGoForward2 });
1719
1762
  }, []);
1720
- const { canGoBack, canGoForward } = (0, import_react24.useMemo)(() => {
1763
+ const { canGoBack, canGoForward } = (0, import_react25.useMemo)(() => {
1721
1764
  const canBack = state.index > 0;
1722
1765
  const canFwd = state.index >= 0 && state.index < state.stack.length - 1;
1723
1766
  return { canGoBack: canBack, canGoForward: canFwd };
@@ -1750,19 +1793,19 @@ function useWebBackHandler(webViewRef) {
1750
1793
  const logging = useNavigationBarLogging();
1751
1794
  const { openConfirm } = (0, import_tds_react_native13.useDialog)();
1752
1795
  const global2 = getAppsInTossGlobals();
1753
- const addEventListener = (0, import_react25.useCallback)(
1796
+ const addEventListener = (0, import_react26.useCallback)(
1754
1797
  (handler) => {
1755
1798
  addWebBackEventListener(handler);
1756
1799
  },
1757
1800
  [addWebBackEventListener]
1758
1801
  );
1759
- const removeEventListener = (0, import_react25.useCallback)(
1802
+ const removeEventListener = (0, import_react26.useCallback)(
1760
1803
  (handler) => {
1761
1804
  removeWebBackEventListener(handler);
1762
1805
  },
1763
1806
  [removeWebBackEventListener]
1764
1807
  );
1765
- const handleWebBack = (0, import_react25.useCallback)(async () => {
1808
+ const handleWebBack = (0, import_react26.useCallback)(async () => {
1766
1809
  if (hasWebBackEvent) {
1767
1810
  for (const handler of webBackHandlersRef) {
1768
1811
  handler();
@@ -1795,7 +1838,7 @@ function useWebBackHandler(webViewRef) {
1795
1838
  openConfirm,
1796
1839
  webViewRef
1797
1840
  ]);
1798
- const handleWebHome = (0, import_react25.useCallback)(() => {
1841
+ const handleWebHome = (0, import_react26.useCallback)(() => {
1799
1842
  logging.homeButtonClick();
1800
1843
  if (hasWebBackEvent) {
1801
1844
  for (const handler of webBackHandlersRef) {
@@ -1805,7 +1848,7 @@ function useWebBackHandler(webViewRef) {
1805
1848
  }
1806
1849
  webViewRef.current?.injectJavaScript(HISTORY_HOME_SCRIPT);
1807
1850
  }, [hasWebBackEvent, webBackHandlersRef, logging, webViewRef]);
1808
- return (0, import_react25.useMemo)(
1851
+ return (0, import_react26.useMemo)(
1809
1852
  () => ({ addEventListener, removeEventListener, handleWebBack, handleWebHome, onNavigationStateChange }),
1810
1853
  [addEventListener, removeEventListener, handleWebBack, handleWebHome, onNavigationStateChange]
1811
1854
  );
@@ -1983,11 +2026,11 @@ function useCreateUserAgent({
1983
2026
  // src/hooks/useGeolocation.ts
1984
2027
  var import_native_modules16 = require("@apps-in-toss/native-modules");
1985
2028
  var import_react_native29 = require("@granite-js/react-native");
1986
- var import_react26 = require("react");
2029
+ var import_react27 = require("react");
1987
2030
  function useGeolocation({ accuracy, distanceInterval, timeInterval }) {
1988
2031
  const isVisible = (0, import_react_native29.useVisibility)();
1989
- const [location, setLocation] = (0, import_react26.useState)(null);
1990
- (0, import_react26.useEffect)(() => {
2032
+ const [location, setLocation] = (0, import_react27.useState)(null);
2033
+ (0, import_react27.useEffect)(() => {
1991
2034
  if (!isVisible) {
1992
2035
  return;
1993
2036
  }
@@ -2006,11 +2049,11 @@ function useGeolocation({ accuracy, distanceInterval, timeInterval }) {
2006
2049
 
2007
2050
  // src/hooks/useWaitForReturnNavigator.tsx
2008
2051
  var import_react_native30 = require("@granite-js/react-native");
2009
- var import_react27 = require("react");
2052
+ var import_react28 = require("react");
2010
2053
  function useWaitForReturnNavigator() {
2011
- const callbacks = (0, import_react27.useRef)([]).current;
2054
+ const callbacks = (0, import_react28.useRef)([]).current;
2012
2055
  const navigation = (0, import_react_native30.useNavigation)();
2013
- const startNavigating = (0, import_react27.useCallback)(
2056
+ const startNavigating = (0, import_react28.useCallback)(
2014
2057
  (route, params) => {
2015
2058
  return new Promise((resolve) => {
2016
2059
  callbacks.push(resolve);
@@ -2019,7 +2062,7 @@ function useWaitForReturnNavigator() {
2019
2062
  },
2020
2063
  [callbacks, navigation]
2021
2064
  );
2022
- const handleVisibilityChange = (0, import_react27.useCallback)(
2065
+ const handleVisibilityChange = (0, import_react28.useCallback)(
2023
2066
  (state) => {
2024
2067
  if (state === "visible" && callbacks.length > 0) {
2025
2068
  for (const callback of callbacks) {
@@ -2127,26 +2170,20 @@ function WebView({ type, local, onMessage, ...props }) {
2127
2170
  if (!TYPES.includes(type)) {
2128
2171
  throw new Error(`Invalid WebView type: '${type}'`);
2129
2172
  }
2130
- const webViewRef = (0, import_react28.useRef)(null);
2173
+ const webViewRef = (0, import_react29.useRef)(null);
2131
2174
  const webBackHandler = useWebBackHandler(webViewRef);
2132
- const uri = (0, import_react28.useMemo)(() => getWebViewUri(local), [local]);
2175
+ const uri = (0, import_react29.useMemo)(() => getWebViewUri(local), [local]);
2133
2176
  const top = (0, import_private9.useSafeAreaTop)();
2134
2177
  const bottom = (0, import_private9.useSafeAreaBottom)();
2178
+ const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
2135
2179
  const global2 = getAppsInTossGlobals();
2136
2180
  const navigationBarContext = useNavigationBarContext();
2137
- const disableTextSelectionCSS = `
2138
- (function() {
2139
- const style = document.createElement('style');
2140
- style.textContent = '*:not(input):not(textarea) { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-touch-callout: none; }';
2141
- document.head.appendChild(style);
2142
- })();
2143
- `;
2144
- const [allowsBackForwardNavigationGestures, setAllowsBackForwardNavigationGestures] = (0, import_react28.useState)(
2181
+ const safeAreaInsetsEmitter = useSafeAreaInsetsEmitter();
2182
+ const [allowsBackForwardNavigationGestures, setAllowsBackForwardNavigationGestures] = (0, import_react29.useState)(
2145
2183
  props.allowsBackForwardNavigationGestures
2146
2184
  );
2147
2185
  const handler = useBridgeHandler({
2148
2186
  onMessage,
2149
- injectedJavaScript: [disableTextSelectionCSS].join("\n"),
2150
2187
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2151
2188
  eventListenerMap: {
2152
2189
  ...appsInTossEventBridges,
@@ -2159,6 +2196,12 @@ function WebView({ type, local, onMessage, ...props }) {
2159
2196
  },
2160
2197
  entryMessageExited: ({ onEvent, onError }) => import_native_modules18.appsInTossEvent.addEventListener("entryMessageExited", { onEvent, onError }),
2161
2198
  updateLocationEvent: ({ onEvent, onError, options }) => import_native_modules18.appsInTossEvent.addEventListener("updateLocationEvent", { onEvent, onError, options }),
2199
+ safeAreaInsetsChange: ({ onEvent }) => {
2200
+ safeAreaInsetsEmitter.on("safeAreaInsetsChange", onEvent);
2201
+ return () => {
2202
+ safeAreaInsetsEmitter.off("safeAreaInsetsChange", onEvent);
2203
+ };
2204
+ },
2162
2205
  /** @internal */
2163
2206
  appBridgeCallbackEvent: ({ onEvent, onError, options }) => import_native_modules18.appsInTossEvent.addEventListener("appBridgeCallbackEvent", { onEvent, onError, options }),
2164
2207
  /** AdMob */
@@ -2177,6 +2220,8 @@ function WebView({ type, local, onMessage, ...props }) {
2177
2220
  ...appsInTossConstantBridges,
2178
2221
  getSafeAreaTop: () => top,
2179
2222
  getSafeAreaBottom: () => bottom,
2223
+ getSafeAreaLeft: () => insets.left,
2224
+ getSafeAreaRight: () => insets.right,
2180
2225
  ...Object.fromEntries(Object.entries(global2).map(([key, value]) => [key, () => value])),
2181
2226
  /** AdMob */
2182
2227
  loadAdMobInterstitialAd_isSupported: import_native_modules18.GoogleAdMob.loadAdMobInterstitialAd.isSupported,
@@ -2221,7 +2266,7 @@ function WebView({ type, local, onMessage, ...props }) {
2221
2266
  completeProductGrant: import_native_modules18.IAP.completeProductGrant
2222
2267
  }
2223
2268
  });
2224
- const headerPropForExternalWebView = (0, import_react28.useMemo)(() => {
2269
+ const headerPropForExternalWebView = (0, import_react29.useMemo)(() => {
2225
2270
  const parsedNavigationBar = global2.navigationBar != null ? safeParseNavigationBar(global2.navigationBar) : null;
2226
2271
  const initialAccessoryButton = parsedNavigationBar?.initialAccessoryButton;
2227
2272
  const withBackButton = parsedNavigationBar?.withBackButton ?? true;
@@ -2242,7 +2287,7 @@ function WebView({ type, local, onMessage, ...props }) {
2242
2287
  colorPreference: "light"
2243
2288
  });
2244
2289
  const refs = mergeRefs(handler.ref, webViewRef);
2245
- (0, import_react28.useEffect)(() => {
2290
+ (0, import_react29.useEffect)(() => {
2246
2291
  const callback = () => {
2247
2292
  webBackHandler.handleWebBack();
2248
2293
  return true;
@@ -2250,6 +2295,7 @@ function WebView({ type, local, onMessage, ...props }) {
2250
2295
  import_react_native33.BackHandler.addEventListener("hardwareBackPress", callback);
2251
2296
  return () => import_react_native33.BackHandler.removeEventListener("hardwareBackPress", callback);
2252
2297
  }, [webBackHandler]);
2298
+ const globalScripts = useGlobalScripts();
2253
2299
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2254
2300
  BaseWebView,
2255
2301
  {
@@ -2277,13 +2323,74 @@ function WebView({ type, local, onMessage, ...props }) {
2277
2323
  webviewDebuggingEnabled: webViewDebuggingEnabled,
2278
2324
  thirdPartyCookiesEnabled: true,
2279
2325
  onMessage: handler.onMessage,
2280
- injectedJavaScript: handler.injectedJavaScript,
2281
- injectedJavaScriptBeforeContentLoaded: handler.injectedJavaScript,
2326
+ injectedJavaScript: globalScripts.afterLoad,
2327
+ injectedJavaScriptBeforeContentLoaded: mergeScripts(handler.injectedJavaScript, globalScripts.beforeLoad),
2282
2328
  decelerationRate: import_react_native33.Platform.OS === "ios" ? 1 : void 0,
2283
2329
  allowsBackForwardNavigationGestures
2284
2330
  }
2285
2331
  );
2286
2332
  }
2333
+ function useGlobalScripts() {
2334
+ const global2 = getAppsInTossGlobals();
2335
+ const disableTextSelectionCSS = `
2336
+ const style = document.createElement('style');
2337
+ style.textContent = '*:not(input):not(textarea) { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-touch-callout: none; }';
2338
+ document.head.appendChild(style);
2339
+ `;
2340
+ const applyGameResourcesCache = `
2341
+ (function () {
2342
+ if (typeof caches === 'undefined') {
2343
+ return;
2344
+ }
2345
+ const cacheKeyPrefix = '@apps-in-toss/caches/';
2346
+ const cacheKey = \`\${cacheKeyPrefix}${global2.deploymentId}\`;
2347
+ window.addEventListener('load', async () => {
2348
+ const keys = await caches.keys();
2349
+ for (const key of keys) {
2350
+ if (key.startsWith(cacheKeyPrefix) && key !== cacheKey) {
2351
+ await caches.delete(key);
2352
+ }
2353
+ }
2354
+ });
2355
+ window.fetch = new Proxy(window.fetch, {
2356
+ async apply(originalFetch, thisArg, args) {
2357
+ const request = new Request(args[0], args[1]);
2358
+ if (!/\\.(data|wasm|framework\\.js)(?:\\.gz|\\.br|\\.unityweb)?$/.test(request.url)) {
2359
+ return await originalFetch.call(thisArg, request);
2360
+ }
2361
+ const cache = await caches.open(cacheKey);
2362
+ const cached = await cache.match(request);
2363
+ if (cached) {
2364
+ const eTag = cached.headers.get('ETag');
2365
+ const lastModified = cached.headers.get('Last-Modified');
2366
+ if (eTag) {
2367
+ request.headers.set('If-None-Match', eTag);
2368
+ }
2369
+ if (lastModified) {
2370
+ request.headers.set('If-Modified-Since', lastModified);
2371
+ }
2372
+ const revalidated = await originalFetch.call(thisArg, request);
2373
+ if (revalidated.status === 304) {
2374
+ return cached;
2375
+ }
2376
+ cache.put(request, revalidated.clone());
2377
+ return revalidated;
2378
+ }
2379
+ const response = await originalFetch.call(thisArg, request);
2380
+ cache.put(request, response.clone());
2381
+ return response;
2382
+ },
2383
+ });
2384
+ })();
2385
+ `;
2386
+ return {
2387
+ beforeLoad: mergeScripts(global2.webViewType === "game" && applyGameResourcesCache),
2388
+ afterLoad: mergeScripts(disableTextSelectionCSS)
2389
+ };
2390
+ }
2391
+ function mergeScripts(...scripts) {
2392
+ return scripts.filter((script) => typeof script === "string").join("\n");
2393
+ }
2287
2394
 
2288
2395
  // src/index.ts
2289
2396
  __reExport(src_exports, require("@apps-in-toss/analytics"), module.exports);
package/dist/index.js CHANGED
@@ -1358,10 +1358,11 @@ import {
1358
1358
  import * as appsInTossAsyncBridges from "@apps-in-toss/native-modules/async-bridges";
1359
1359
  import * as appsInTossConstantBridges from "@apps-in-toss/native-modules/constant-bridges";
1360
1360
  import * as appsInTossEventBridges from "@apps-in-toss/native-modules/event-bridges";
1361
+ import { useSafeAreaInsets as useSafeAreaInsets4 } from "@granite-js/native/react-native-safe-area-context";
1361
1362
  import { getSchemeUri as getSchemeUri6 } from "@granite-js/react-native";
1362
1363
  import { ExternalWebViewScreen, tdsEvent } from "@toss/tds-react-native";
1363
1364
  import { useSafeAreaBottom, useSafeAreaTop as useSafeAreaTop3 } from "@toss/tds-react-native/private";
1364
- import { useEffect as useEffect12, useMemo as useMemo6, useRef as useRef6, useState as useState8 } from "react";
1365
+ import { useEffect as useEffect14, useMemo as useMemo7, useRef as useRef6, useState as useState8 } from "react";
1365
1366
  import { BackHandler as BackHandler2, Platform as Platform6 } from "react-native";
1366
1367
 
1367
1368
  // src/components/GameWebView.tsx
@@ -1529,7 +1530,7 @@ var PartnerWebView = forwardRef2(function PartnerWebViewScreen({ onBackButtonCli
1529
1530
  });
1530
1531
 
1531
1532
  // src/bridge-handler/useBridgeHandler.tsx
1532
- import { useCallback as useCallback10, useMemo as useMemo3, useRef as useRef4 } from "react";
1533
+ import { useCallback as useCallback10, useEffect as useEffect11, useMemo as useMemo3, useRef as useRef4 } from "react";
1533
1534
  function serializeError(error) {
1534
1535
  return JSON.stringify(error, (_, value) => {
1535
1536
  if (value instanceof Error) {
@@ -1579,26 +1580,24 @@ function useBridgeHandler({
1579
1580
  onMessage,
1580
1581
  constantHandlerMap,
1581
1582
  asyncHandlerMap,
1582
- eventListenerMap,
1583
- injectedJavaScript: originalInjectedJavaScript
1583
+ eventListenerMap
1584
1584
  }) {
1585
1585
  const ref = useRef4(null);
1586
1586
  const injectedJavaScript = useMemo3(
1587
- () => [
1588
- `window.__CONSTANT_HANDLER_MAP = ${JSON.stringify(
1589
- Object.entries(constantHandlerMap).reduce(
1590
- (acc, [key, value]) => {
1591
- acc[key] = typeof value === "function" ? value() : value;
1592
- return acc;
1593
- },
1594
- {}
1595
- )
1596
- )};`,
1597
- originalInjectedJavaScript,
1598
- "true"
1599
- ].join("\n"),
1600
- [constantHandlerMap, originalInjectedJavaScript]
1587
+ () => `window.__CONSTANT_HANDLER_MAP = ${JSON.stringify(
1588
+ Object.entries(constantHandlerMap).reduce(
1589
+ (acc, [key, value]) => {
1590
+ acc[key] = typeof value === "function" ? value() : value;
1591
+ return acc;
1592
+ },
1593
+ {}
1594
+ )
1595
+ )};`,
1596
+ [constantHandlerMap]
1601
1597
  );
1598
+ useEffect11(() => {
1599
+ ref.current?.injectJavaScript(injectedJavaScript);
1600
+ }, [injectedJavaScript]);
1602
1601
  const createHandleOnEvent = (functionName, eventId) => (response) => {
1603
1602
  ref.current?.injectJavaScript(`
1604
1603
  window.__GRANITE_NATIVE_EMITTER.emit('${functionName}/onEvent/${eventId}', ${JSON.stringify(response, null, 0)});
@@ -1613,7 +1612,7 @@ function useBridgeHandler({
1613
1612
  const $onMessage = useCallback10(
1614
1613
  async (e) => {
1615
1614
  onMessage?.(e);
1616
- const data = JSON.parse(e.nativeEvent.data);
1615
+ const data = parseNativeEventData(e.nativeEvent.data);
1617
1616
  if (typeof data !== "object" || data === null || typeof data.functionName !== "string" || typeof data.eventId !== "string" || typeof data.type !== "string" || !["addEventListener", "removeEventListener", "method"].includes(data.type)) {
1618
1617
  return;
1619
1618
  }
@@ -1659,15 +1658,59 @@ function useBridgeHandler({
1659
1658
  onMessage: $onMessage
1660
1659
  };
1661
1660
  }
1661
+ function parseNativeEventData(data) {
1662
+ try {
1663
+ return JSON.parse(data);
1664
+ } catch (error) {
1665
+ console.error(error);
1666
+ return null;
1667
+ }
1668
+ }
1669
+
1670
+ // src/core/hooks/useSafeAreaInsetsEmitter.tsx
1671
+ import { useSafeAreaInsets as useSafeAreaInsets3 } from "@granite-js/native/react-native-safe-area-context";
1672
+ import { useEffect as useEffect12, useMemo as useMemo4 } from "react";
1673
+ var EventEmitter = class {
1674
+ listeners = {};
1675
+ on(event, listener) {
1676
+ if (!this.listeners[event]) {
1677
+ this.listeners[event] = [];
1678
+ }
1679
+ this.listeners[event].push(listener);
1680
+ }
1681
+ emit(event, ...args) {
1682
+ if (!this.listeners[event]) {
1683
+ return;
1684
+ }
1685
+ this.listeners[event].forEach((listener) => listener(...args));
1686
+ }
1687
+ off(event, listener) {
1688
+ if (!this.listeners[event]) {
1689
+ return;
1690
+ }
1691
+ this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
1692
+ }
1693
+ };
1694
+ function useSafeAreaInsetsEmitter() {
1695
+ const insets = useSafeAreaInsets3();
1696
+ const emitter = useMemo4(() => new EventEmitter(), []);
1697
+ useEffect12(() => {
1698
+ emitter.emit("safeAreaInsetsChange", insets);
1699
+ return () => {
1700
+ emitter.off("safeAreaInsetsChange", (listener) => listener(insets));
1701
+ };
1702
+ }, [emitter, insets]);
1703
+ return emitter;
1704
+ }
1662
1705
 
1663
1706
  // src/core/hooks/useWebBackHandler.tsx
1664
1707
  import { closeView as closeView6, useBackEventState } from "@granite-js/react-native";
1665
1708
  import { useDialog as useDialog7 } from "@toss/tds-react-native";
1666
1709
  import { josa as josa5 } from "es-hangul";
1667
- import { useCallback as useCallback12, useMemo as useMemo5 } from "react";
1710
+ import { useCallback as useCallback12, useMemo as useMemo6 } from "react";
1668
1711
 
1669
1712
  // src/hooks/useWebviewHistoryStack.tsx
1670
- import { useCallback as useCallback11, useMemo as useMemo4, useReducer } from "react";
1713
+ import { useCallback as useCallback11, useMemo as useMemo5, useReducer } from "react";
1671
1714
  var INITIAL_STATE = { stack: [], index: -1 };
1672
1715
  function reducer(state, action) {
1673
1716
  switch (action.type) {
@@ -1702,7 +1745,7 @@ function useWebViewHistory() {
1702
1745
  const onNavigationStateChange = useCallback11(({ url, canGoForward: canGoForward2 }) => {
1703
1746
  dispatch({ type: "NAVIGATION_CHANGE", url, canGoForward: canGoForward2 });
1704
1747
  }, []);
1705
- const { canGoBack, canGoForward } = useMemo4(() => {
1748
+ const { canGoBack, canGoForward } = useMemo5(() => {
1706
1749
  const canBack = state.index > 0;
1707
1750
  const canFwd = state.index >= 0 && state.index < state.stack.length - 1;
1708
1751
  return { canGoBack: canBack, canGoForward: canFwd };
@@ -1790,7 +1833,7 @@ function useWebBackHandler(webViewRef) {
1790
1833
  }
1791
1834
  webViewRef.current?.injectJavaScript(HISTORY_HOME_SCRIPT);
1792
1835
  }, [hasWebBackEvent, webBackHandlersRef, logging, webViewRef]);
1793
- return useMemo5(
1836
+ return useMemo6(
1794
1837
  () => ({ addEventListener, removeEventListener, handleWebBack, handleWebHome, onNavigationStateChange }),
1795
1838
  [addEventListener, removeEventListener, handleWebBack, handleWebHome, onNavigationStateChange]
1796
1839
  );
@@ -1968,11 +2011,11 @@ function useCreateUserAgent({
1968
2011
  // src/hooks/useGeolocation.ts
1969
2012
  import { startUpdateLocation } from "@apps-in-toss/native-modules";
1970
2013
  import { useVisibility as useVisibility3 } from "@granite-js/react-native";
1971
- import { useEffect as useEffect11, useState as useState7 } from "react";
2014
+ import { useEffect as useEffect13, useState as useState7 } from "react";
1972
2015
  function useGeolocation({ accuracy, distanceInterval, timeInterval }) {
1973
2016
  const isVisible = useVisibility3();
1974
2017
  const [location, setLocation] = useState7(null);
1975
- useEffect11(() => {
2018
+ useEffect13(() => {
1976
2019
  if (!isVisible) {
1977
2020
  return;
1978
2021
  }
@@ -2114,24 +2157,18 @@ function WebView({ type, local, onMessage, ...props }) {
2114
2157
  }
2115
2158
  const webViewRef = useRef6(null);
2116
2159
  const webBackHandler = useWebBackHandler(webViewRef);
2117
- const uri = useMemo6(() => getWebViewUri(local), [local]);
2160
+ const uri = useMemo7(() => getWebViewUri(local), [local]);
2118
2161
  const top = useSafeAreaTop3();
2119
2162
  const bottom = useSafeAreaBottom();
2163
+ const insets = useSafeAreaInsets4();
2120
2164
  const global2 = getAppsInTossGlobals();
2121
2165
  const navigationBarContext = useNavigationBarContext();
2122
- const disableTextSelectionCSS = `
2123
- (function() {
2124
- const style = document.createElement('style');
2125
- style.textContent = '*:not(input):not(textarea) { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-touch-callout: none; }';
2126
- document.head.appendChild(style);
2127
- })();
2128
- `;
2166
+ const safeAreaInsetsEmitter = useSafeAreaInsetsEmitter();
2129
2167
  const [allowsBackForwardNavigationGestures, setAllowsBackForwardNavigationGestures] = useState8(
2130
2168
  props.allowsBackForwardNavigationGestures
2131
2169
  );
2132
2170
  const handler = useBridgeHandler({
2133
2171
  onMessage,
2134
- injectedJavaScript: [disableTextSelectionCSS].join("\n"),
2135
2172
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2136
2173
  eventListenerMap: {
2137
2174
  ...appsInTossEventBridges,
@@ -2144,6 +2181,12 @@ function WebView({ type, local, onMessage, ...props }) {
2144
2181
  },
2145
2182
  entryMessageExited: ({ onEvent, onError }) => appsInTossEvent4.addEventListener("entryMessageExited", { onEvent, onError }),
2146
2183
  updateLocationEvent: ({ onEvent, onError, options }) => appsInTossEvent4.addEventListener("updateLocationEvent", { onEvent, onError, options }),
2184
+ safeAreaInsetsChange: ({ onEvent }) => {
2185
+ safeAreaInsetsEmitter.on("safeAreaInsetsChange", onEvent);
2186
+ return () => {
2187
+ safeAreaInsetsEmitter.off("safeAreaInsetsChange", onEvent);
2188
+ };
2189
+ },
2147
2190
  /** @internal */
2148
2191
  appBridgeCallbackEvent: ({ onEvent, onError, options }) => appsInTossEvent4.addEventListener("appBridgeCallbackEvent", { onEvent, onError, options }),
2149
2192
  /** AdMob */
@@ -2162,6 +2205,8 @@ function WebView({ type, local, onMessage, ...props }) {
2162
2205
  ...appsInTossConstantBridges,
2163
2206
  getSafeAreaTop: () => top,
2164
2207
  getSafeAreaBottom: () => bottom,
2208
+ getSafeAreaLeft: () => insets.left,
2209
+ getSafeAreaRight: () => insets.right,
2165
2210
  ...Object.fromEntries(Object.entries(global2).map(([key, value]) => [key, () => value])),
2166
2211
  /** AdMob */
2167
2212
  loadAdMobInterstitialAd_isSupported: GoogleAdMob.loadAdMobInterstitialAd.isSupported,
@@ -2206,7 +2251,7 @@ function WebView({ type, local, onMessage, ...props }) {
2206
2251
  completeProductGrant: IAP.completeProductGrant
2207
2252
  }
2208
2253
  });
2209
- const headerPropForExternalWebView = useMemo6(() => {
2254
+ const headerPropForExternalWebView = useMemo7(() => {
2210
2255
  const parsedNavigationBar = global2.navigationBar != null ? safeParseNavigationBar(global2.navigationBar) : null;
2211
2256
  const initialAccessoryButton = parsedNavigationBar?.initialAccessoryButton;
2212
2257
  const withBackButton = parsedNavigationBar?.withBackButton ?? true;
@@ -2227,7 +2272,7 @@ function WebView({ type, local, onMessage, ...props }) {
2227
2272
  colorPreference: "light"
2228
2273
  });
2229
2274
  const refs = mergeRefs(handler.ref, webViewRef);
2230
- useEffect12(() => {
2275
+ useEffect14(() => {
2231
2276
  const callback = () => {
2232
2277
  webBackHandler.handleWebBack();
2233
2278
  return true;
@@ -2235,6 +2280,7 @@ function WebView({ type, local, onMessage, ...props }) {
2235
2280
  BackHandler2.addEventListener("hardwareBackPress", callback);
2236
2281
  return () => BackHandler2.removeEventListener("hardwareBackPress", callback);
2237
2282
  }, [webBackHandler]);
2283
+ const globalScripts = useGlobalScripts();
2238
2284
  return /* @__PURE__ */ jsx16(
2239
2285
  BaseWebView,
2240
2286
  {
@@ -2262,13 +2308,74 @@ function WebView({ type, local, onMessage, ...props }) {
2262
2308
  webviewDebuggingEnabled: webViewDebuggingEnabled,
2263
2309
  thirdPartyCookiesEnabled: true,
2264
2310
  onMessage: handler.onMessage,
2265
- injectedJavaScript: handler.injectedJavaScript,
2266
- injectedJavaScriptBeforeContentLoaded: handler.injectedJavaScript,
2311
+ injectedJavaScript: globalScripts.afterLoad,
2312
+ injectedJavaScriptBeforeContentLoaded: mergeScripts(handler.injectedJavaScript, globalScripts.beforeLoad),
2267
2313
  decelerationRate: Platform6.OS === "ios" ? 1 : void 0,
2268
2314
  allowsBackForwardNavigationGestures
2269
2315
  }
2270
2316
  );
2271
2317
  }
2318
+ function useGlobalScripts() {
2319
+ const global2 = getAppsInTossGlobals();
2320
+ const disableTextSelectionCSS = `
2321
+ const style = document.createElement('style');
2322
+ style.textContent = '*:not(input):not(textarea) { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-touch-callout: none; }';
2323
+ document.head.appendChild(style);
2324
+ `;
2325
+ const applyGameResourcesCache = `
2326
+ (function () {
2327
+ if (typeof caches === 'undefined') {
2328
+ return;
2329
+ }
2330
+ const cacheKeyPrefix = '@apps-in-toss/caches/';
2331
+ const cacheKey = \`\${cacheKeyPrefix}${global2.deploymentId}\`;
2332
+ window.addEventListener('load', async () => {
2333
+ const keys = await caches.keys();
2334
+ for (const key of keys) {
2335
+ if (key.startsWith(cacheKeyPrefix) && key !== cacheKey) {
2336
+ await caches.delete(key);
2337
+ }
2338
+ }
2339
+ });
2340
+ window.fetch = new Proxy(window.fetch, {
2341
+ async apply(originalFetch, thisArg, args) {
2342
+ const request = new Request(args[0], args[1]);
2343
+ if (!/\\.(data|wasm|framework\\.js)(?:\\.gz|\\.br|\\.unityweb)?$/.test(request.url)) {
2344
+ return await originalFetch.call(thisArg, request);
2345
+ }
2346
+ const cache = await caches.open(cacheKey);
2347
+ const cached = await cache.match(request);
2348
+ if (cached) {
2349
+ const eTag = cached.headers.get('ETag');
2350
+ const lastModified = cached.headers.get('Last-Modified');
2351
+ if (eTag) {
2352
+ request.headers.set('If-None-Match', eTag);
2353
+ }
2354
+ if (lastModified) {
2355
+ request.headers.set('If-Modified-Since', lastModified);
2356
+ }
2357
+ const revalidated = await originalFetch.call(thisArg, request);
2358
+ if (revalidated.status === 304) {
2359
+ return cached;
2360
+ }
2361
+ cache.put(request, revalidated.clone());
2362
+ return revalidated;
2363
+ }
2364
+ const response = await originalFetch.call(thisArg, request);
2365
+ cache.put(request, response.clone());
2366
+ return response;
2367
+ },
2368
+ });
2369
+ })();
2370
+ `;
2371
+ return {
2372
+ beforeLoad: mergeScripts(global2.webViewType === "game" && applyGameResourcesCache),
2373
+ afterLoad: mergeScripts(disableTextSelectionCSS)
2374
+ };
2375
+ }
2376
+ function mergeScripts(...scripts) {
2377
+ return scripts.filter((script) => typeof script === "string").join("\n");
2378
+ }
2272
2379
 
2273
2380
  // src/index.ts
2274
2381
  export * from "@apps-in-toss/analytics";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@apps-in-toss/framework",
3
3
  "type": "module",
4
- "version": "1.4.5",
4
+ "version": "1.4.7",
5
5
  "description": "The framework for Apps In Toss",
6
6
  "scripts": {
7
7
  "prepack": "yarn build",
@@ -56,11 +56,11 @@
56
56
  "ait": "./bin/ait.js"
57
57
  },
58
58
  "dependencies": {
59
- "@apps-in-toss/analytics": "1.4.5",
60
- "@apps-in-toss/cli": "1.4.5",
61
- "@apps-in-toss/native-modules": "1.4.5",
62
- "@apps-in-toss/plugins": "1.4.5",
63
- "@apps-in-toss/types": "1.4.5",
59
+ "@apps-in-toss/analytics": "1.4.7",
60
+ "@apps-in-toss/cli": "1.4.7",
61
+ "@apps-in-toss/native-modules": "1.4.7",
62
+ "@apps-in-toss/plugins": "1.4.7",
63
+ "@apps-in-toss/types": "1.4.7",
64
64
  "es-hangul": "^2.3.2"
65
65
  },
66
66
  "devDependencies": {
@@ -96,5 +96,5 @@
96
96
  "publishConfig": {
97
97
  "access": "public"
98
98
  },
99
- "gitHead": "62245d6361e8c062814ca80002b230ea281a15ff"
99
+ "gitHead": "9c3df99aae50b27ef775840416e7cc912de13c6a"
100
100
  }