| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "fmt" |
| 7 | "sync" |
| 8 | ) |
| 9 | |
| 10 | // hostRemoteWindows tracks the remote Serve windows the Electron shell owns on |
| 11 | // this process's behalf. Under the shell there is no child process per host: |
| 12 | // the shell keeps one BrowserWindow per host key and reports its closure. |
| 13 | type hostRemoteWindows struct { |
| 14 | mu sync.Mutex |
| 15 | open map[string]bool |
| 16 | } |
| 17 | |
| 18 | func (b *hostShellBridge) remoteWindows() *hostRemoteWindows { |
| 19 | b.remoteMu.Lock() |
| 20 | defer b.remoteMu.Unlock() |
| 21 | if b.remote == nil { |
| 22 | b.remote = &hostRemoteWindows{open: map[string]bool{}} |
| 23 | } |
| 24 | return b.remote |
| 25 | } |
| 26 | |
| 27 | func (b *hostShellBridge) openRemoteWindow(launch remoteWindowLaunch) error { |
| 28 | if launch.HostKey == "" { |
| 29 | return fmt.Errorf("remote window host key is required") |
| 30 | } |
| 31 | ctx, cancel := context.WithTimeout(context.Background(), rpcHostWindowTimeout) |
| 32 | defer cancel() |
| 33 | params := map[string]string{"hostKey": launch.HostKey, "url": launch.URL, "title": launch.Title} |
| 34 | if err := b.server.Request(ctx, "host/remoteWindow.open", params, nil); err != nil { |
| 35 | return fmt.Errorf("open remote window: %w", err) |
| 36 | } |
| 37 | windows := b.remoteWindows() |
| 38 | windows.mu.Lock() |
| 39 | windows.open[launch.HostKey] = true |
| 40 | windows.mu.Unlock() |
| 41 | return nil |
| 42 | } |
| 43 | |
| 44 | func (b *hostShellBridge) closeRemoteWindow(hostKey string) { |
| 45 | windows := b.remoteWindows() |
| 46 | windows.mu.Lock() |
| 47 | present := windows.open[hostKey] |
| 48 | delete(windows.open, hostKey) |
| 49 | windows.mu.Unlock() |
| 50 | if !present { |
| 51 | return |
| 52 | } |
| 53 | ctx, cancel := context.WithTimeout(context.Background(), rpcHostWindowTimeout) |
| 54 | defer cancel() |
| 55 | _ = b.server.Request(ctx, "host/remoteWindow.close", map[string]string{"hostKey": hostKey}, nil) |
| 56 | } |
| 57 | |
| 58 | func (b *hostShellBridge) hasRemoteWindow(hostKey string) bool { |
| 59 | windows := b.remoteWindows() |
| 60 | windows.mu.Lock() |
| 61 | defer windows.mu.Unlock() |
| 62 | return windows.open[hostKey] |
| 63 | } |
| 64 | |
| 65 | func (b *hostShellBridge) closeAllRemoteWindows() { |
| 66 | windows := b.remoteWindows() |
| 67 | windows.mu.Lock() |
| 68 | keys := make([]string, 0, len(windows.open)) |
| 69 | for key := range windows.open { |
| 70 | keys = append(keys, key) |
| 71 | } |
| 72 | windows.mu.Unlock() |
| 73 | for _, key := range keys { |
| 74 | b.closeRemoteWindow(key) |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | // remoteWindowClosed records a window the user closed in the shell so a later |
| 79 | // open creates a fresh window instead of navigating a closed one. |
| 80 | func (b *hostShellBridge) remoteWindowClosed(payload json.RawMessage) { |
| 81 | var closed struct { |
| 82 | HostKey string `json:"hostKey"` |
| 83 | } |
| 84 | if err := json.Unmarshal(payload, &closed); err != nil || closed.HostKey == "" { |
| 85 | return |
| 86 | } |
| 87 | windows := b.remoteWindows() |
| 88 | windows.mu.Lock() |
| 89 | delete(windows.open, closed.HostKey) |
| 90 | windows.mu.Unlock() |
| 91 | } |
| 92 |