main
1/*
2 * noVNC: HTML5 VNC client
3 * Copyright (c) 2025 The noVNC authors
4 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
5 */
6
7import * as Log from './util/logging.js';
8import { browserAsyncClipboardSupport } from './util/browser.js';
9
10export default class AsyncClipboard {
11 constructor(target) {
12 this._target = target || null;
13
14 this._isAvailable = null;
15
16 this._eventHandlers = {
17 'focus': this._handleFocus.bind(this),
18 };
19
20 // ===== EVENT HANDLERS =====
21
22 this.onpaste = () => {};
23 }
24
25 // ===== PRIVATE METHODS =====
26
27 async _ensureAvailable() {
28 if (this._isAvailable !== null) return this._isAvailable;
29 try {
30 const status = await browserAsyncClipboardSupport();
31 this._isAvailable = (status === 'available');
32 } catch {
33 this._isAvailable = false;
34 }
35 return this._isAvailable;
36 }
37
38 async _handleFocus(event) {
39 if (!(await this._ensureAvailable())) return;
40 try {
41 const text = await navigator.clipboard.readText();
42 this.onpaste(text);
43 } catch (error) {
44 Log.Error("Clipboard read failed: ", error);
45 }
46 }
47
48 // ===== PUBLIC METHODS =====
49
50 writeClipboard(text) {
51 // Can lazily check cached availability
52 if (!this._isAvailable) return false;
53 navigator.clipboard.writeText(text)
54 .catch(error => Log.Error("Clipboard write failed: ", error));
55 return true;
56 }
57
58 grab() {
59 if (!this._target) return;
60 this._ensureAvailable()
61 .then((isAvailable) => {
62 if (isAvailable) {
63 this._target.addEventListener('focus', this._eventHandlers.focus);
64 }
65 });
66 }
67
68 ungrab() {
69 if (!this._target) return;
70 this._target.removeEventListener('focus', this._eventHandlers.focus);
71 }
72}