lib/clipboard-sync
Client/server clipboard synchronization, shared by both transports.
The pieces are factories the cores compose: createClipboardSync owns the
server-clipboard cache and the change-only signature (unchanged content
never re-crosses the transport in either direction), createMultipartClipboardState
reassembles multipart server pushes, createTaggedClipboardFetch marks the
connect-time cache-only fetch, createLocalClipboardSender is the
focus-driven local-to-server path, createDeferredClipboardWriter lands
server pushes on engines that reject clipboard writes outside a user
activation, localClipboardBlocker names what stops a local write at all,
and createClipboardGestures wires the copy and paste keystrokes. The transports differ only in the hooks they inject: how a
request or payload is sent and the enablement gates, which are closures
re-read per event so runtime settings changes apply immediately.
Interfaces
MultipartClipboardState
Defined in: lib/clipboard-sync.js:128
Properties
begin
begin: (mime, total) => void;Defined in: lib/clipboard-sync.js:129
Arms a transfer.
Parameters
| Parameter | Type |
|---|---|
mime | string |
total | number |
Returns
void
push
push: (b64) => void;Defined in: lib/clipboard-sync.js:130
Accumulates one base64 chunk.
Parameters
| Parameter | Type |
|---|---|
b64 | string |
Returns
void
assemble
assemble: () => object;Defined in: lib/clipboard-sync.js:131
Joins the chunks and resets; null when no transfer is in progress.
Returns
object
base64
base64: string;mimeType
mimeType: string;totalSize
totalSize: number;reset
reset: () => void;Defined in: lib/clipboard-sync.js:133
Drops the transfer.
Returns
void
inProgress
inProgress: boolean;Defined in: lib/clipboard-sync.js:134
mimeType
mimeType: string;Defined in: lib/clipboard-sync.js:135
totalSize
totalSize: number;Defined in: lib/clipboard-sync.js:136
Declared size in bytes.
receivedSize
receivedSize: number;Defined in: lib/clipboard-sync.js:137
Decoded bytes accumulated so far.
TaggedClipboardFetch
Defined in: lib/clipboard-sync.js:197
Properties
arm
arm: () => void;Defined in: lib/clipboard-sync.js:198
Records that the server tags the reply.
Returns
void
armLegacyWindow
armLegacyWindow: (ms) => void;Defined in: lib/clipboard-sync.js:199
Starts the timed fallback
after sending cr.
Parameters
| Parameter | Type |
|---|---|
ms | number |
Returns
void
consume
consume: () => boolean;Defined in: lib/clipboard-sync.js:201
Whether the next payload is the fetch reply.
Returns
boolean
LocalClipboardSender
Defined in: lib/clipboard-sync.js:242
Properties
readAndSend
readAndSend: () => Promise<void>;Defined in: lib/clipboard-sync.js:243
Reads the local clipboard and pushes any content to the server.
Returns
Promise<void>
maybeInitial
maybeInitial: () => Promise<void>;Defined in: lib/clipboard-sync.js:245
The connect-time one-shot send.
Returns
Promise<void>
getSendInFlight
getSendInFlight: () => Promise<void>;Defined in: lib/clipboard-sync.js:246
The send the
paste-ordering hold awaits, or null.
Returns
Promise<void>
DeferredClipboardWriter
Defined in: lib/clipboard-sync.js:353
Properties
write
write: (attempt, callbacks?) => Promise<boolean>;Defined in: lib/clipboard-sync.js:354
Runs an async clipboard write now, stashing it for the next gesture on an activation rejection.
Parameters
| Parameter | Type |
|---|---|
attempt | () => Promise<void> |
callbacks? | { onSuccess?: () => void; onFailure?: (err) => void; } |
callbacks.onSuccess? | () => void |
callbacks.onFailure? | (err) => void |
Returns
Promise<boolean>
flush
flush: () => void;Defined in: lib/clipboard-sync.js:357
Retries the stashed write.
Returns
void
getInFlight
getInFlight: () => Promise<boolean>;Defined in: lib/clipboard-sync.js:358
The most recent
attempt, immediate or flushed, or null.
Returns
Promise<boolean>
ClipboardSync
Defined in: lib/clipboard-sync.js:469
Properties
sig
sig: (data, mime?) => string;Defined in: lib/clipboard-sync.js:470
Content signature.
Parameters
| Parameter | Type |
|---|---|
data | string | ArrayBuffer | Uint8Array<ArrayBufferLike> | Blob |
mime? | string |
Returns
string
shouldSend
shouldSend: (data, mime?) => boolean;Defined in: lib/clipboard-sync.js:472
Change-only gate.
Parameters
| Parameter | Type |
|---|---|
data | string | ArrayBuffer | Uint8Array<ArrayBufferLike> | Blob |
mime? | string |
Returns
boolean
markSynced
markSynced: (data, mime?) => void;Defined in: lib/clipboard-sync.js:474
Records content as synced, on transfer success.
Parameters
| Parameter | Type |
|---|---|
data | string | ArrayBuffer | Uint8Array<ArrayBufferLike> | Blob |
mime? | string |
Returns
void
resolveServer
resolveServer: (text?, blob?, mime?, bytes?) => void;Defined in: lib/clipboard-sync.js:476
Caches fresh server data and settles pending requests.
Parameters
| Parameter | Type |
|---|---|
text? | string |
blob? | Blob |
mime? | string |
bytes? | Uint8Array |
Returns
void
captureLocalImageSig
captureLocalImageSig: () => Promise<void>;Defined in: lib/clipboard-sync.js:478
Records the browser's re-encoded form of the image just written locally.
Returns
Promise<void>
request
request: (wantBinary) => Promise<string | Blob>;Defined in: lib/clipboard-sync.js:480
Requests the server clipboard.
Parameters
| Parameter | Type |
|---|---|
wantBinary | boolean |
Returns
Promise<string | Blob>
copyViaExecCommand
copyViaExecCommand: (textPromise) => Promise<void>;Defined in: lib/clipboard-sync.js:482
Last-resort copy through execCommand.
Parameters
| Parameter | Type |
|---|---|
textPromise | Promise<string> |
Returns
Promise<void>
lastText
lastText: string;Defined in: lib/clipboard-sync.js:484
lastBlob
lastBlob: Blob;Defined in: lib/clipboard-sync.js:485
lastMime
lastMime: string;Defined in: lib/clipboard-sync.js:486
Type Aliases
LocalClipboardContent
type LocalClipboardContent =
| {
kind: "text";
text: string;
}
| {
kind: "image";
blob: Blob;
mime: string;
};Defined in: lib/clipboard-sync.js:25
Type Parameters
| Type Parameter |
|---|
Variables
CLIPBOARD_PREVIEW_LIMIT
const CLIPBOARD_PREVIEW_LIMIT: number;Defined in: lib/clipboard-sync.js:445
Longest server clipboard text the dashboards are shown, in characters.
Functions
reencodeBlobAsPng()
function reencodeBlobAsPng(blob): Promise<Blob>;Defined in: lib/clipboard-sync.js:38
Re-encodes a raster blob as PNG.
Chromium's async clipboard accepts only image/png on write, but a source
may offer only JPEG, BMP or WebP, so the blob is decoded with the browser's
own decoders and re-encoded first.
Parameters
| Parameter | Type | Description |
|---|---|---|
blob | Blob | The image. |
Returns
Promise<Blob>
The PNG.
Throws
When the blob is undecodable (a dimensionless SVG) or the encode fails.
localClipboardBlocker()
function localClipboardBlocker(): string;Defined in: lib/clipboard-sync.js:62
Why the browser cannot be asked to write the local clipboard, or null
when it can.
Both engines expose navigator.clipboard in a secure context only, so a
deployment served over http:// on anything but localhost has no clipboard
API at all: a server image then lands nowhere, and saying so is the
difference between a bug report and a certificate.
Returns
string
The reason, ready to show.
writeImageToLocalClipboard()
function writeImageToLocalClipboard(blob, mime): Promise<void>;Defined in: lib/clipboard-sync.js:76
Writes a server image to the local clipboard, PNG-normalized through
reencodeBlobAsPng.
Parameters
| Parameter | Type | Description |
|---|---|---|
blob | Blob | The image. |
mime | string | Its type. |
Returns
Promise<void>
Throws
When the type is undecodable or the clipboard write fails.
readLocalClipboard()
function readLocalClipboard(binaryEnabled): Promise<LocalClipboardContent>;Defined in: lib/clipboard-sync.js:91
Reads the local clipboard for the focus and gesture send path.
Chromium's read()/getType() throw DataError on large text and some
images while readText() still returns the text, so every such failure
falls back to it rather than dropping the sync.
Parameters
| Parameter | Type | Description |
|---|---|---|
binaryEnabled | boolean | Whether images may be read. |
Returns
Promise<LocalClipboardContent>
The content, or null when empty.
Throws
Only genuinely unexpected errors, for the caller to log.
createMultipartClipboardState()
function createMultipartClipboardState(): MultipartClipboardState;Defined in: lib/clipboard-sync.js:150
Multipart server-to-client clipboard download state.
The decoded byte count is tracked incrementally from the base64 lengths, so
nothing decodes on the main thread until the caller assembles. A truncated
stream must never be delivered as content: callers compare receivedSize
against totalSize before assembling, or the decoded byte length after,
and discard on a mismatch.
Returns
createTaggedClipboardFetch()
function createTaggedClipboardFetch(): TaggedClipboardFetch;Defined in: lib/clipboard-sync.js:214
Tracker for the connect-time cache-only clipboard fetch (cr).
The reply must populate the sync cache and preview but never be written to the local clipboard, which would clobber whatever the user copied just before connecting. A tagging server marks the answering payload deterministically; for a server that never tags, a short timed window stands in, so a dropped reply cannot swallow a later genuine push.
Returns
createLocalClipboardSender()
function createLocalClipboardSender(hooks): LocalClipboardSender;Defined in: lib/clipboard-sync.js:273
Focus and gesture driven local-to-server clipboard sync.
readAndSend is serialized so the paste-ordering hold can hold Ctrl/Cmd+V
until the send settles. maybeInitial covers a focused Chromium tab, which
gets no focus event after connect and would otherwise leave the server on
its stale clipboard until the first alt-tab; it runs only when clipboard
read is already granted, since it must never raise a prompt at load.
Parameters
| Parameter | Type | Description |
|---|---|---|
hooks | { isChromium: boolean; isSharedMode: () => boolean; canSync: () => boolean; canRead: () => boolean; binaryEnabled: () => boolean; sendClipboardData: (data, mime?) => Promise<void>; dedupeText?: boolean; getDeferredWriteInFlight?: () => Promise<any>; } | - |
hooks.isChromium | boolean | Engine flag. |
hooks.isSharedMode | () => boolean | Viewer sessions never send. |
hooks.canSync | () => boolean | Clipboard sync enabled. |
hooks.canRead | () => boolean | Local-to-server direction enabled. |
hooks.binaryEnabled | () => boolean | Whether images are sent. |
hooks.sendClipboardData | (data, mime?) => Promise<void> | Transport send. |
hooks.dedupeText? | boolean | Suppresses re-sending unchanged text; the WebRTC core's behavior, while the WebSocket core sends per event and dedupes at the server. |
hooks.getDeferredWriteInFlight? | () => Promise<any> | The deferred writer's pending write, awaited before reading. |
Returns
readAndSend()
function readAndSend(): Promise<void>;Defined in: lib/clipboard-sync.js:293
A server push still settling through the deferred writer must land before this read: reading around the flush returns the pre-push content, which then reads as a change and bounces the stale value back to the server.
Returns
Promise<void>
createDeferredClipboardWriter()
function createDeferredClipboardWriter(): DeferredClipboardWriter;Defined in: lib/clipboard-sync.js:381
Deferred local-clipboard writer for server pushes.
Firefox and WebKit reject navigator.clipboard writes outside a transient
user activation, and a server push handler never has one, so on an
activation or focus rejection the write is stashed and retried on the next
real gesture instead of being lost. Only the newest pending write is kept,
since the clipboard is last-value-wins: a monotonic sequence lets a failed
newer write replace an older stash while a flushed stash that fails again
can never clobber a write that arrived during its attempt. The
paste-ordering hold awaits the in-flight attempt so a server-to-client
write lands before a paste reads the local clipboard; otherwise the stash
flushes on the paste's own keydown and lands just after the read, and the
first paste is one behind. The flush rides keydown and pointerdown, which
carry a user activation, and focus and visibilitychange, which land the
write the instant Chromium accepts it again (it rejects writes from an
unfocused document), well before the user's next paste.
Returns
attemptOnce()
function attemptOnce(w): Promise<boolean>;Defined in: lib/clipboard-sync.js:405
Runs one write. An activation rejection (a synthetic event, or a
blurred tab) stashes it for the next gesture unless something newer
replaced it; any other error reaches onFailure.
The attempt is started inside a promise chain so that a caller passing
a plain expression -- navigator.clipboard.write(...), which throws
outright where the browser exposes no clipboard -- fails the same way
an async one does, instead of throwing past onFailure.
Parameters
| Parameter | Type |
|---|---|
w | any |
Returns
Promise<boolean>
write()
function write(attempt, __namedParameters?): Promise<boolean>;Defined in: lib/clipboard-sync.js:435
Runs attempt now; on an activation or focus rejection queues it for
the next gesture. onSuccess fires whenever the write eventually lands,
onFailure only for non-activation errors.
Parameters
| Parameter | Type |
|---|---|
attempt | any |
__namedParameters | { } |
Returns
Promise<boolean>
clipboardPreviewMessage()
function clipboardPreviewMessage(text): object;Defined in: lib/clipboard-sync.js:458
The clipboardContentUpdate message carrying server clipboard text to the
dashboards.
A multi-MB payload structured-clones through postMessage and lands in a
controlled textarea, freezing the page, while the UI only needs a bounded
preview. The truncated flag tells the dashboard to render it read-only so
a blur cannot echo the cut-down text back over the real server clipboard.
Parameters
| Parameter | Type | Description |
|---|---|---|
text | string | The server clipboard text. |
Returns
object
type
type: string;text
text: string;truncated
truncated: boolean;totalLength
totalLength: number;createClipboardSync()
function createClipboardSync(hooks): ClipboardSync;Defined in: lib/clipboard-sync.js:510
Server-clipboard cache, change-only signature and the Ctrl/Cmd+C request queue with its one-behind guard.
The server reads its clipboard the instant REQUEST_CLIPBOARD arrives, racing ahead of the application writing the new selection, so a request stays open until an incoming value differs from the value cached when it was made. The wire protocol carries no request id, so any server push can settle the oldest pending request; the timeout plus the cache bound the impact.
Exactly one value is current at a time, the latest synced in either direction: remembering older signatures would suppress legitimately re-copying content copied before an intervening value. Beside it lives the browser's re-encoded form of the latest inbound image, since writing a pushed image recompresses it and the focus read-back would otherwise read as new and echo once; it follows the synced signature's lifetime.
Parameters
| Parameter | Type | Description |
|---|---|---|
hooks | { sendRequest: () => void; } | - |
hooks.sendRequest | () => void | Emits REQUEST_CLIPBOARD on the transport. |
Returns
sigOf()
function sigOf(data, mime): object;Defined in: lib/clipboard-sync.js:535
Both signature forms of a value. Text and byte-backed values are
content-hashed so two distinct payloads of equal size still differ; a
bare Blob, whose bytes are not in hand, gets the size-only legacy
form, which also rides along with hashed binary signatures so the two
can be cross-matched.
Parameters
| Parameter | Type |
|---|---|
data | any |
mime | any |
Returns
object
full
full: string;legacy
legacy: string;shouldSend()
function shouldSend(data, mime): boolean;Defined in: lib/clipboard-sync.js:565
Change-only gate: true while this content and mime differ from the last
synced value. Read-only: the caller marks the content synced through
markSynced only after the transfer completes, so a failed transfer
never permanently suppresses re-sending the same content. The legacy
compare suppresses echoes of content whose receive-side signature was
stored without bytes.
Parameters
| Parameter | Type |
|---|---|
data | any |
mime | any |
Returns
boolean
markSynced()
function markSynced(data, mime): void;Defined in: lib/clipboard-sync.js:572
Records content as synced; called on transfer success.
Parameters
| Parameter | Type |
|---|---|
data | any |
mime | any |
Returns
void
resolveServer()
function resolveServer(
text,
blob,
mime,
bytes
): void;Defined in: lib/clipboard-sync.js:582
Caches fresh server data and settles pending requests through the
one-behind guard. bytes, when the receive path has them, make the
stored signature content-hashed so it matches what shouldSend
computes for the same data.
Parameters
| Parameter | Type |
|---|---|
text | any |
blob | any |
mime | any |
bytes | any |
Returns
void
captureLocalImageSig()
function captureLocalImageSig(): Promise<void>;Defined in: lib/clipboard-sync.js:613
After a server image is written to the local clipboard, records the browser's re-encoded representation so the next focus read is recognized as the same content instead of echoed back. Needs clipboard read permission and focus and is silently skipped otherwise; the worst case is one redundant round trip, never a loop. The capture is anchored to the synced signature at entry: a sync in either direction landing mid-read makes it stale, and storing it would suppress a legitimate later copy.
Returns
Promise<void>
request()
function request(wantBinary): Promise<string | Blob>;Defined in: lib/clipboard-sync.js:642
Requests the server clipboard and resolves with the next fresh value.
After two seconds the request settles so the ClipboardItem promise, and the browser's transient-activation window, can never hang: with a cached value that differs from the baseline recorded at request time it resolves, otherwise it rejects, since resolving with the baseline-equal cache would settle the copy with stale content exactly when the session-start cache is empty or stale.
Parameters
| Parameter | Type | Description |
|---|---|---|
wantBinary | boolean | Whether an image is wanted rather than text. |
Returns
Promise<string | Blob>
copyViaExecCommand()
function copyViaExecCommand(textPromise): Promise<void>;Defined in: lib/clipboard-sync.js:677
Last-resort copy for browsers that reject navigator.clipboard.write
(older Firefox and Safari): execCommand('copy') from a hidden
textarea. Awaiting the promise first can outlive the Ctrl/Cmd+C
transient activation, hence last resort. A rejected request or an empty
value writes nothing: either would clobber the user's local clipboard
with pre-copy content.
Parameters
| Parameter | Type | Description |
|---|---|---|
textPromise | Promise<string> | The pending server text. |
Returns
Promise<void>
createClipboardGestures()
function createClipboardGestures(hooks): object;Defined in: lib/clipboard-sync.js:756
Keyboard and paste gesture wiring for clipboard sync.
Owns the three window-level pieces around the per-transport read and send functions:
- Paste-ordering hold: a Ctrl/Cmd+V arriving while the local clipboard is still being read or sent would depart the ordered channel before the clipboard content and paste the previous value on the server. The chord's key events are swallowed, held until the send flushes (bounded), then replayed in order for the input stack.
- Non-Chromium Ctrl/Cmd+C: Safari and Firefox reject
navigator.clipboardfrom focus and message handlers, which have no transient activation, so the server clipboard is written inside the copy gesture through a ClipboardItem whose blob is a Promise, withexecCommand('copy')as last resort. - Non-Chromium paste-to-server: driven by the
pasteevent's synchronousclipboardData. There is deliberately no Ctrl/Cmd+Vnavigator.clipboardread: WebKit rejects it from keydown, Firefox re-raises its paste prompt, and it would double-send next to the paste event.
Gestures in page form fields (the settings UI) are left alone; the stream's
overlay input is exempt. Consumed gestures are never preventDefaulted:
the chord must still reach the remote session.
Parameters
| Parameter | Type | Description |
|---|---|---|
hooks | { isChromium: boolean; clipboardSync: ClipboardSync; sendClipboardData: (data, mime?) => Promise<void>; canSync: () => boolean; canRead: () => boolean; canWrite: () => boolean; binaryEnabled: () => boolean; getSendInFlight: () => Promise<any>; getDeferredWriteInFlight?: () => Promise<any>; } | - |
hooks.isChromium | boolean | Engine flag. |
hooks.clipboardSync | ClipboardSync | The server-clipboard state. |
hooks.sendClipboardData | (data, mime?) => Promise<void> | Transport send. |
hooks.canSync | () => boolean | Clipboard sync enabled. |
hooks.canRead | () => boolean | Local-to-server direction enabled. |
hooks.canWrite | () => boolean | Server-to-local direction enabled. |
hooks.binaryEnabled | () => boolean | Whether images are sent. |
hooks.getSendInFlight | () => Promise<any> | The local sender's pending send. |
hooks.getDeferredWriteInFlight? | () => Promise<any> | The deferred writer's pending write. |
Returns
object
Listener registration.
wire
wire: () => void;Returns
void
unwire
unwire: () => void;Returns
void
dropHeldPasteKeydowns()
function dropHeldPasteKeydowns(): void;Defined in: lib/clipboard-sync.js:795
The in-flight transfer failed or never settled: injecting the held V now would paste stale content, so the held keydowns are dropped. The swallowed keyups (V and the chord's modifiers) are still replayed, as losing a modifier keyup would leave it stuck server-side.
Returns
void
holdPasteWhileClipboardInFlight()
function holdPasteWhileClipboardInFlight(ev): void;Defined in: lib/clipboard-sync.js:818
Capture-phase key listener implementing the paste-ordering hold.
A paste chord is held while a send is in flight or a server-to-client
local-clipboard write is still landing, since the paste would otherwise
read the old value; any KeyV event is held while a replay is queued, so
its keyup cannot overtake the held keydown, and so are the chord's
modifier keyups, since a Ctrl keyup overtaking the replayed V would
break the chord server-side and type a literal v. The hold waits for
the current read/send and deferred write, then re-checks, as a
follow-on transfer may have started meanwhile (the deferred write
flushed by this very keydown); replay happens only once nothing is
pending, and on failure or an expired bound the paste is dropped rather
than injected with stale content.
Parameters
| Parameter | Type | Description |
|---|---|---|
ev | KeyboardEvent | - |
Returns
void
onCopyKeydown()
function onCopyKeydown(event): void;Defined in: lib/clipboard-sync.js:864
Non-Chromium Ctrl/Cmd+C: writes the server clipboard inside the gesture.
Only text/plain is advertised: a Ctrl/Cmd+C cannot synchronously know
whether the server's current clipboard is an image, and a stale cached
MIME type would build a malformed ClipboardItem. Server images are
delivered by the push handler instead. Autorepeat is ignored so it
cannot spam REQUEST_CLIPBOARD.
Parameters
| Parameter | Type | Description |
|---|---|---|
event | KeyboardEvent | - |
Returns
void
onPaste()
function onPaste(event): void;Defined in: lib/clipboard-sync.js:898
Non-Chromium paste-to-server from the event's synchronous clipboard data, preferring an image when binary clipboard is on and the payload carries one.
Parameters
| Parameter | Type | Description |
|---|---|---|
event | ClipboardEvent | - |
Returns
void
wire()
function wire(): void;Defined in: lib/clipboard-sync.js:922
Registers the listeners; called before input attaches so the hold runs first.
Returns
void
unwire()
function unwire(): void;Defined in: lib/clipboard-sync.js:932
Removes the listeners wire registered.
Returns
void