diff --git a/desktop/src/lib/__tests__/formPreviewBridge.test.ts b/desktop/src/lib/__tests__/formPreviewBridge.test.ts index d56255572..4df7f2796 100644 --- a/desktop/src/lib/__tests__/formPreviewBridge.test.ts +++ b/desktop/src/lib/__tests__/formPreviewBridge.test.ts @@ -208,6 +208,36 @@ describe('handleFormPreviewBridgeMessage', () => { expect(payload.result).toBe('nested-synthetic-id'); }); + it('allocateSequence honors peek (nested in options) without advancing the counter', async () => { + const postMessage = vi.fn(); + const cw = { postMessage } as unknown as Window; + const iframe = { contentWindow: cw } as HTMLIFrameElement; + + const allocate = async ( + options: Record | undefined, + messageId: string, + ) => { + await handleFormPreviewBridgeMessage( + bridgeMessageFromIframe(iframe, { + type: 'allocateSequence', + messageId, + scopeKey: 'tb:peek-test:role:A:af', + options, + }), + { iframe, onFinalize: async () => ({ error: 'no' }) }, + ); + const calls = postMessage.mock.calls; + const payload = JSON.parse(calls[calls.length - 1][0] as string); + return payload.result as number; + }; + + // Consume -> 1, peek -> 2 (no advance), consume -> 2. + expect(await allocate(undefined, 'seq1')).toBe(1); + expect(await allocate({ peek: true }, 'seq2')).toBe(2); + expect(await allocate({ peek: true }, 'seq3')).toBe(2); + expect(await allocate(undefined, 'seq4')).toBe(2); + }); + it('stubs requestCamera with prefixed error', async () => { const postMessage = vi.fn(); const cw = { postMessage } as unknown as Window; diff --git a/desktop/src/lib/formPreviewBridge.ts b/desktop/src/lib/formPreviewBridge.ts index 1bb5583ab..af03ac3d1 100644 --- a/desktop/src/lib/formPreviewBridge.ts +++ b/desktop/src/lib/formPreviewBridge.ts @@ -134,6 +134,7 @@ export type FormPreviewBridgeContext = { formType: string; params: Record; savedData: Record; + observationId?: string; }) => void; /** * Form preview: defer `openFormplayer_response` until nested finalize/cancel. @@ -386,9 +387,16 @@ export async function handleFormPreviewBridgeMessage( : typeof data.payload === 'string' ? data.payload : ''; + // Injection sends options nested under `options`; fall back to flat + // top-level fields for back-compat with older callers. + const opts = ( + data.options && typeof data.options === 'object' + ? (data.options as Record) + : data + ) as { startAt?: unknown; peek?: unknown }; const startAt = - typeof data.startAt === 'number' ? data.startAt : undefined; - const peek = Boolean(data.peek); + typeof opts.startAt === 'number' ? opts.startAt : undefined; + const peek = Boolean(opts.peek); const result = previewAllocateSequence(scopeKey, { startAt, peek }); reply('allocateSequence', { result }); return; @@ -416,9 +424,14 @@ export async function handleFormPreviewBridgeMessage( subObservationMode?: boolean; skipFinalize?: boolean; skipDraftSelection?: boolean; + observationId?: string | null; } | undefined; const subObservationMode = Boolean(options?.subObservationMode); + const observationId = + typeof options?.observationId === 'string' + ? options.observationId.trim() + : ''; if (subObservationMode && ctx.onDeferOpenSubObservation) { const parentIframe = resolveBridgeReplyIframe(eventSource, ctx); @@ -448,7 +461,12 @@ export async function handleFormPreviewBridgeMessage( } if (ctx.onOpenFormplayerNavigate) { - ctx.onOpenFormplayerNavigate({ formType, params, savedData }); + ctx.onOpenFormplayerNavigate({ + formType, + params, + savedData, + ...(observationId ? { observationId } : {}), + }); reply('openFormplayer', { result: { status: 'cancelled', diff --git a/desktop/src/pages/WorkbenchCustomAppPage.tsx b/desktop/src/pages/WorkbenchCustomAppPage.tsx index 5261ed4ce..a832707f7 100644 --- a/desktop/src/pages/WorkbenchCustomAppPage.tsx +++ b/desktop/src/pages/WorkbenchCustomAppPage.tsx @@ -57,14 +57,20 @@ export function WorkbenchCustomAppPage() { formType: string; params: Record; savedData: Record; + observationId?: string; }) => { + const explicitId = + typeof payload.observationId === 'string' + ? payload.observationId.trim() + : ''; const sd = payload.savedData; const observationId = - typeof sd.observationId === 'string' + explicitId || + (typeof sd.observationId === 'string' ? sd.observationId : typeof sd.id === 'string' ? sd.id - : ''; + : ''); navigate('/workbench/form-preview', { state: { formPreviewEdit: { diff --git a/formulus-formplayer/src/renderers/SubObservationQuestionRenderer.tsx b/formulus-formplayer/src/renderers/SubObservationQuestionRenderer.tsx index d8a4b18f1..e08130a0c 100644 --- a/formulus-formplayer/src/renderers/SubObservationQuestionRenderer.tsx +++ b/formulus-formplayer/src/renderers/SubObservationQuestionRenderer.tsx @@ -238,6 +238,42 @@ const SubObservationQuestionRendererInner: React.FC = ({ ], ); + const refreshSubObservationWindowContext = useCallback( + (rows: Record[]) => { + const root = jsonForms.core?.data; + if (!root || typeof root !== 'object' || !path) return; + const parentData = writeDataPath( + root as Record, + path, + rows, + ); + const pv = resolveParentValue(formData, parentValuePath); + const contextTemplate = optionalRecordMap(config.subObservationContext); + const mergeConfigRaw = config.subObservationContextMerge; + const mergeConfig = + mergeConfigRaw && typeof mergeConfigRaw === 'object' + ? mergeConfigRaw + : undefined; + if (contextTemplate || mergeConfig) { + const refreshed = refreshSubObservationContextFromFormData( + parentData, + contextTemplate, + pv, + mergeConfig as SubObservationContextMergeConfig, + ); + writeSubObservationContextToWindow(refreshed); + } + }, + [ + jsonForms.core?.data, + path, + formData, + parentValuePath, + config.subObservationContext, + config.subObservationContextMerge, + ], + ); + const mergeSubmittedRow = useCallback( (result: FormCompletionResult) => { if ( @@ -249,43 +285,10 @@ const SubObservationQuestionRendererInner: React.FC = ({ const row = result.formData as Record; const next = [...getCurrentRows(), row]; pushSorted(next); - - const root = jsonForms.core?.data; - if (root && typeof root === 'object' && path) { - const parentData = writeDataPath( - root as Record, - path, - next, - ); - const pv = resolveParentValue(formData, parentValuePath); - const contextTemplate = optionalRecordMap(config.subObservationContext); - const mergeConfigRaw = config.subObservationContextMerge; - const mergeConfig = - mergeConfigRaw && typeof mergeConfigRaw === 'object' - ? mergeConfigRaw - : undefined; - if (contextTemplate || mergeConfig) { - const refreshed = refreshSubObservationContextFromFormData( - parentData, - contextTemplate, - pv, - mergeConfig as SubObservationContextMergeConfig, - ); - writeSubObservationContextToWindow(refreshed); - } - } + refreshSubObservationWindowContext(next); return true; }, - [ - getCurrentRows, - pushSorted, - jsonForms.core?.data, - path, - formData, - parentValuePath, - config.subObservationContext, - config.subObservationContextMerge, - ], + [getCurrentRows, pushSorted, refreshSubObservationWindowContext], ); const handleAdd = useCallback(async () => { @@ -371,31 +374,7 @@ const SubObservationQuestionRendererInner: React.FC = ({ i === index ? (result.formData as Record) : r, ); pushSorted(updated); - const root = jsonForms.core?.data; - if (root && typeof root === 'object' && path) { - const parentData = writeDataPath( - root as Record, - path, - updated, - ); - const contextTemplate = optionalRecordMap( - config.subObservationContext, - ); - const mergeConfigRaw = config.subObservationContextMerge; - const mergeConfig = - mergeConfigRaw && typeof mergeConfigRaw === 'object' - ? mergeConfigRaw - : undefined; - if (contextTemplate || mergeConfig) { - const refreshed = refreshSubObservationContextFromFormData( - parentData, - contextTemplate, - pv, - mergeConfig as SubObservationContextMergeConfig, - ); - writeSubObservationContextToWindow(refreshed); - } - } + refreshSubObservationWindowContext(updated); } } catch (e) { setError( @@ -414,6 +393,7 @@ const SubObservationQuestionRendererInner: React.FC = ({ config, getCurrentRows, pushSorted, + refreshSubObservationWindowContext, ], ); diff --git a/formulus-formplayer/src/renderers/subObservationHelpers.test.ts b/formulus-formplayer/src/renderers/subObservationHelpers.test.ts index f7a4a428b..4779fbeed 100644 --- a/formulus-formplayer/src/renderers/subObservationHelpers.test.ts +++ b/formulus-formplayer/src/renderers/subObservationHelpers.test.ts @@ -40,6 +40,35 @@ describe('subObservationHelpers', () => { expect(merged.person_codigos).toEqual([]); }); + it('mergePreservingSubObsArrays keeps off-page scalar prefills and autoSequence fields', () => { + // SwipeLayout partial payload: only the current page's field is present. + const baseline = { + obsdate: '2026-06-19', + region: 'Bafata', + tb: 'TB01', + device_role: 'A', + num: 7, + }; + const incoming = { nome_chefe: 'Maria' }; + const merged = mergePreservingSubObsArrays(baseline, incoming); + expect(merged.nome_chefe).toBe('Maria'); + expect(merged.obsdate).toBe('2026-06-19'); + expect(merged.region).toBe('Bafata'); + expect(merged.tb).toBe('TB01'); + expect(merged.device_role).toBe('A'); + // Critical: a directly-used x-autoSequence value must survive partial + // payloads so applyAutoSequences does not re-allocate it. + expect(merged.num).toBe(7); + }); + + it('mergePreservingSubObsArrays lets on-page edits override baseline', () => { + const baseline = { nome_chefe: 'Maria', num: 7 }; + const incoming = { nome_chefe: 'João' }; + const merged = mergePreservingSubObsArrays(baseline, incoming); + expect(merged.nome_chefe).toBe('João'); + expect(merged.num).toBe(7); + }); + it('formDataJsonEqual compares stable JSON snapshots', () => { expect(formDataJsonEqual({ a: 1 }, { a: 1 })).toBe(true); expect(formDataJsonEqual({ a: 1 }, { a: 2 })).toBe(false); diff --git a/formulus-formplayer/src/renderers/subObservationHelpers.ts b/formulus-formplayer/src/renderers/subObservationHelpers.ts index a936df1e1..98e8c465f 100644 --- a/formulus-formplayer/src/renderers/subObservationHelpers.ts +++ b/formulus-formplayer/src/renderers/subObservationHelpers.ts @@ -132,16 +132,21 @@ export function formDataJsonEqual( } /** - * Keep embedded sub-obs arrays when JsonForms emits partial onChange payloads. - * SwipeLayout only mounts the current page, so controls on other pages (e.g. - * `quartos`) are absent from `incoming` even though they still live in our - * baseline draft state. + * Keep baseline values when JsonForms emits partial onChange payloads. + * SwipeLayout only mounts the current page, so controls on other pages are + * absent from `incoming` even though they still live in our baseline draft + * state. Starting from `{ ...baseline, ...incoming }` preserves off-page + * scalars (host prefills like cluster stamps / obsdate, and directly-used + * `x-autoSequence` fields) so they are not dropped and re-allocated. On-page + * edits (including clearing a field) still override baseline via `incoming`. + * Sub-observation arrays get extra protection below against shorter/partial + * payloads. */ export function mergePreservingSubObsArrays( baseline: Record, incoming: Record, ): Record { - const merged = { ...incoming }; + const merged = { ...baseline, ...incoming }; for (const key of PRESERVED_ARRAY_KEYS) { const baseArr = baseline[key]; if (!(key in incoming)) { diff --git a/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts b/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts index a4028f8f5..3be0c2205 100644 --- a/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts +++ b/formulus-formplayer/src/types/FormulusInterfaceDefinition.ts @@ -394,6 +394,9 @@ export interface FormulusInterface { * `window.formulusSessionContext`. Draft bypass is not a param key — use * `options.skipDraftSelection` on {@link openFormplayer} (same as `skipFinalize`). * @param {Object} savedData - Previously saved form data (for editing) + * @param {Object} [options] - Session options (not persisted as observation data) + * @param {string|null} [options.observationId] - When set, finalize updates this + * observation instead of creating a new one * @returns {Promise} Promise that resolves when the form is completed/closed with result details */ openFormplayer( @@ -404,6 +407,7 @@ export interface FormulusInterface { subObservationMode?: boolean; skipFinalize?: boolean; skipDraftSelection?: boolean; + observationId?: string | null; }, ): Promise; diff --git a/formulus/assets/webview/FormulusInjectionScript.js b/formulus/assets/webview/FormulusInjectionScript.js index 7207daf0d..5c071dd1e 100644 --- a/formulus/assets/webview/FormulusInjectionScript.js +++ b/formulus/assets/webview/FormulusInjectionScript.js @@ -1,51 +1,38 @@ // Auto-generated from FormulusInterfaceDefinition.ts // Do not edit directly - this file will be overwritten -// Last generated: 2026-06-19T12:32:54.430Z +// Last generated: 2026-06-19T16:58:11.324Z -(function () { +(function() { // Enhanced API availability detection and recovery function getFormulus() { // Check multiple locations where the API might exist - return ( - globalThis.formulus || - (typeof window !== 'undefined' ? window.formulus : undefined) - ); + return globalThis.formulus || (typeof window !== 'undefined' ? window.formulus : undefined); } function isFormulusAvailable() { const api = getFormulus(); - return ( - api && typeof api === 'object' && typeof api.getVersion === 'function' - ); + return api && typeof api === 'object' && typeof api.getVersion === 'function'; } // Idempotent guard to avoid double-initialization when scripts are reinjected - if (globalThis.__formulusBridgeInitialized) { + if ((globalThis).__formulusBridgeInitialized) { if (isFormulusAvailable()) { - console.debug( - 'Formulus bridge already initialized and functional. Skipping duplicate injection.', - ); + console.debug('Formulus bridge already initialized and functional. Skipping duplicate injection.'); return; } else { - console.warn( - 'Formulus bridge flag is set but API is not functional. Proceeding with re-injection...', - ); + console.warn('Formulus bridge flag is set but API is not functional. Proceeding with re-injection...'); } } // If API already exists and is functional, skip injection if (isFormulusAvailable()) { - console.debug( - 'Formulus interface already exists and is functional. Skipping injection.', - ); + console.debug('Formulus interface already exists and is functional. Skipping injection.'); return; } // If API exists but is not functional, log warning and proceed with re-injection if (getFormulus()) { - console.warn( - 'Formulus interface exists but appears non-functional. Re-injecting...', - ); + console.warn('Formulus interface exists but appears non-functional. Re-injecting...'); } // Helper function to handle callbacks @@ -61,7 +48,7 @@ // Initialize callbacks const callbacks = {}; - + // Global function to handle responses from React Native function handleMessage(event) { try { @@ -74,2087 +61,1625 @@ // console.warn('Global handleMessage: Received message with unexpected data type:', typeof event.data, event.data); return; // Or handle error, but for now, just return to avoid breaking others. } - + // Handle callbacks - if ( - data.type === 'callback' && - data.callbackId && - callbacks[data.callbackId] - ) { + if (data.type === 'callback' && data.callbackId && callbacks[data.callbackId]) { handleCallback(callbacks[data.callbackId], data.data); delete callbacks[data.callbackId]; } - + // Handle specific callbacks - - if ( - data.type === 'onFormulusReady' && - globalThis.formulusCallbacks?.onFormulusReady - ) { + + + if (data.type === 'onFormulusReady' && globalThis.formulusCallbacks?.onFormulusReady) { handleCallback(globalThis.formulusCallbacks.onFormulusReady); } } catch (e) { - console.error( - 'Global handleMessage: Error processing message:', - e, - 'Raw event.data:', - event.data, - ); + console.error('Global handleMessage: Error processing message:', e, 'Raw event.data:', event.data); } } - + // Set up message listener document.addEventListener('message', handleMessage); window.addEventListener('message', handleMessage); // Initialize the formulus interface globalThis.formulus = { - // getVersion: => Promise - getVersion: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getVersion callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getVersion callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getVersion_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + // getVersion: => Promise + getVersion: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getVersion callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getVersion callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getVersion' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getVersion_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getVersion' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getVersion', messageId, - }), - ); - }); - }, - - // getAvailableForms: => Promise - getAvailableForms: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getAvailableForms callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getAvailableForms callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getAvailableForms_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // getAvailableForms: => Promise + getAvailableForms: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getAvailableForms callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getAvailableForms callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getAvailableForms' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getAvailableForms_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getAvailableForms' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getAvailableForms', messageId, - }), - ); - }); - }, - - // openFormplayer: formType: string, params: Record, savedData: Record, options: { subObservationMode?: boolean; skipFinalize?: boolean; skipDraftSelection?: boolean; } => Promise - openFormplayer: function (formType, params, savedData, options) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('openFormplayer callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'openFormplayer callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'openFormplayer_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // openFormplayer: formType: string, params: Record, savedData: Record, options: { subObservationMode?: boolean; skipFinalize?: boolean; skipDraftSelection?: boolean; observationId?: string; } => Promise + openFormplayer: function(formType, params, savedData, options) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('openFormplayer callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('openFormplayer callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'openFormplayer' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'openFormplayer_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'openFormplayer' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'openFormplayer', messageId, - formType: formType, + formType: formType, params: params, savedData: savedData, - options: options, - }), - ); - }); - }, - - // getObservations: formType: string, isDraft: boolean, includeDeleted: boolean => Promise - getObservations: function (formType, isDraft, includeDeleted) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getObservations callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getObservations callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getObservations_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + options: options + })); + + }); + }, + + // getObservations: formType: string, isDraft: boolean, includeDeleted: boolean => Promise + getObservations: function(formType, isDraft, includeDeleted) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getObservations callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getObservations callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getObservations' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getObservations_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getObservations' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getObservations', messageId, - formType: formType, + formType: formType, isDraft: isDraft, - includeDeleted: includeDeleted, - }), - ); - }); - }, - - // getObservationsByQuery: options: { formType: string; isDraft?: boolean; includeDeleted?: boolean; filter?: ObservationFilter; whereClause?: string; } => Promise - getObservationsByQuery: function (options) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getObservationsByQuery callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getObservationsByQuery callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getObservationsByQuery_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + includeDeleted: includeDeleted + })); + + }); + }, + + // getObservationsByQuery: options: { formType: string; isDraft?: boolean; includeDeleted?: boolean; filter?: ObservationFilter; whereClause?: string; } => Promise + getObservationsByQuery: function(options) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getObservationsByQuery callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getObservationsByQuery callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getObservationsByQuery' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getObservationsByQuery_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getObservationsByQuery' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getObservationsByQuery', messageId, - options: options, - }), - ); - }); - }, - - // submitObservation: formType: string, finalData: Record => Promise - submitObservation: function (formType, finalData) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('submitObservation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'submitObservation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'submitObservation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + options: options + })); + + }); + }, + + // submitObservation: formType: string, finalData: Record => Promise + submitObservation: function(formType, finalData) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('submitObservation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('submitObservation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'submitObservation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'submitObservation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'submitObservation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'submitObservation', messageId, - formType: formType, - finalData: finalData, - }), - ); - }); - }, - - // updateObservation: observationId: string, formType: string, finalData: Record => Promise - updateObservation: function (observationId, formType, finalData) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('updateObservation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'updateObservation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'updateObservation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + formType: formType, + finalData: finalData + })); + + }); + }, + + // updateObservation: observationId: string, formType: string, finalData: Record => Promise + updateObservation: function(observationId, formType, finalData) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('updateObservation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('updateObservation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'updateObservation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'updateObservation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'updateObservation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'updateObservation', messageId, - observationId: observationId, + observationId: observationId, formType: formType, - finalData: finalData, - }), - ); - }); - }, - - // requestCamera: fieldId: string => Promise - requestCamera: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestCamera callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestCamera callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestCamera_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + finalData: finalData + })); + + }); + }, + + // requestCamera: fieldId: string => Promise + requestCamera: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestCamera callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestCamera callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestCamera' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestCamera_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestCamera' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestCamera', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // requestLocation: fieldId: string => Promise - requestLocation: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestLocation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestLocation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestLocation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // requestLocation: fieldId: string => Promise + requestLocation: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestLocation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestLocation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestLocation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestLocation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestLocation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestLocation', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // getCachedLocation: fieldId: string => Promise - getCachedLocation: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getCachedLocation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getCachedLocation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getCachedLocation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // getCachedLocation: fieldId: string => Promise + getCachedLocation: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getCachedLocation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getCachedLocation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getCachedLocation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getCachedLocation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getCachedLocation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getCachedLocation', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // allocateSequence: scopeKey: string, options: { startAt?: number; peek?: boolean; } => Promise - allocateSequence: function (scopeKey, options) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('allocateSequence callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'allocateSequence callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'allocateSequence_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // allocateSequence: scopeKey: string, options: { startAt?: number; peek?: boolean; } => Promise + allocateSequence: function(scopeKey, options) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('allocateSequence callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('allocateSequence callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'allocateSequence' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'allocateSequence_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'allocateSequence' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'allocateSequence', messageId, - scopeKey: scopeKey, - options: options, - }), - ); - }); - }, - - // watchLocation: fieldId: string => Promise<{ status: "started" | "error"; message?: string; }> - watchLocation: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('watchLocation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'watchLocation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'watchLocation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + scopeKey: scopeKey, + options: options + })); + + }); + }, + + // watchLocation: fieldId: string => Promise<{ status: "started" | "error"; message?: string; }> + watchLocation: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('watchLocation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('watchLocation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'watchLocation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'watchLocation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'watchLocation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'watchLocation', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // stopWatchLocation: fieldId: string => Promise - stopWatchLocation: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('stopWatchLocation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'stopWatchLocation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'stopWatchLocation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // stopWatchLocation: fieldId: string => Promise + stopWatchLocation: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('stopWatchLocation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('stopWatchLocation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'stopWatchLocation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'stopWatchLocation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'stopWatchLocation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'stopWatchLocation', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // requestFile: fieldId: string => Promise - requestFile: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestFile callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestFile callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestFile_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // requestFile: fieldId: string => Promise + requestFile: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestFile callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestFile callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestFile' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestFile_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestFile' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestFile', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // launchIntent: fieldId: string, intentSpec: Record => Promise - launchIntent: function (fieldId, intentSpec) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('launchIntent callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'launchIntent callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'launchIntent_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // launchIntent: fieldId: string, intentSpec: Record => Promise + launchIntent: function(fieldId, intentSpec) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('launchIntent callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('launchIntent callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'launchIntent' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'launchIntent_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'launchIntent' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'launchIntent', messageId, - fieldId: fieldId, - intentSpec: intentSpec, - }), - ); - }); - }, - - // callSubform: fieldId: string, formType: string, options: Record => Promise - callSubform: function (fieldId, formType, options) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('callSubform callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'callSubform callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'callSubform_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId, + intentSpec: intentSpec + })); + + }); + }, + + // callSubform: fieldId: string, formType: string, options: Record => Promise + callSubform: function(fieldId, formType, options) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('callSubform callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('callSubform callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'callSubform' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'callSubform_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'callSubform' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'callSubform', messageId, - fieldId: fieldId, + fieldId: fieldId, formType: formType, - options: options, - }), - ); - }); - }, - - // requestAudio: fieldId: string => Promise - requestAudio: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestAudio callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestAudio callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestAudio_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + options: options + })); + + }); + }, + + // requestAudio: fieldId: string => Promise + requestAudio: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestAudio callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestAudio callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestAudio' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestAudio_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestAudio' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestAudio', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // requestVideo: fieldId: string => Promise - requestVideo: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestVideo callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestVideo callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestVideo_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // requestVideo: fieldId: string => Promise + requestVideo: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestVideo callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestVideo callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestVideo' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestVideo_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestVideo' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestVideo', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // requestQrcode: fieldId: string => Promise - requestQrcode: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestQrcode callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestQrcode callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestQrcode_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // requestQrcode: fieldId: string => Promise + requestQrcode: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestQrcode callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestQrcode callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestQrcode' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestQrcode_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestQrcode' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestQrcode', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // requestBiometric: fieldId: string => Promise - requestBiometric: function (fieldId) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestBiometric callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestBiometric callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestBiometric_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // requestBiometric: fieldId: string => Promise + requestBiometric: function(fieldId) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestBiometric callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestBiometric callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestBiometric' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestBiometric_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestBiometric' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestBiometric', messageId, - fieldId: fieldId, - }), - ); - }); - }, - - // requestConnectivityStatus: => Promise - requestConnectivityStatus: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestConnectivityStatus callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestConnectivityStatus callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestConnectivityStatus_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fieldId: fieldId + })); + + }); + }, + + // requestConnectivityStatus: => Promise + requestConnectivityStatus: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestConnectivityStatus callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestConnectivityStatus callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestConnectivityStatus' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestConnectivityStatus_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestConnectivityStatus' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestConnectivityStatus', messageId, - }), - ); - }); - }, - - // requestSyncStatus: => Promise - requestSyncStatus: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('requestSyncStatus callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'requestSyncStatus callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'requestSyncStatus_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // requestSyncStatus: => Promise + requestSyncStatus: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('requestSyncStatus callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('requestSyncStatus callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'requestSyncStatus' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'requestSyncStatus_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'requestSyncStatus' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'requestSyncStatus', messageId, - }), - ); - }); - }, - - // runLocalModel: fieldId: string, modelId: string, input: Record => Promise - runLocalModel: function (fieldId, modelId, input) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('runLocalModel callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'runLocalModel callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'runLocalModel_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // runLocalModel: fieldId: string, modelId: string, input: Record => Promise + runLocalModel: function(fieldId, modelId, input) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('runLocalModel callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('runLocalModel callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'runLocalModel' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'runLocalModel_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'runLocalModel' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'runLocalModel', messageId, - fieldId: fieldId, + fieldId: fieldId, modelId: modelId, - input: input, - }), - ); - }); - }, - - // getCurrentUser: => Promise<{ username: string; displayName?: string; role?: "read-only" | "read-write" | "admin"; }> - getCurrentUser: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getCurrentUser callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getCurrentUser callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getCurrentUser_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + input: input + })); + + }); + }, + + // getCurrentUser: => Promise<{ username: string; displayName?: string; role?: "read-only" | "read-write" | "admin"; }> + getCurrentUser: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getCurrentUser callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getCurrentUser callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getCurrentUser' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getCurrentUser_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getCurrentUser' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getCurrentUser', messageId, - }), - ); - }); - }, - - // getThemeMode: => Promise<"light" | "dark" | "system"> - getThemeMode: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getThemeMode callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getThemeMode callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getThemeMode_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // getThemeMode: => Promise<"light" | "dark" | "system"> + getThemeMode: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getThemeMode callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getThemeMode callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getThemeMode' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getThemeMode_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getThemeMode' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getThemeMode', messageId, - }), - ); - }); - }, - - // getAttachmentUri: fileName: string | AttachmentDisplayDescriptor => Promise - getAttachmentUri: function (fileName) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getAttachmentUri callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getAttachmentUri callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getAttachmentUri_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // getAttachmentUri: fileName: string | AttachmentDisplayDescriptor => Promise + getAttachmentUri: function(fileName) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getAttachmentUri callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getAttachmentUri callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getAttachmentUri' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getAttachmentUri_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getAttachmentUri' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getAttachmentUri', messageId, - fileName: fileName, - }), - ); - }); - }, - - // getAttachmentsUri: => Promise - getAttachmentsUri: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getAttachmentsUri callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getAttachmentsUri callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getAttachmentsUri_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + fileName: fileName + })); + + }); + }, + + // getAttachmentsUri: => Promise + getAttachmentsUri: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getAttachmentsUri callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getAttachmentsUri callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getAttachmentsUri' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getAttachmentsUri_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getAttachmentsUri' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getAttachmentsUri', messageId, - }), - ); - }); - }, - - // getCustomAppUri: => Promise - getCustomAppUri: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getCustomAppUri callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getCustomAppUri callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getCustomAppUri_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // getCustomAppUri: => Promise + getCustomAppUri: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getCustomAppUri callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getCustomAppUri callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getCustomAppUri' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getCustomAppUri_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getCustomAppUri' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getCustomAppUri', messageId, - }), - ); - }); - }, - - // getFormSpecsUri: => Promise - getFormSpecsUri: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getFormSpecsUri callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getFormSpecsUri callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getFormSpecsUri_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // getFormSpecsUri: => Promise + getFormSpecsUri: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getFormSpecsUri callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getFormSpecsUri callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getFormSpecsUri' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getFormSpecsUri_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getFormSpecsUri' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getFormSpecsUri', messageId, - }), - ); - }); - }, - - // persistObservation: input: PersistObservationInput => Promise - persistObservation: function (input) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('persistObservation callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'persistObservation callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'persistObservation_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // persistObservation: input: PersistObservationInput => Promise + persistObservation: function(input) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('persistObservation callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('persistObservation callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'persistObservation' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'persistObservation_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'persistObservation' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'persistObservation', messageId, - input: input, - }), - ); - }); - }, - - // sync: options: { includeAttachments?: boolean; } => Promise - sync: function (options) { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('sync callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'sync callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if (data.type === 'sync_response' && data.messageId === messageId) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + input: input + })); + + }); + }, + + // sync: options: { includeAttachments?: boolean; } => Promise + sync: function(options) { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('sync callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('sync callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'sync' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'sync_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'sync' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'sync', messageId, - options: options, - }), - ); - }); - }, - - // getConnectivityStatus: => Promise - getConnectivityStatus: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getConnectivityStatus callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getConnectivityStatus callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getConnectivityStatus_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + options: options + })); + + }); + }, + + // getConnectivityStatus: => Promise + getConnectivityStatus: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getConnectivityStatus callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getConnectivityStatus callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getConnectivityStatus' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getConnectivityStatus_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getConnectivityStatus' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getConnectivityStatus', messageId, - }), - ); - }); - }, - - // getCurrentDataRevisionCount: => Promise - getCurrentDataRevisionCount: function () { - return new Promise((resolve, reject) => { - const messageId = - 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); - - // Add response handler for methods that return values - - const callback = event => { - try { - let data; - if (typeof event.data === 'string') { - data = JSON.parse(event.data); - } else if (typeof event.data === 'object' && event.data !== null) { - data = event.data; // Already an object - } else { - // console.warn('getCurrentDataRevisionCount callback: Received response with unexpected data type:', typeof event.data, event.data); - window.removeEventListener('message', callback); // Clean up listener - reject( - new Error( - 'getCurrentDataRevisionCount callback: Received response with unexpected data type. Raw: ' + - String(event.data), - ), - ); - return; - } - if ( - data.type === 'getCurrentDataRevisionCount_response' && - data.messageId === messageId - ) { - window.removeEventListener('message', callback); - if (data.error) { - reject(new Error(data.error)); + + })); + + }); + }, + + // getCurrentDataRevisionCount: => Promise + getCurrentDataRevisionCount: function() { + return new Promise((resolve, reject) => { + const messageId = 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000); + + // Add response handler for methods that return values + + const callback = (event) => { + try { + let data; + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + } else if (typeof event.data === 'object' && event.data !== null) { + data = event.data; // Already an object } else { - resolve(data.result); + // console.warn('getCurrentDataRevisionCount callback: Received response with unexpected data type:', typeof event.data, event.data); + window.removeEventListener('message', callback); // Clean up listener + reject(new Error('getCurrentDataRevisionCount callback: Received response with unexpected data type. Raw: ' + String(event.data))); + return; } - } - } catch (e) { - console.error( - "'getCurrentDataRevisionCount' callback: Error processing response:", - e, - 'Raw event.data:', - event.data, - ); - window.removeEventListener('message', callback); // Ensure listener is removed on error too - reject(e); - } - }; - window.addEventListener('message', callback); - - // Send the message to React Native - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ + if (data.type === 'getCurrentDataRevisionCount_response' && data.messageId === messageId) { + window.removeEventListener('message', callback); + if (data.error) { + reject(new Error(data.error)); + } else { + resolve(data.result); + } + } + } catch (e) { + console.error("'getCurrentDataRevisionCount' callback: Error processing response:" , e, "Raw event.data:", event.data); + window.removeEventListener('message', callback); // Ensure listener is removed on error too + reject(e); + } + }; + window.addEventListener('message', callback); + + + // Send the message to React Native + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ type: 'getCurrentDataRevisionCount', messageId, - }), - ); - }); - }, + + })); + + }); + }, }; - + // Register the callback handler with the window object globalThis.formulusCallbacks = {}; - + // Notify that the interface is ready console.log('Formulus interface initialized'); - globalThis.__formulusBridgeInitialized = true; + (globalThis).__formulusBridgeInitialized = true; // Simple API availability check for internal use function requestApiReinjection() { console.log('Formulus: Requesting re-injection from host...'); if (globalThis.ReactNativeWebView) { - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ - type: 'requestApiReinjection', - timestamp: Date.now(), - }), - ); + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ + type: 'requestApiReinjection', + timestamp: Date.now() + })); } } globalThis.__formulusRequestApiReinjection = requestApiReinjection; // Notify React Native that the interface is ready if (globalThis.ReactNativeWebView) { - globalThis.ReactNativeWebView.postMessage( - JSON.stringify({ - type: 'onFormulusReady', - }), - ); + globalThis.ReactNativeWebView.postMessage(JSON.stringify({ + type: 'onFormulusReady' + })); } - + // Make the API available globally in browser environments if (typeof window !== 'undefined') { window.formulus = globalThis.formulus; } + })(); diff --git a/formulus/assets/webview/formulus-api.js b/formulus/assets/webview/formulus-api.js index 71dec8160..2aee71918 100644 --- a/formulus/assets/webview/formulus-api.js +++ b/formulus/assets/webview/formulus-api.js @@ -5,7 +5,7 @@ * that's available in the WebView context as `globalThis.formulus`. * * This file is auto-generated from FormulusInterfaceDefinition.ts - * Last generated: 2026-06-19T12:32:54.875Z + * Last generated: 2026-06-19T16:58:11.756Z * * @example * // In your JavaScript file: @@ -31,14 +31,12 @@ const FormulusAPI = { /** * Get the current version of the Formulus bridge API (the interface contract * version, e.g. for {@link isCompatibleVersion} checks). - * / * @returns {Promise} The API version (semver) */ getVersion: function() {}, /** * Get a list of available forms - * / * @returns {Promise} Array of form information objects */ getAvailableForms: function() {}, @@ -51,7 +49,7 @@ const FormulusAPI = { * ...) that Formplayer never persists and exposes to extensions as * `window.formulusSessionContext`. Draft bypass is not a param key — use * `options.skipDraftSelection` on {@link openFormplayer} (same as `skipFinalize`). - * / + * observation instead of creating a new one * @param {string} formType - The identifier of the formtype to open * @param {Object} params - Additional parameters for form initialization. * @param {Object} savedData - Previously saved form data (for editing) @@ -61,7 +59,6 @@ const FormulusAPI = { /** * Get observations for a specific form - * / * @param {string} formType - The identifier of the formtype * @returns {Promise} Array of form observations */ @@ -71,14 +68,12 @@ const FormulusAPI = { * Get observations with optional WHERE clause filtering (for dynamic choice lists). * Supports format: data.field = 'value' AND data.other = 'value' * Age filtering via age_from_dob(data.dob) is handled client-side in formplayer. - * / * @returns {Promise} Array of filtered observations */ getObservationsByQuery: function(options) {}, /** * Submit a completed form - * / * @param {string} formType - The identifier of the formtype * @param {Object} finalData - The final form data to submit * @returns {Promise} The observationId of the submitted form @@ -87,7 +82,6 @@ const FormulusAPI = { /** * Update an existing form - * / * @param {string} observationId - The identifier of the observation * @param {string} formType - The identifier of the formtype * @param {Object} finalData - The final form data to update @@ -97,7 +91,6 @@ const FormulusAPI = { /** * Request camera access for a field - * / * @param {string} fieldId - The ID of the field * @returns {Promise} Promise that resolves with camera result or rejects on error/cancellation */ @@ -105,7 +98,6 @@ const FormulusAPI = { /** * Request location for a field (captures into the form GPS field). - * / * @param {string} fieldId - The ID of the field * @returns {Promise} Promise that resolves with location result or rejects on error */ @@ -113,32 +105,28 @@ const FormulusAPI = { /** * Return the best cached device fix (if any) without forcing a new GPS read. - * / */ getCachedLocation: function(fieldId) {}, /** * Allocate the next monotonic integer in a device-local scope. * Formulus prepends `device:{deviceId}:` to the app-authored `scopeKey` suffix. - * / */ allocateSequence: function(scopeKey, options) {}, /** * Subscribe to location updates while the custom app WebView is active. * Updates arrive as `locationWatchUpdate` window messages. - * / */ watchLocation: function(fieldId) {}, /** - * /** Stop a prior `watchLocation` subscription for `fieldId`. */ + * Stop a prior `watchLocation` subscription for `fieldId`. */ stopWatchLocation: function(fieldId) {}, /** * Request file selection for a field - * / * @param {string} fieldId - The ID of the field * @returns {Promise} Promise that resolves with file result or rejects on error/cancellation */ @@ -146,7 +134,6 @@ const FormulusAPI = { /** * Launch an external intent - * / * @param {string} fieldId - The ID of the field * @param {Object} intentSpec - The intent specification * @returns {Promise} @@ -155,7 +142,6 @@ const FormulusAPI = { /** * Call a subform - * / * @param {string} fieldId - The ID of the field * @param {string} formType - The ID of the subform * @param {Object} options - Additional options for the subform @@ -165,7 +151,6 @@ const FormulusAPI = { /** * Request audio recording for a field - * / * @param {string} fieldId - The ID of the field * @returns {Promise} Promise that resolves with audio result or rejects on error/cancellation */ @@ -173,7 +158,6 @@ const FormulusAPI = { /** * Request video recording for a field (camera / picker — host-defined). - * / * @param {string} fieldId - The ID of the field * @returns {Promise} Promise that resolves with video result or rejects on error/cancellation */ @@ -181,7 +165,6 @@ const FormulusAPI = { /** * Request QR code scanning for a field - * / * @param {string} fieldId - The ID of the field * @returns {Promise} Promise that resolves with QR code result or rejects on error/cancellation */ @@ -189,7 +172,6 @@ const FormulusAPI = { /** * Request biometric authentication - * / * @param {string} fieldId - The ID of the field * @returns {Promise} */ @@ -197,21 +179,18 @@ const FormulusAPI = { /** * Request the current connectivity status - * / * @returns {Promise} */ requestConnectivityStatus: function() {}, /** * Request the current sync status - * / * @returns {Promise} */ requestSyncStatus: function() {}, /** * Run a local ML model - * / * @param {string} fieldId - The ID of the field * @param {string} modelId - The ID of the model to run * @param {Object} input - The input data for the model @@ -222,14 +201,12 @@ const FormulusAPI = { /** * Get information about the currently authenticated user. * When no one is logged in, resolves with `{ username: '' }` (does not reject). - * / * @returns {Promise<{username: string, displayName?: string, role?: 'read-only' | 'read-write' | 'admin'} */ getCurrentUser: function() {}, /** * Get the current theme mode (System / Light / Dark) so custom apps can match the host app. - * / * @returns {Promise<'light' | 'dark' | 'system'>} Current theme mode; 'system' means follow device preference. */ getThemeMode: function() {}, @@ -243,7 +220,6 @@ const FormulusAPI = { * Legacy locations (`attachments/` and `attachments/pending_upload/`) are also checked. * Path segments and ".." are rejected. * **`AttachmentDisplayDescriptor`:** `{ filename }` basename only (same lookup as a string argument). - * / * @returns {Promise} Display URL, or `null` if none */ getAttachmentUri: function(fileName) {}, @@ -257,14 +233,12 @@ const FormulusAPI = { * parent directory, which mixed committed files with `draft/` and * `pending_upload/` subfolders. Custom apps that iterate this URL will now * see only fully-committed attachments. - * / * @returns {Promise} e.g. `file:///.../attachments/synced/` */ getAttachmentsUri: function() {}, /** * Base `file://` URL for the custom app bundle root (`DocumentDirectory/app/`, trailing slash). - * / * @returns {Promise} App directory URL for extensions, question_types, etc. */ getCustomAppUri: function() {}, @@ -272,7 +246,6 @@ const FormulusAPI = { /** * Primary `file://` URL for downloaded form specs (`DocumentDirectory/forms/`, trailing slash). * Some bundles also use files under the custom app `forms/` subdirectory. - * / * @returns {Promise} Forms directory URL */ getFormSpecsUri: function() {}, @@ -283,7 +256,6 @@ const FormulusAPI = { * Uses the same persistence path as a Formplayer submit, including promotion * of any referenced draft attachments. Provide `observationId` to update an * existing row; omit it to create a new one. - * / * @since 1.3.0 * @param {PersistObservationInput} input - The observation to persist * @returns {Promise} The stored observation id and data @@ -294,7 +266,6 @@ const FormulusAPI = { * Trigger a synchronization with Synkronus (pull + push). * Resolves when the sync completes; rejects if a sync is already in progress * or the sync fails. Attachments are excluded by default for speed. - * / * @since 1.3.0 * @returns {Promise} The final data version after sync */ @@ -305,7 +276,6 @@ const FormulusAPI = { * Never rejects for an offline device — returns `{ online: false }` so callers * can branch on connectivity without try/catch. Suitable for the * "verify subject details when online, fall back when offline" pattern. - * / * @since 1.3.0 * @returns {Promise} The current connectivity status */ @@ -316,7 +286,6 @@ const FormulusAPI = { * from the most recent successful sync). Reflects server-stream alignment * only — not unsynced local edits. Use with periodic polling or after * {@link sync} to detect when another device has pushed changes. - * / * @since 1.4.0 * @returns {Promise} Non-negative revision count (0 if never synced) */ diff --git a/formulus/scripts/generateInjectionScript.ts b/formulus/scripts/generateInjectionScript.ts index c11897b7c..3cc4140e6 100644 --- a/formulus/scripts/generateInjectionScript.ts +++ b/formulus/scripts/generateInjectionScript.ts @@ -474,11 +474,22 @@ function extractMethods(sourceFile: ts.SourceFile): MethodInfo[] { jsDocText.end, ); - // Parse the JSDoc to extract the description and tags + // Parse the JSDoc to extract the description and tags. + // Strip the comment delimiters up-front so single-line comments + // (`/** text */`) and inline closings don't leak into the parsed + // description — otherwise we emit nested/duplicated `*/`, producing + // malformed (and sometimes unparseable) generated JSDoc. const lines = rawJsDoc + .replace(/^\s*\/\*\*+/, '') // opening /** (or /***) + .replace(/\*+\/\s*$/, '') // closing */ .split('\n') - .map(line => line.trim().replace(/^\* ?/, '').trim()) - .filter(line => line !== '/**' && line !== '*/'); + .map(line => + line + .trim() + .replace(/^\*+ ?/, '') + .trim(), + ) + .filter(line => line.length > 0); const description: string[] = []; const tags: JSDocTag[] = []; diff --git a/formulus/src/webview/FormulusInterfaceDefinition.ts b/formulus/src/webview/FormulusInterfaceDefinition.ts index a4028f8f5..3be0c2205 100644 --- a/formulus/src/webview/FormulusInterfaceDefinition.ts +++ b/formulus/src/webview/FormulusInterfaceDefinition.ts @@ -394,6 +394,9 @@ export interface FormulusInterface { * `window.formulusSessionContext`. Draft bypass is not a param key — use * `options.skipDraftSelection` on {@link openFormplayer} (same as `skipFinalize`). * @param {Object} savedData - Previously saved form data (for editing) + * @param {Object} [options] - Session options (not persisted as observation data) + * @param {string|null} [options.observationId] - When set, finalize updates this + * observation instead of creating a new one * @returns {Promise} Promise that resolves when the form is completed/closed with result details */ openFormplayer( @@ -404,6 +407,7 @@ export interface FormulusInterface { subObservationMode?: boolean; skipFinalize?: boolean; skipDraftSelection?: boolean; + observationId?: string | null; }, ): Promise; diff --git a/formulus/src/webview/FormulusMessageHandlers.ts b/formulus/src/webview/FormulusMessageHandlers.ts index a75244d88..72ae442f9 100644 --- a/formulus/src/webview/FormulusMessageHandlers.ts +++ b/formulus/src/webview/FormulusMessageHandlers.ts @@ -724,6 +724,7 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { scopeKey?: string; startAt?: number; peek?: boolean; + options?: { startAt?: number; peek?: boolean }; }, ): Promise => { const scopeKey = @@ -732,14 +733,14 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { : typeof payload?.scopeKey === 'string' ? payload.scopeKey : ''; - const startAt = + // The WebView injection nests options under `options`; fall back to flat + // top-level fields for back-compat with older/string callers. + const opts = typeof payload === 'object' && payload != null - ? payload.startAt - : undefined; - const peek = - typeof payload === 'object' && payload != null - ? payload.peek + ? (payload.options ?? payload) : undefined; + const startAt = opts?.startAt; + const peek = opts?.peek; return sequenceCounterService.allocate(scopeKey, { startAt, peek }); }, @@ -1424,6 +1425,7 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { subObservationMode?: boolean; skipFinalize?: boolean; skipDraftSelection?: boolean; + observationId?: string | null; returnOnly?: boolean; }; /** @deprecated Legacy key; prefer subObservationMode */ @@ -1442,11 +1444,13 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { const skipDraftSelection = Boolean( data.options?.skipDraftSelection || data.skipDraftSelection, ); + const observationId = + data.observationId ?? data.options?.observationId ?? null; return startFormplayerOperation( data.formType, data.params, data.savedData, - data.observationId ?? null, + observationId, subObservationMode, skipFinalize, skipDraftSelection,