New tab or iframe
A new tab gives the desktop the whole window. An iframe, a frame inside your own page, keeps it in your page.
| New tab | Iframe | |
|---|---|---|
| Setting on the Anping server | none | your site's address in Oturl:FrameAncestors |
| Win, Alt+Tab | full screen (Chrome, Edge) or Keys | Keys (a frame can't capture these keys) |
| Ctrl+Alt+Del | Keys | Keys |
| Reload | reconnects for a while (the reconnect window) | reconnects if the browser remembers the link (below) |
New tab
Open the tab right when the user clicks, then ask your server for the link. Browsers block new windows that open later (popup blockers).
button.addEventListener('click', async () => {
const tab = window.open('', '_blank') // during the click
if (tab) tab.opener = null
const response = await fetch('/api/anping-link', { method: 'POST' })
const { url } = await response.json() // your server asks Anping
if (tab) tab.location.replace(url)
})Your page itself runs in a restricted frame (sandbox)?
That frame needs allow-popups and allow-popups-to-escape-sandbox in its sandbox attribute, or the new tab is blocked or stays sandboxed.
Iframe
<iframe src="https://anping.example.com/oturl.html?key=...&pass=..."
title="Remote desktop"
allow="fullscreen; clipboard-read; clipboard-write; autoplay"
referrerpolicy="no-referrer"
style="width: 100%; height: 80vh; border: 0"></iframe>In allow | Why you need it |
|---|---|
fullscreen | The toolbar's Full screen button. |
clipboard-read, clipboard-write | Copy and paste work by themselves where the browser allows it; otherwise users use the Clipboard panel. |
autoplay | Sound starts after a click in your page, not only in the frame.MoreWithout it the toolbar shows Enable sound until a click or key press in the frame. |
referrerpolicy="no-referrer" | The link's secret parts (key and pass) are never passed on to other sites. |
With resolution=fittobrowser the remote screen follows the frame's size.
Allow your page
Only the link page (/oturl.html) can be shown in a frame, and only on the sites you list. Write each site as https:// and its host name (and port), without a path, or 'self'.
Technical detail
Anping sends the list as Content-Security-Policy: frame-ancestors.
Oturl__FrameAncestors__0=https://app.example.com
Oturl__FrameAncestors__1=https://portal.example.comReloading inside a frame
The browser remembers the link with a small cookie. In a frame on https, each site that shows Anping gets its own (technical detail: SameSite=None; Secure; Partitioned).
| Browser | A reload of your page |
|---|---|
| Chrome, Edge (114 and later) | Reconnects (tested). |
| Firefox | Should reconnect; not fully tested yet. |
| Safari | Blocks cookies from other sites, so most likely shows the used message. |
| Plain http (development) | Works only when your page is on the same site (technical detail: SameSite=Strict). |
When the cookie is blocked; behind a load balancer
- Where the cookie is blocked, the page's own Reconnect still works (it keeps its own copy while it is open); a reload shows the used message and your page needs a new link.
- Behind a load balancer that handles https (a TLS proxy), set
ForwardedHeaders:KnownNetworksso the Anping server knows the page came over https (settings).
Messages to your page
The page in the frame tells your page what happens (with postMessage). Only the sites listed in Oturl:FrameAncestors hear it.
- Desktop shows, also after each reconnect
- Link used, expired or unknown
- Connection dropped: the page retries
- Back in time
- Not back within a minute (a final error:
ended) - Disconnect, remote ended or an error
- User pressed Reconnect
- Reconnect window over
- End, idle or time limit, restart, final error: a new link is needed
window.addEventListener('message', (e) => {
if (e.origin !== 'https://anping.example.com' || e.data?.source !== 'anping' || e.data.v !== 1) return
// e.data.type: connected | reconnecting | disconnected | ended | link_invalid
})Each message looks like {source: 'anping', v: 1, type, link, code?, until?}. link is the link's code: not secret, the same as in the connection log, and null until Anping has given it one. v is the version: it changes only if a field changes meaning or goes away. In the table, type is what happened, code why, and until the time until which the page keeps trying or Reconnect still works.
type | code | until |
|---|---|---|
connected | none | none |
reconnecting | connection_lost or another short-lived error | when it stops trying |
disconnected | closed (Disconnect, or the remote ended) or an error code | end of the reconnect window |
ended | session_ended (the user chose End), idle_timeout, max_duration, server_restart, another error code, or the disconnected code once its window is over | none |
link_invalid | none | none |
Sound and autoplay
Browsers allow sound only after the user interacts, so a link's sound (soundenabled=true) starts at the first click or key press. Until then the toolbar shows Enable sound, then Mute or Unmute. In an iframe, add autoplay to allow.
More
Sound that arrives before that is dropped, so nothing plays all at once later.