Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 92x 11016x 11016x 11016x 3183x 3608x 3593x 10809x 207x 199x 198x 10x 9x 92x 92x 92x 1x 427x 426x 426x 426x 4x 4x 4x 4x 4x 94x 4x 4x | import { useCallback, useSyncExternalStore } from "react";
function getReactNativeWebView(): typeof window.ReactNativeWebView {
Iif (typeof window !== "undefined" && window.ReactNativeWebView) {
return window.ReactNativeWebView;
}
}
function isNativeEmbed(): boolean {
const webview = getReactNativeWebView();
if (!webview) return false;
// Android-reliable detection: Check injectedObjectJson() which is more reliable than
// just checking for ReactNativeWebView existence due to timing issues on Android
try {
Iif (webview.injectedObjectJson) {
const injectedData = JSON.parse(webview.injectedObjectJson());
Iif (injectedData?.isNativeEmbed) return true;
}
} catch {
// Parsing failed or method not available - fall through to default behavior
}
// iOS and fallback: If ReactNativeWebView exists, assume we're in native embed
return true;
}
export function useIsNativeEmbed(): boolean {
return useSyncExternalStore(
// Subscribe function - no-op since value doesn't change after initial load
() => () => {},
// Client snapshot - check if we're in native embed
() => isNativeEmbed(),
// Server snapshot - always false during SSR
() => false,
);
}
type MessageType = "sendState" | "clearState" | "REQUEST_IMAGE_PICK";
function sendToNative(type: MessageType, data: unknown) {
if (!isNativeEmbed()) return;
getReactNativeWebView()!.postMessage(
JSON.stringify({ type: type, data: data }),
);
}
export function sendState<T>(key: string, value: T) {
sendToNative("sendState", { key: key, value: value });
}
export function clearState(key: string) {
sendToNative("clearState", { key: key });
}
// Image picker bridge for native mobile app
export type ImagePickResult =
| { success: true; imageBase64: string; mimeType: string }
| { success: false; canceled?: boolean; error?: string };
type ImagePickCallback = (result: ImagePickResult) => void;
let imagePickCallback: ImagePickCallback | null = null;
// Handle messages from native app
if (typeof window !== "undefined") {
window.addEventListener("message", (event) => {
try {
const data =
typeof event.data === "string" ? JSON.parse(event.data) : event.data;
Iif (data?.type === "IMAGE_PICK_RESULT" && imagePickCallback) {
imagePickCallback(data.result);
imagePickCallback = null;
}
} catch {
// Ignore non-JSON messages
}
});
}
export function requestNativeImagePick(callback: ImagePickCallback) {
Iif (!isNativeEmbed()) {
callback({ success: false, error: "Not in native app" });
return;
}
imagePickCallback = callback;
sendToNative("REQUEST_IMAGE_PICK", {});
}
// Hook for components to use
export function useNativeImagePicker() {
const isNative = useIsNativeEmbed();
const pickImage = useCallback((): Promise<ImagePickResult> => {
return new Promise((resolve) => {
requestNativeImagePick(resolve);
});
}, []);
return { isNative, pickImage };
}
// Helper to convert base64 to File object
export function base64ToFile(
base64: string,
mimeType: string,
filename: string,
): File {
const byteString = atob(base64);
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ab], { type: mimeType });
return new File([blob], filename, { type: mimeType });
}
|