PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.7
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / assets / js / iframe-bridge.js

iframe-bridge.js in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.8.7, at assets/js/iframe-bridge.js

555 lines 15.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function() {
2 "use strict";
3 (function() {
4 if (!window.parent || window.parent === window) {
5 return;
6 }
7 const w = window;
8 if (w.wp?.desktop?.iframe) {
9 return;
10 }
11 const parentOrigin = window.location.origin;
12 const connections = {};
13 const connectionListeners = [];
14 const subs = {};
15 const channelSubs = {};
16 const emitToParent = (connectionId, topic, payload) => {
17 try {
18 window.parent.postMessage(
19 {
20 type: "desktop-mode-bridge-publish",
21 connectionId,
22 topic,
23 payload
24 },
25 parentOrigin
26 );
27 } catch {
28 }
29 };
30 window.addEventListener("message", (ev) => {
31 if (ev.origin !== parentOrigin) {
32 return;
33 }
34 const data = ev?.data;
35 if (!data || typeof data !== "object" || typeof data.type !== "string") {
36 return;
37 }
38 if (data.type === "desktop-mode-bridge-handshake" && typeof data.connectionId === "string") {
39 if (connections[data.connectionId]) {
40 try {
41 window.parent.postMessage(
42 {
43 type: "desktop-mode-bridge-handshake-ack",
44 connectionId: data.connectionId
45 },
46 parentOrigin
47 );
48 } catch {
49 }
50 return;
51 }
52 const conn = {
53 id: data.connectionId,
54 topics: Array.isArray(data.topics) ? data.topics.slice() : []
55 };
56 connections[conn.id] = conn;
57 try {
58 window.parent.postMessage(
59 {
60 type: "desktop-mode-bridge-handshake-ack",
61 connectionId: conn.id
62 },
63 parentOrigin
64 );
65 } catch {
66 }
67 for (const listener of connectionListeners) {
68 try {
69 listener({ id: conn.id, topics: conn.topics.slice() });
70 } catch {
71 }
72 }
73 return;
74 }
75 if (data.type === "desktop-mode-bridge-publish" && typeof data.topic === "string") {
76 const meta = {
77 topic: data.topic,
78 connectionId: data.connectionId
79 };
80 const bucket = subs[data.topic];
81 if (bucket) {
82 for (const cb of bucket) {
83 try {
84 cb(data.payload, meta);
85 } catch {
86 }
87 }
88 }
89 const wildcard = subs["*"];
90 if (wildcard) {
91 for (const cb of wildcard) {
92 try {
93 cb(data.payload, meta);
94 } catch {
95 }
96 }
97 }
98 return;
99 }
100 if (data.type === "desktop-mode-bridge-disconnect" && typeof data.connectionId === "string") {
101 delete connections[data.connectionId];
102 }
103 if (data.type === "desktop-mode-window-send" && typeof data.channel === "string") {
104 const d = data;
105 const meta = { channel: d.channel };
106 const bucket = channelSubs[d.channel];
107 if (bucket) {
108 for (const cb of bucket.slice()) {
109 try {
110 cb(d.payload, meta);
111 } catch {
112 }
113 }
114 }
115 const wildcard = channelSubs["*"];
116 if (wildcard) {
117 for (const cb of wildcard.slice()) {
118 try {
119 cb(d.payload, meta);
120 } catch {
121 }
122 }
123 }
124 }
125 });
126 const iframeApi = {
127 publish(topic, payload) {
128 if (typeof topic !== "string" || topic === "") {
129 return;
130 }
131 for (const id of Object.keys(connections)) {
132 emitToParent(id, topic, payload);
133 }
134 },
135 subscribe(topic, cb) {
136 if (typeof topic !== "string" || topic === "" || typeof cb !== "function") {
137 return () => {
138 };
139 }
140 let bucket = subs[topic];
141 if (!bucket) {
142 bucket = [];
143 subs[topic] = bucket;
144 }
145 bucket.push(cb);
146 return () => {
147 const i = bucket.indexOf(cb);
148 if (i >= 0) {
149 bucket.splice(i, 1);
150 }
151 };
152 },
153 onConnection(cb) {
154 if (typeof cb !== "function") {
155 return () => {
156 };
157 }
158 connectionListeners.push(cb);
159 for (const id of Object.keys(connections)) {
160 try {
161 cb({
162 id: connections[id].id,
163 topics: connections[id].topics.slice()
164 });
165 } catch {
166 }
167 }
168 return () => {
169 const i = connectionListeners.indexOf(cb);
170 if (i >= 0) {
171 connectionListeners.splice(i, 1);
172 }
173 };
174 },
175 /**
176 * Iframe-initiated connection request. Asks the parent to open
177 * a connection back to this iframe. Returns a Promise that
178 * resolves with the new `{ id, topics }` once the parent acks
179 * (or rejects on timeout / refusal).
180 *
181 * Parent-side handler: see `src/connection/index.ts`
182 * `handleConnectionRequest` + the
183 * `desktop-mode.iframe.connection-request` filter.
184 */
185 chrome: {
186 setTheme(tokens) {
187 try {
188 window.parent.postMessage(
189 {
190 type: "desktop-mode-chrome-theme",
191 tokens: tokens ?? {}
192 },
193 parentOrigin
194 );
195 } catch {
196 }
197 },
198 setControls(config) {
199 try {
200 window.parent.postMessage(
201 {
202 type: "desktop-mode-chrome-controls",
203 config: config ?? null
204 },
205 parentOrigin
206 );
207 } catch {
208 }
209 },
210 setSlot(name, html) {
211 if (typeof name !== "string" || name === "") {
212 return;
213 }
214 try {
215 window.parent.postMessage(
216 {
217 type: "desktop-mode-chrome-slot",
218 slot: name,
219 html: typeof html === "string" ? html : ""
220 },
221 parentOrigin
222 );
223 } catch {
224 }
225 }
226 },
227 requestConnection(opts) {
228 const o = opts ?? {};
229 const topics = Array.isArray(o.topics) ? o.topics.slice() : [];
230 const requestId = "wpdir-" + Math.random().toString(36).slice(2, 10);
231 return new Promise((resolve, reject) => {
232 let settled = false;
233 const timeoutMs = typeof o.timeoutMs === "number" ? o.timeoutMs : 5e3;
234 const settle = (ok, value) => {
235 if (settled) {
236 return;
237 }
238 settled = true;
239 window.removeEventListener("message", onAck);
240 clearTimeout(timer);
241 if (ok) {
242 resolve(value);
243 } else {
244 reject(value);
245 }
246 };
247 const onAck = (ev) => {
248 if (ev.origin !== parentOrigin) {
249 return;
250 }
251 const d = ev?.data;
252 if (!d || typeof d !== "object" || d.type !== "desktop-mode-bridge-connection-ack" || d.requestId !== requestId) {
253 return;
254 }
255 if (d.accepted) {
256 const summary = {
257 id: typeof d.connectionId === "string" ? d.connectionId : "",
258 topics: topics.slice()
259 };
260 if (typeof o.onOpen === "function") {
261 try {
262 o.onOpen(summary);
263 } catch {
264 }
265 }
266 settle(true, summary);
267 } else {
268 settle(false, new Error(d.reason || "rejected"));
269 }
270 };
271 window.addEventListener("message", onAck);
272 const timer = setTimeout(() => {
273 settle(false, new Error("timeout"));
274 }, timeoutMs);
275 try {
276 window.parent.postMessage(
277 {
278 type: "desktop-mode-bridge-connection-request",
279 requestId,
280 topics
281 },
282 parentOrigin
283 );
284 } catch (err) {
285 settle(false, err);
286 }
287 });
288 }
289 };
290 if (!w.wp) {
291 w.wp = {};
292 }
293 if (!w.wp.desktop) {
294 w.wp.desktop = {};
295 }
296 w.wp.desktop.iframe = iframeApi;
297 if (typeof w.wp.desktop.send !== "function") {
298 w.wp.desktop.send = (channel, payload) => {
299 if (typeof channel !== "string" || channel === "") {
300 return;
301 }
302 try {
303 window.parent.postMessage(
304 {
305 type: "desktop-mode-window-publish",
306 channel,
307 payload
308 },
309 parentOrigin
310 );
311 } catch {
312 }
313 };
314 }
315 if (typeof w.wp.desktop.on !== "function") {
316 w.wp.desktop.on = (channel, cb) => {
317 if (typeof channel !== "string" || channel === "" || typeof cb !== "function") {
318 return () => void 0;
319 }
320 let bucket = channelSubs[channel];
321 if (!bucket) {
322 bucket = [];
323 channelSubs[channel] = bucket;
324 }
325 bucket.push(cb);
326 return () => {
327 const i = bucket.indexOf(cb);
328 if (i >= 0) {
329 bucket.splice(i, 1);
330 }
331 };
332 };
333 }
334 const sentinelHost = window;
335 if (!sentinelHost.__desktopModeScreenMetaInstalled) {
336 sentinelHost.__desktopModeScreenMetaInstalled = true;
337 installScreenMetaHoist(parentOrigin);
338 }
339 if (!sentinelHost.__desktopModeOsFileDropForwarderInstalled) {
340 sentinelHost.__desktopModeOsFileDropForwarderInstalled = true;
341 const hasFiles = (ev) => {
342 const types = ev.dataTransfer?.types;
343 if (!types) {
344 return false;
345 }
346 const list = types;
347 if (typeof list.includes === "function") {
348 return list.includes("Files");
349 }
350 if (typeof list.contains === "function") {
351 return list.contains("Files");
352 }
353 for (let i = 0; i < list.length; i++) {
354 if (list[i] === "Files") {
355 return true;
356 }
357 }
358 return false;
359 };
360 const dropPassthroughSelectors = [
361 ".components-drop-zone",
362 "[data-drop-zone]",
363 ".uploader-window",
364 ".media-frame-content"
365 ];
366 const targetWantsFile = (target) => {
367 const el = target;
368 if (!el || !el.closest) {
369 return false;
370 }
371 for (const sel of dropPassthroughSelectors) {
372 if (el.closest(sel)) {
373 return true;
374 }
375 }
376 return false;
377 };
378 document.addEventListener(
379 "dragover",
380 (ev) => {
381 if (!hasFiles(ev)) {
382 return;
383 }
384 if (targetWantsFile(ev.target)) {
385 return;
386 }
387 ev.preventDefault();
388 if (ev.dataTransfer) {
389 ev.dataTransfer.dropEffect = "copy";
390 }
391 },
392 true
393 );
394 document.addEventListener(
395 "drop",
396 (ev) => {
397 if (!hasFiles(ev)) {
398 return;
399 }
400 if (targetWantsFile(ev.target)) {
401 return;
402 }
403 ev.preventDefault();
404 ev.stopPropagation();
405 const files = [];
406 if (ev.dataTransfer?.files) {
407 for (let i = 0; i < ev.dataTransfer.files.length; i++) {
408 files.push(ev.dataTransfer.files[i]);
409 }
410 }
411 if (files.length === 0) {
412 return;
413 }
414 try {
415 window.parent.postMessage(
416 {
417 type: "desktop-mode-os-file-drop",
418 files,
419 x: ev.clientX,
420 y: ev.clientY
421 },
422 parentOrigin
423 );
424 } catch {
425 }
426 },
427 true
428 );
429 }
430 function installScreenMetaHoist(origin) {
431 const start = () => {
432 const links = document.getElementById("screen-meta-links");
433 if (!links) {
434 return;
435 }
436 const screenOptionsBtn = document.getElementById(
437 "show-settings-link"
438 );
439 const helpBtn = document.getElementById("contextual-help-link");
440 const panels = [];
441 if (screenOptionsBtn) {
442 panels.push("screen-options");
443 }
444 if (helpBtn) {
445 panels.push("help");
446 }
447 if (panels.length === 0) {
448 return;
449 }
450 try {
451 window.parent.postMessage(
452 { type: "desktop-mode-screen-meta", panels },
453 origin
454 );
455 } catch {
456 }
457 const getOpenPanel = () => {
458 if (screenOptionsBtn && screenOptionsBtn.getAttribute("aria-expanded") === "true") {
459 return "screen-options";
460 }
461 if (helpBtn && helpBtn.getAttribute("aria-expanded") === "true") {
462 return "help";
463 }
464 return null;
465 };
466 const reportState = () => {
467 try {
468 window.parent.postMessage(
469 {
470 type: "desktop-mode-screen-meta-state",
471 open: getOpenPanel()
472 },
473 origin
474 );
475 } catch {
476 }
477 };
478 reportState();
479 const observer = new MutationObserver(reportState);
480 if (screenOptionsBtn) {
481 observer.observe(screenOptionsBtn, {
482 attributes: true,
483 attributeFilter: ["aria-expanded"]
484 });
485 }
486 if (helpBtn) {
487 observer.observe(helpBtn, {
488 attributes: true,
489 attributeFilter: ["aria-expanded"]
490 });
491 }
492 const forceClose = (button) => {
493 if (!button || button.getAttribute("aria-expanded") !== "true") {
494 return;
495 }
496 const panelId = button.getAttribute("aria-controls");
497 const panel = panelId ? document.getElementById(panelId) : null;
498 if (!panel) {
499 return;
500 }
501 const jq = window.jQuery;
502 if (jq) {
503 try {
504 jq(panel).stop(true, false);
505 } catch {
506 }
507 }
508 panel.style.display = "none";
509 panel.classList.add("hidden");
510 if (panel.parentElement) {
511 panel.parentElement.style.display = "none";
512 }
513 button.classList.remove("screen-meta-active");
514 button.setAttribute("aria-expanded", "false");
515 const toggles = document.querySelectorAll(
516 ".screen-meta-toggle"
517 );
518 toggles.forEach((t) => {
519 t.style.visibility = "";
520 });
521 };
522 window.addEventListener("message", (e) => {
523 if (e.origin !== origin) {
524 return;
525 }
526 const d = e.data;
527 if (!d || d.type !== "desktop-mode-toggle-panel") {
528 return;
529 }
530 let target = null;
531 if (d.panel === "screen-options" && screenOptionsBtn) {
532 target = screenOptionsBtn;
533 } else if (d.panel === "help" && helpBtn) {
534 target = helpBtn;
535 }
536 if (!target) {
537 return;
538 }
539 if (target.getAttribute("aria-expanded") !== "true") {
540 forceClose(
541 target === screenOptionsBtn ? helpBtn : screenOptionsBtn
542 );
543 }
544 target.click();
545 });
546 };
547 if (document.readyState === "loading") {
548 document.addEventListener("DOMContentLoaded", start, { once: true });
549 } else {
550 start();
551 }
552 }
553 })();
554 })();
555