Skip to content
rdlabo.devdocs

Update serialization and recovery

Use createNativeUpdateQueue when a renderer reads DOM state asynchronously and
sends native updates. Event bursts should produce one current-state read, and a
second read must not overlap the previous pass. This helper is optional: it does
not change native registrations or other projection APIs. Keep an existing scheduler
if the renderer needs a different cadence or recovery policy. The queue batches requests on
requestAnimationFrame; requests arriving during a pass trigger one later pass.

For example, a resize, a selection update and a DOM mutation arrive in one frame
while the previous native update is still awaiting acknowledgement. Queue one
fresh-state pass instead of starting overlapping reads. If the bridge loses an
acknowledgement, reconcile current state before restoring updates; never use this
queue to retry a purchase, click or advertisement load.

import { createNativeUpdateQueue } from '@webview-native-ui/engine';

const updates = createNativeUpdateQueue(window, {
  async update(send) {
    const state = await renderer.readCurrentState();
    const reply = await send(
      () => renderer.sendState(state),
      (reply) => {
        if (reply.revision !== state.revision) throw new Error('Revision mismatch');
      },
    );
    if (reply) renderer.accept(state, reply);
  },
  retry: () => renderer.invalidateLastAcceptedState(),
  failed: (error) => renderer.stopAndRestoreFallback(error),
  reconnect: true,
});

// From existing layout/content notifications:
updates.request();
// After the existing connection check has reconciled native session/revision:
updates.reconnected();
// At renderer teardown:
updates.destroy();

renderer represents the integration's existing code. send is only for
replaceable state, called sequentially inside an update pass, never clicks,
selection actions, purchases or SDK requests. A failed send skips the remaining sends in that pass and requests one fresh
pass, not a replay of its payload. If that also fails, reconnect: true waits for
an external handshake; without it the queue reports terminal failure. An invalid
acknowledgement waits for reconciliation immediately. After reconciliation, another
failed retry or invalid acknowledgement is terminal; a fully successful update pass resets the
recovery budget. The queue does not install a connection poller or subscriptions.

Keep operation timeouts in the transport. A timeout does not cancel an underlying
native call: the native renderer must reject stale revisions, and the connection
check must reconcile current state before declaring recovery. Disposal suppresses
late send results and future passes; it cannot undo a native operation already sent.
Use dirty to reject obsolete DOM measurements, running to defer connection
reconciliation during a pass, and waiting to detect a pending recovery. The
renderer still owns revision assignment, accepted geometry and fallback policy.