| 1 | package uihub |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "strings" |
| 6 | |
| 7 | "reasonix/internal/extension/protocol" |
| 8 | ) |
| 9 | |
| 10 | type activeForm struct { |
| 11 | pluginID string |
| 12 | surfaceID string |
| 13 | sessionID string |
| 14 | generation uint64 |
| 15 | instanceID string |
| 16 | submitting bool |
| 17 | } |
| 18 | |
| 19 | func formKey(pluginID, surfaceID string) string { return pluginID + "\x00" + surfaceID } |
| 20 | |
| 21 | // SubmitExact pins the form instance and sidecar client while holding the hub |
| 22 | // lock. The sidecar call runs after unlock, so replacements keep their identity. |
| 23 | func (h *Hub) SubmitExact(ctx context.Context, pluginID, surfaceID, sessionID string, generation uint64, instanceID string, values map[string]any) (protocol.UISubmitResult, error) { |
| 24 | if strings.TrimSpace(surfaceID) == "" || strings.TrimSpace(instanceID) == "" { |
| 25 | return protocol.UISubmitResult{}, &protocol.ProtocolError{Reason: protocol.ErrInvalidParams, Message: "exact form identity is required"} |
| 26 | } |
| 27 | h.mu.Lock() |
| 28 | form, ok := h.activeForms[formKey(pluginID, surfaceID)] |
| 29 | if !ok || form.sessionID != sessionID || form.generation != generation || form.instanceID != instanceID { |
| 30 | h.mu.Unlock() |
| 31 | return protocol.UISubmitResult{}, &protocol.ProtocolError{Reason: protocol.ErrInvalidParams, Message: "extension form is stale"} |
| 32 | } |
| 33 | if form.submitting { |
| 34 | h.mu.Unlock() |
| 35 | return protocol.UISubmitResult{}, &protocol.ProtocolError{Reason: protocol.ErrInvalidParams, Message: "extension form submission is already in progress"} |
| 36 | } |
| 37 | if !h.known[pluginID] { |
| 38 | h.mu.Unlock() |
| 39 | return protocol.UISubmitResult{}, unknownClientError(pluginID) |
| 40 | } |
| 41 | if h.crashed[pluginID] || h.resolve == nil { |
| 42 | h.mu.Unlock() |
| 43 | return protocol.UISubmitResult{}, &protocol.ProtocolError{Reason: protocol.ErrProviderInterrupted, Message: "extension sidecar is unavailable"} |
| 44 | } |
| 45 | client := h.resolve(pluginID) |
| 46 | if client == nil { |
| 47 | h.mu.Unlock() |
| 48 | return protocol.UISubmitResult{}, &protocol.ProtocolError{Reason: protocol.ErrProviderInterrupted, Message: "extension " + pluginID + " has no live sidecar"} |
| 49 | } |
| 50 | form.submitting = true |
| 51 | h.activeForms[formKey(pluginID, surfaceID)] = form |
| 52 | h.mu.Unlock() |
| 53 | result, err := client.UISubmit(ctx, protocol.UISubmitParams{SurfaceID: surfaceID, SessionID: sessionID, Generation: generation, Values: values}) |
| 54 | h.mu.Lock() |
| 55 | current, currentOK := h.activeForms[formKey(pluginID, surfaceID)] |
| 56 | if currentOK && current.instanceID == instanceID { |
| 57 | if err != nil || result.Accepted { |
| 58 | delete(h.activeForms, formKey(pluginID, surfaceID)) |
| 59 | } else { |
| 60 | current.submitting = false |
| 61 | h.activeForms[formKey(pluginID, surfaceID)] = current |
| 62 | } |
| 63 | } |
| 64 | h.mu.Unlock() |
| 65 | return result, err |
| 66 | } |
| 67 |