package shell import ( "bytes" "io/fs" "time" ) // linkScriptPath serves linkScript, which withLinkScript adds to the // client's index.html. A script of the page's own, rather than one the // webview injects, runs before the app on every platform and is allowed by // the client's Content-Security-Policy (script-src 'self'). const linkScriptPath = "/_desktop/links.js" // linkScript makes links to other sites open in the system browser. A URL // in a message would otherwise replace the app in its own window, or open a // bare webview window for target="_blank"; this sends those to openPath // instead. Links within the app, and blob: URLs for downloads, are left // alone. const linkScript = `(() => { if (window.__warpDesktopLinks) return window.__warpDesktopLinks = true const external = (href) => { try { const u = new URL(href, location.href) return u.origin !== location.origin && ['http:', 'https:', 'mailto:'].includes(u.protocol) ? u.href : null } catch { return null } } const open = (href) => fetch('/_desktop/open', { method: 'POST', credentials: 'omit', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: href }), }).catch(() => {}) const onClick = (e) => { if (e.defaultPrevented || e.button > 1) return const a = e.target instanceof Element ? e.target.closest('a[href]') : null const href = a && external(a.href) if (!href) return e.preventDefault() open(href) } // auxclick is the middle button, which would open a new window. document.addEventListener('click', onClick, true) document.addEventListener('auxclick', onClick, true) const windowOpen = window.open window.open = function (url, ...rest) { const href = url == null ? null : external(String(url)) if (href) { open(href); return null } return windowOpen.call(window, url, ...rest) } })() ` // withLinkScript returns the client with a script tag for linkScript at the // top of index.html's head. ok is false when index.html has no to // add it to; the client is then returned unchanged. func withLinkScript(client fs.FS) (_ fs.FS, ok bool, _ error) { index, err := fs.ReadFile(client, "index.html") if err != nil { return nil, false, err } tag := []byte(`` + "\n " + ``) patched := bytes.Replace(index, []byte(""), tag, 1) if bytes.Equal(patched, index) { return client, false, nil } return indexFS{FS: client, index: patched}, true, nil } // indexFS is a client whose index.html is replaced. type indexFS struct { fs.FS index []byte } func (f indexFS) Open(name string) (fs.File, error) { if name != "index.html" { return f.FS.Open(name) } return &memFile{Reader: bytes.NewReader(f.index), size: int64(len(f.index))}, nil } func (f indexFS) ReadFile(name string) ([]byte, error) { if name != "index.html" { return fs.ReadFile(f.FS, name) } return bytes.Clone(f.index), nil } type memFile struct { *bytes.Reader size int64 } func (m *memFile) Stat() (fs.FileInfo, error) { return memInfo{m.size}, nil } func (m *memFile) Close() error { return nil } type memInfo struct{ size int64 } func (i memInfo) Name() string { return "index.html" } func (i memInfo) Size() int64 { return i.size } func (i memInfo) Mode() fs.FileMode { return 0o444 } func (i memInfo) ModTime() time.Time { return time.Time{} } func (i memInfo) IsDir() bool { return false } func (i memInfo) Sys() any { return nil }