| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "strings" |
| 7 | ) |
| 8 | |
| 9 | const remotePendingExtensionFormKey = "extension_surface:form" |
| 10 | |
| 11 | // cacheRemotePendingExtensionForm retains the latest actionable form while a |
| 12 | // remote tab has no mounted frontend listener. Other extension publications |
| 13 | // remain ordinary stream history; the shared reducer only exposes one pending |
| 14 | // form at a time, so the newest form replaces the previous one here as well. |
| 15 | func (a *App) cacheRemotePendingExtensionForm(tabID string, gen uint64, frame json.RawMessage) bool { |
| 16 | if !remotePendingExtensionForm(frame) { |
| 17 | return false |
| 18 | } |
| 19 | a.remoteTabMu.Lock() |
| 20 | tab := a.remoteTabs[tabID] |
| 21 | if tab == nil || tab.gen != gen { |
| 22 | a.remoteTabMu.Unlock() |
| 23 | return false |
| 24 | } |
| 25 | tab.runtime.revision++ |
| 26 | if tab.pendingEvents == nil { |
| 27 | tab.pendingEvents = make(map[string]json.RawMessage) |
| 28 | } |
| 29 | tab.pendingEvents[remotePendingExtensionFormKey] = append(json.RawMessage(nil), frame...) |
| 30 | tab.runtime.pendingPrompt = true |
| 31 | tab.runtime.cancellable = true |
| 32 | meta := remoteTabMetaLocked(tab) |
| 33 | a.remoteTabMu.Unlock() |
| 34 | a.emitRemoteEvent("remote-tab:updated", meta) |
| 35 | return true |
| 36 | } |
| 37 | |
| 38 | func remotePendingExtensionForm(frame json.RawMessage) bool { |
| 39 | var probe struct { |
| 40 | Extension *struct { |
| 41 | PluginID string `json:"pluginId"` |
| 42 | SurfaceID string `json:"surfaceId"` |
| 43 | Kind string `json:"kind"` |
| 44 | Form json.RawMessage `json:"form"` |
| 45 | } `json:"extension"` |
| 46 | } |
| 47 | if json.Unmarshal(frame, &probe) != nil || probe.Extension == nil || |
| 48 | probe.Extension.Kind != "form" || len(probe.Extension.Form) == 0 || bytes.Equal(bytes.TrimSpace(probe.Extension.Form), []byte("null")) || |
| 49 | strings.TrimSpace(probe.Extension.PluginID) == "" || strings.TrimSpace(probe.Extension.SurfaceID) == "" { |
| 50 | return false |
| 51 | } |
| 52 | return true |
| 53 | } |
| 54 | |
| 55 | func (a *App) clearRemotePendingExtensionForm(tabID, pluginID, surfaceID string) { |
| 56 | a.remoteTabMu.Lock() |
| 57 | var meta TabMeta |
| 58 | changed := false |
| 59 | if tab := a.remoteTabs[tabID]; tab != nil { |
| 60 | frame := tab.pendingEvents[remotePendingExtensionFormKey] |
| 61 | var probe struct { |
| 62 | Extension *struct { |
| 63 | PluginID string `json:"pluginId"` |
| 64 | SurfaceID string `json:"surfaceId"` |
| 65 | } `json:"extension"` |
| 66 | } |
| 67 | if json.Unmarshal(frame, &probe) == nil && probe.Extension != nil && |
| 68 | probe.Extension.PluginID == pluginID && probe.Extension.SurfaceID == surfaceID { |
| 69 | tab.runtime.revision++ |
| 70 | delete(tab.pendingEvents, remotePendingExtensionFormKey) |
| 71 | pending := len(tab.pendingEvents) > 0 |
| 72 | changed = tab.runtime.pendingPrompt != pending |
| 73 | tab.runtime.pendingPrompt = pending |
| 74 | meta = remoteTabMetaLocked(tab) |
| 75 | } |
| 76 | } |
| 77 | a.remoteTabMu.Unlock() |
| 78 | if changed { |
| 79 | a.emitRemoteEvent("remote-tab:updated", meta) |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | func (a *App) clearRemotePendingExtensionFormExact(tabID, pluginID, surfaceID, formInstanceID string) { |
| 84 | a.remoteTabMu.Lock() |
| 85 | var meta TabMeta |
| 86 | changed := false |
| 87 | if tab := a.remoteTabs[tabID]; tab != nil { |
| 88 | frame := tab.pendingEvents[remotePendingExtensionFormKey] |
| 89 | var probe struct { |
| 90 | Extension *struct { |
| 91 | PluginID string `json:"pluginId"` |
| 92 | SurfaceID string `json:"surfaceId"` |
| 93 | FormInstanceID string `json:"formInstanceId"` |
| 94 | } `json:"extension"` |
| 95 | } |
| 96 | if json.Unmarshal(frame, &probe) == nil && probe.Extension != nil && |
| 97 | probe.Extension.PluginID == pluginID && probe.Extension.SurfaceID == surfaceID && |
| 98 | probe.Extension.FormInstanceID == formInstanceID { |
| 99 | tab.runtime.revision++ |
| 100 | delete(tab.pendingEvents, remotePendingExtensionFormKey) |
| 101 | pending := len(tab.pendingEvents) > 0 |
| 102 | changed = tab.runtime.pendingPrompt != pending |
| 103 | tab.runtime.pendingPrompt = pending |
| 104 | meta = remoteTabMetaLocked(tab) |
| 105 | } |
| 106 | } |
| 107 | a.remoteTabMu.Unlock() |
| 108 | if changed { |
| 109 | a.emitRemoteEvent("remote-tab:updated", meta) |
| 110 | } |
| 111 | } |
| 112 |