feat(api): add shared node-state stream foundation (#746)

* feat(api): add shared node-state stream

* fix(desktop): decode streamed state domains
This commit was merged in pull request #746.
This commit is contained in:
excho0 authored and GitHub committed 2026-10-28 16:38:32 +02:00
1 parent e1d71681d2
commit 04bb1f8bdd
14 files changed
+1323 -425

No files matched your search

@@ -0,0 +1,11 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { sharedStateDomainCode } from '../src/renderer/src/lib/shared-state-domain.ts'
test('shared-state domains accept grpc-js enum names and browser numeric values', () => {
assert.equal(sharedStateDomainCode('SHARED_STATE_DOMAIN_WORKERS'), 'workers')
assert.equal(sharedStateDomainCode('SHARED_STATE_DOMAIN_JOINED_POOL'), 'joinedPool')
assert.equal(sharedStateDomainCode(3), 'workers')
assert.equal(sharedStateDomainCode(5), 'joinedPool')
assert.equal(sharedStateDomainCode('SHARED_STATE_DOMAIN_PEERS'), undefined)
})
+1 -1
View File
@@ -310,7 +310,7 @@ const unaryRpcMethods = new Set([
'getModelConfig', 'setModelConfig', 'listModelProfiles', 'saveModelProfile', 'deleteModelProfile', 'listProviders', 'listModels', 'removeModel', 'getWebAccessConfig', 'setWebAccessConfig', 'listNetworkInterfaces', 'getModelLending', 'setModelLending', 'inspectOllama', 'getLocationDiscoverySettings', 'setLocationDiscoverySettings', 'getNodeLocation', 'setNodeLocation', 'getCityTable', 'downloadCityTable', 'removeCityTable', 'getPoolLimits', 'setPoolLimits', 'listChats', 'getChat', 'deleteChat', 'getModelConfig', 'setModelConfig', 'listModelProfiles', 'saveModelProfile', 'deleteModelProfile', 'listProviders', 'listModels', 'removeModel', 'getWebAccessConfig', 'setWebAccessConfig', 'listNetworkInterfaces', 'getModelLending', 'setModelLending', 'inspectOllama', 'getLocationDiscoverySettings', 'setLocationDiscoverySettings', 'getNodeLocation', 'setNodeLocation', 'getCityTable', 'downloadCityTable', 'removeCityTable', 'getPoolLimits', 'setPoolLimits', 'listChats', 'getChat', 'deleteChat',
'listFiles', 'removeFile', 'createInvitation', 'listMembers', 'removeMember', 'joinPool', 'getJoinedPool', 'leavePool', 'resolveActionConfirmation', 'listFiles', 'removeFile', 'createInvitation', 'listMembers', 'removeMember', 'joinPool', 'getJoinedPool', 'leavePool', 'resolveActionConfirmation',
]) ])
const streamingRpcMethods = new Set(['watchJobs', 'watchJobEvents', 'watchPairedBrowsers', 'watchPendingActionConfirmations', 'watchWorkers', 'watchJoinedPool', 'ask', 'pullModel']) const streamingRpcMethods = new Set(['watchJobs', 'watchJobEvents', 'watchPairedBrowsers', 'watchPendingActionConfirmations', 'watchWorkers', 'watchJoinedPool', 'watchSharedState', 'ask', 'pullModel'])
function callUnary(method: string, request: Record<string, unknown>): Promise<unknown> { function callUnary(method: string, request: Record<string, unknown>): Promise<unknown> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
+1 -1
View File
@@ -80,7 +80,7 @@ export type NodeRpcMethod =
| 'listFiles' | 'removeFile' | 'listFiles' | 'removeFile'
| 'createInvitation' | 'listMembers' | 'removeMember' | 'joinPool' | 'resolveActionConfirmation' | 'createInvitation' | 'listMembers' | 'removeMember' | 'joinPool' | 'resolveActionConfirmation'
export type NodeStreamMethod = 'watchJobs' | 'watchJobEvents' | 'watchPairedBrowsers' | 'watchPendingActionConfirmations' | 'watchWorkers' | 'watchJoinedPool' | 'ask' | 'pullModel' export type NodeStreamMethod = 'watchJobs' | 'watchJobEvents' | 'watchPairedBrowsers' | 'watchPendingActionConfirmations' | 'watchWorkers' | 'watchJoinedPool' | 'watchSharedState' | 'ask' | 'pullModel'
const api = { const api = {
getSnapshot: (): Promise<NodeSnapshot> => ipcRenderer.invoke('node:get-snapshot'), getSnapshot: (): Promise<NodeSnapshot> => ipcRenderer.invoke('node:get-snapshot'),
@@ -0,0 +1,13 @@
// grpc-js with proto-loader's `enums: String` option returns the protobuf
// enum name, while browser Connect clients return the numeric enum value.
export type SharedStateKind = 'workers' | 'joinedPool'
export function sharedStateDomainCode(domain: number | string): SharedStateKind | undefined {
switch (domain) {
case 3:
case 'SHARED_STATE_DOMAIN_WORKERS': return 'workers'
case 5:
case 'SHARED_STATE_DOMAIN_JOINED_POOL': return 'joinedPool'
default: return undefined
}
}
@@ -15,6 +15,8 @@ import { displayDaemonVersion } from '@/lib/daemon-version'
import { ResponsiveActionDialog } from '@/components/ui/responsive-action-dialog' import { ResponsiveActionDialog } from '@/components/ui/responsive-action-dialog'
import { PoolHostingOverview } from '@/components/pool-hosting-overview' import { PoolHostingOverview } from '@/components/pool-hosting-overview'
import { offeredWorkloads } from '@/lib/worker-workloads' import { offeredWorkloads } from '@/lib/worker-workloads'
import { SharedStateDomain } from '@sisyphus/client'
import { sharedStateDomainCode } from '@/lib/shared-state-domain'
type Messages = ReturnType<typeof getMessages> type Messages = ReturnType<typeof getMessages>
function shortId(id: string) { return id.length > 22 ? `${id.slice(0, 12)}…${id.slice(-8)}` : id } function shortId(id: string) { return id.length > 22 ? `${id.slice(0, 12)}…${id.slice(-8)}` : id }
@@ -34,6 +36,20 @@ function formatBytes(value: string | number) {
return `${(bytes / 1024 ** power).toFixed(power ? 1 : 0)} ${units[power]}` return `${(bytes / 1024 ** power).toFixed(power ? 1 : 0)} ${units[power]}`
} }
type SharedStatePacket = {
domain: number | string
error?: string
state?: { case?: string; value?: unknown } | string
workers?: unknown
joinedPool?: JoinedPool
}
function sharedStatePayload<T>(event: SharedStatePacket, key: 'workers' | 'joinedPool'): T | undefined {
if (event.state && typeof event.state === 'object' && event.state.case === key) return event.state.value as T
const direct = event[key]
return direct === undefined ? undefined : direct as T
}
export function NodeDetailsPage({ snapshot, messages }: { snapshot: NodeSnapshot; messages: Messages }) { export function NodeDetailsPage({ snapshot, messages }: { snapshot: NodeSnapshot; messages: Messages }) {
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [peerAddress, setPeerAddress] = useState('') const [peerAddress, setPeerAddress] = useState('')
@@ -49,80 +65,69 @@ export function NodeDetailsPage({ snapshot, messages }: { snapshot: NodeSnapshot
const connectedPeers = useMemo(() => snapshot.peers.filter(peerIsConnected).length, [snapshot.peers]) const connectedPeers = useMemo(() => snapshot.peers.filter(peerIsConnected).length, [snapshot.peers])
const statusLabel = snapshot.status === 'connected' ? messages.connected : snapshot.status === 'connecting' ? messages.connecting : messages.offline const statusLabel = snapshot.status === 'connected' ? messages.connected : snapshot.status === 'connecting' ? messages.connecting : messages.offline
useEffect(() => { useEffect(() => {
if (snapshot.status !== 'connected') { setWorkers([]); return } if (snapshot.status !== 'connected') {
setWorkers([])
setWorkersState('loading')
setJoinedPool(null)
setJoinedPoolState('loading')
return
}
let active = true let active = true
let cancel: (() => void) | undefined let cancel: (() => void) | undefined
let retry: number | undefined let retry: number | undefined
let attempt = 0 let attempt = 0
setWorkersState('loading') setWorkersState('loading')
setJoinedPoolState('loading')
const connect = () => { const connect = () => {
if (!active) return if (!active) return
cancel = getNodeApi().stream<{ workers?: typeof workers }>('watchWorkers', {}, (result) => { cancel = getNodeApi().stream<SharedStatePacket>('watchSharedState', {
domains: [SharedStateDomain.WORKERS, SharedStateDomain.JOINED_POOL],
}, (event) => {
attempt = 0 attempt = 0
setWorkers(result.workers ?? []) const domain = sharedStateDomainCode(event.domain)
setWorkersState('ready') if (domain === 'workers') {
}, () => { if (event.error) {
setWorkersState('error')
} else {
setWorkers(sharedStatePayload<{ workers?: typeof workers }>(event, 'workers')?.workers ?? [])
setWorkersState('ready')
}
} else if (domain === 'joinedPool') {
if (event.error) {
if (/has joined no other/i.test(event.error)) {
setJoinedPool(null)
setJoinedPoolState('none')
} else {
setJoinedPoolState('unavailable')
}
} else {
const pool = sharedStatePayload<JoinedPool>(event, 'joinedPool')
if (!pool || (!pool.coordinatorId && !pool.coordinatorName && !pool.address)) {
setJoinedPool(null)
setJoinedPoolState('none')
} else {
setJoinedPool(pool)
setJoinedPoolState('ready')
}
}
}
}, (error) => {
cancel = undefined cancel = undefined
if (active) { if (active) {
setWorkersState('error') setWorkersState('error')
retry = window.setTimeout(connect, Math.min(30_000, 500 * (2 ** Math.min(attempt++, 6))))
}
}, () => {
cancel = undefined
if (active) retry = window.setTimeout(connect, Math.min(30_000, 500 * (2 ** Math.min(attempt++, 6))))
})
}
connect()
return () => {
active = false
if (retry !== undefined) window.clearTimeout(retry)
cancel?.()
}
}, [snapshot.status, snapshot.endpoint])
useEffect(() => {
if (snapshot.status !== 'connected') { setJoinedPoolState((current) => current === 'ready' ? 'unavailable' : current); return }
let active = true
let cancel: (() => void) | undefined
let retry: number | undefined
let attempt = 0
const connect = () => {
if (!active) return
cancel = getNodeApi().stream<JoinedPool>('watchJoinedPool', {}, (event) => {
attempt = 0
setJoinedPool(event)
setJoinedPoolState('ready')
}, (error, code) => {
cancel = undefined
if (active) {
if (code === 9 || /has joined no other/i.test(error)) { setJoinedPool(null); setJoinedPoolState('none'); return }
setJoinedPoolState('unavailable') setJoinedPoolState('unavailable')
retry = window.setTimeout(connect, Math.min(30_000, 500 * (2 ** Math.min(attempt++, 6)))) retry = window.setTimeout(connect, Math.min(30_000, 500 * (2 ** Math.min(attempt++, 6))))
} }
}, () => { }, () => {
cancel = undefined cancel = undefined
if (active) retry = window.setTimeout(connect, Math.min(30_000, 500 * (2 ** Math.min(attempt++, 6)))) if (active) {
setWorkersState('error')
setJoinedPoolState('unavailable')
retry = window.setTimeout(connect, Math.min(30_000, 500 * (2 ** Math.min(attempt++, 6))))
}
}) })
} }
// Fetch the baseline before subscribing. WatchJoinedPool sends the current connect()
// state as its first event too, closing the gap without allowing an older
// fetch response to overwrite a newer stream update.
void getNodeApi().call<JoinedPool>('getJoinedPool').then((initial) => {
if (!active) return
setJoinedPool(initial)
setJoinedPoolState('ready')
connect()
}).catch((error: unknown) => {
if (!active) return
// A coordinating node has joined no other pool; don't render a false
// error card for the normal standalone-node case.
if (/has joined no other/i.test(error instanceof Error ? error.message : String(error))) {
setJoinedPool(null)
setJoinedPoolState('none')
return
}
setJoinedPoolState('unavailable')
connect()
})
return () => { active = false; if (retry !== undefined) window.clearTimeout(retry); cancel?.() } return () => { active = false; if (retry !== undefined) window.clearTimeout(retry); cancel?.() }
}, [snapshot.status, snapshot.endpoint]) }, [snapshot.status, snapshot.endpoint])
// Leaving has the pool let go of what it kept for this node; the node then // Leaving has the pool let go of what it kept for this node; the node then
+2
View File
@@ -947,6 +947,8 @@ Country markers ignore addresses inside this machine's directly connected interf
| owner's location | Where the owner says the node is, in degrees, and how much of it other members of a pool may see. See below. | | owner's location | Where the owner says the node is, in degrees, and how much of it other members of a pool may see. See below. |
| city | The city the node's public address is in, from DB-IP's free city table, which is downloaded only when the owner asks for it. See below. | | city | The city the node's public address is in, from DB-IP's free city table, which is downloaded only when the owner asks for it. See below. |
`WatchSharedState` is the authenticated stream for clients connected to the same node. It sends an initial full snapshot for each requested domain, then complete typed snapshots when that state changes; each event has a per-stream revision. A slow client may receive coalesced changes, but it reads the latest committed snapshot, and reconnecting starts from a fresh baseline. A domain that is unavailable (for example, workers on a node with no coordinator) reports that condition without ending other subscriptions. The stream currently covers node details, peers, workers, paired browsers and joined-pool state; additional shared settings and resources are being migrated incrementally. Tokens and provider credentials are not part of its payloads.
Location discovery flags take precedence over the node database only when explicitly supplied (for example, `--locate-country=false`); flags omitted at startup use the saved setting, then the flag's default. This lets an operator pin an opt-out on the command line while allowing paired browsers and Electron to manage saved settings through the local API. `GetLocationDiscoverySettings` reports the effective running settings and whether a restart is needed. Location discovery flags take precedence over the node database only when explicitly supplied (for example, `--locate-country=false`); flags omitted at startup use the saved setting, then the flag's default. This lets an operator pin an opt-out on the command line while allowing paired browsers and Electron to manage saved settings through the local API. `GetLocationDiscoverySettings` reports the effective running settings and whether a restart is needed.
The limits a coordinator holds each member to, `--max-jobs-per-client`, `--max-tasks-per-client`, `--max-task-timeout`, `--max-store-bytes-per-client` and `--max-upload-bytes-per-attempt`, can be saved in the node database the same way, with `SetPoolLimits`, and take effect when the node restarts. A flag supplied explicitly (for example, `--max-jobs-per-client=0`) overrides its saved limit; an omitted flag uses the saved limit, then the flag's default of no limit. `GetPoolLimits` reports the saved and running limits, whether a restart is needed, and whether the node runs the coordinator role, without which the limits hold no one to anything. The task timeout is saved in whole seconds. So that what is reported is what is enforced, the node refuses to start with a job or task count past 2147483647 or a task timeout that is not a whole number of seconds, whether it comes from a flag or was saved. The limits a coordinator holds each member to, `--max-jobs-per-client`, `--max-tasks-per-client`, `--max-task-timeout`, `--max-store-bytes-per-client` and `--max-upload-bytes-per-attempt`, can be saved in the node database the same way, with `SetPoolLimits`, and take effect when the node restarts. A flag supplied explicitly (for example, `--max-jobs-per-client=0`) overrides its saved limit; an omitted flag uses the saved limit, then the flag's default of no limit. `GetPoolLimits` reports the saved and running limits, whether a restart is needed, and whether the node runs the coordinator role, without which the limits hold no one to anything. The task timeout is saved in whole seconds. So that what is reported is what is enforced, the node refuses to start with a job or task count past 2147483647 or a task timeout that is not a whole number of seconds, whether it comes from a flag or was saved.
+126 -2
View File
@@ -384,8 +384,9 @@ func (s *localService) nodeInfo(ctx context.Context) *nodepb.GetNodeInfoResponse
return info return info
} }
// WatchNodeState streams complete node-and-peer snapshots, triggered by node // WatchNodeState streams complete node-and-peer snapshots when their sources
// discovery and peer events, with a slow reconciliation check as a safeguard. // notify a change. It does not poll; callers that need several shared domains
// can use WatchSharedState.
func (s *localService) WatchNodeState(_ *nodepb.GetNodeInfoRequest, stream grpc.ServerStreamingServer[nodepb.NodeState]) error { func (s *localService) WatchNodeState(_ *nodepb.GetNodeInfoRequest, stream grpc.ServerStreamingServer[nodepb.NodeState]) error {
var changes <-chan struct{} var changes <-chan struct{}
var unsubscribe func() var unsubscribe func()
@@ -396,6 +397,129 @@ func (s *localService) WatchNodeState(_ *nodepb.GetNodeInfoRequest, stream grpc.
return watchNodeState(stream.Context(), changes, func() *nodepb.NodeState { return s.nodeState(stream.Context()) }, stream.Send) return watchNodeState(stream.Context(), changes, func() *nodepb.NodeState { return s.nodeState(stream.Context()) }, stream.Send)
} }
// WatchSharedState streams typed, authoritative snapshots for shared domains.
// The token is required because some domains contain owner-only state. Changes
// are coalesced by the notifier; each wake reads the latest committed state,
// so a slow subscriber cannot miss the final value. A reconnect starts with a
// fresh baseline rather than replaying an unbounded event log.
func (s *localService) WatchSharedState(req *nodepb.WatchSharedStateRequest, stream grpc.ServerStreamingServer[nodepb.SharedStateEvent]) error {
if err := s.authorize(stream.Context()); err != nil {
return err
}
domains, err := sharedStateDomains(req.GetDomains())
if err != nil {
return err
}
var changes <-chan struct{}
var unsubscribe func()
if s.cfg.NodeInfoChanges != nil {
changes, unsubscribe = s.cfg.NodeInfoChanges.subscribe()
defer unsubscribe()
}
last := make(map[nodepb.SharedStateDomain]*nodepb.SharedStateEvent, len(domains))
var revision uint64
sendCurrent := func(domain nodepb.SharedStateDomain, snapshot bool) error {
current := s.sharedStateSnapshot(stream.Context(), domain)
if previous, ok := last[domain]; ok && sharedStateEqual(previous, current) {
return nil
}
revision++
current.Revision = revision
current.Snapshot = snapshot
if err := stream.Send(current); err != nil {
return err
}
last[domain] = proto.Clone(current).(*nodepb.SharedStateEvent)
return nil
}
for _, domain := range domains {
if err := sendCurrent(domain, true); err != nil {
return err
}
}
for {
select {
case <-changes:
for _, domain := range domains {
if err := sendCurrent(domain, false); err != nil {
return err
}
}
case <-stream.Context().Done():
return nil
}
}
}
func sharedStateDomains(requested []nodepb.SharedStateDomain) ([]nodepb.SharedStateDomain, error) {
if len(requested) == 0 {
return []nodepb.SharedStateDomain{
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_WORKERS,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PAIRED_BROWSERS,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_JOINED_POOL,
}, nil
}
seen := make(map[nodepb.SharedStateDomain]struct{}, len(requested))
domains := make([]nodepb.SharedStateDomain, 0, len(requested))
for _, domain := range requested {
if domain < nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE || domain > nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_JOINED_POOL {
return nil, status.Errorf(codes.InvalidArgument, "unsupported shared-state domain %s", domain)
}
if _, ok := seen[domain]; ok {
continue
}
seen[domain] = struct{}{}
domains = append(domains, domain)
}
sort.Slice(domains, func(i, j int) bool { return domains[i] < domains[j] })
return domains, nil
}
func (s *localService) sharedStateSnapshot(ctx context.Context, domain nodepb.SharedStateDomain) *nodepb.SharedStateEvent {
event := &nodepb.SharedStateEvent{Domain: domain}
switch domain {
case nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE:
event.State = &nodepb.SharedStateEvent_Node{Node: s.nodeState(ctx)}
case nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS:
var peers []*nodepb.Peer
if s.cfg.Peers != nil {
peers = s.cfg.Peers()
}
event.State = &nodepb.SharedStateEvent_Peers{Peers: &nodepb.ListPeersResponse{Peers: peers}}
case nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_WORKERS:
if err := s.jobsAvailable(); err != nil {
event.Error = err.Error()
} else {
event.State = &nodepb.SharedStateEvent_Workers{Workers: s.workerList(true)}
}
case nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PAIRED_BROWSERS:
state, err := s.pairedBrowsers()
if err != nil {
event.Error = err.Error()
} else {
event.State = &nodepb.SharedStateEvent_PairedBrowsers{PairedBrowsers: state}
}
case nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_JOINED_POOL:
state := &nodepb.JoinedPool{}
if s.cfg.JoinedPool != nil {
state = s.cfg.JoinedPool()
}
event.State = &nodepb.SharedStateEvent_JoinedPool{JoinedPool: state}
}
return event
}
func sharedStateEqual(left, right *nodepb.SharedStateEvent) bool {
left = proto.Clone(left).(*nodepb.SharedStateEvent)
right = proto.Clone(right).(*nodepb.SharedStateEvent)
left.Revision, right.Revision = 0, 0
left.Snapshot, right.Snapshot = false, false
return proto.Equal(left, right)
}
// WatchPairedBrowsers streams the authorized paired-device list on changes. // WatchPairedBrowsers streams the authorized paired-device list on changes.
func (s *localService) WatchPairedBrowsers(_ *nodepb.WatchPairedBrowsersRequest, stream grpc.ServerStreamingServer[nodepb.PairedBrowsersState]) error { func (s *localService) WatchPairedBrowsers(_ *nodepb.WatchPairedBrowsersRequest, stream grpc.ServerStreamingServer[nodepb.PairedBrowsersState]) error {
if err := s.authorize(stream.Context()); err != nil { if err := s.authorize(stream.Context()); err != nil {
+197
View File
@@ -341,6 +341,203 @@ func TestWatchNodeStatePublishesNodeAndPeerChanges(t *testing.T) {
} }
} }
func TestWatchSharedStateSendsBaselinesUpdatesAndRequiresToken(t *testing.T) {
var mu sync.Mutex
country := ""
var peers []*nodepb.Peer
changes := NewNodeInfoChanges()
client := serveLocal(t, LocalConfig{
Token: "the-token", NodeInfoChanges: changes,
Peers: func() []*nodepb.Peer {
mu.Lock()
defer mu.Unlock()
return peers
},
Country: func() (string, string) {
mu.Lock()
defer mu.Unlock()
return country, ""
},
})
request := &nodepb.WatchSharedStateRequest{Domains: []nodepb.SharedStateDomain{
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE,
}}
ctx, cancel := context.WithCancel(withToken("the-token"))
defer cancel()
stream, err := client.WatchSharedState(ctx, request)
if err != nil {
t.Fatal(err)
}
initialNode, err := stream.Recv()
if err != nil || initialNode.GetRevision() != 1 || !initialNode.GetSnapshot() || initialNode.GetDomain() != nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE || initialNode.GetNode().GetInfo().GetCountryCode() != "" {
t.Fatalf("initial node snapshot = %v, %v", initialNode, err)
}
initialPeers, err := stream.Recv()
if err != nil || initialPeers.GetRevision() != 2 || !initialPeers.GetSnapshot() || initialPeers.GetDomain() != nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS {
t.Fatalf("initial peers snapshot = %v, %v", initialPeers, err)
}
// A second client sees the same baseline independently; neither subscriber
// consumes the other's change notification.
secondCtx, cancelSecond := context.WithCancel(withToken("the-token"))
defer cancelSecond()
second, err := client.WatchSharedState(secondCtx, request)
if err != nil {
t.Fatal(err)
}
if event, err := second.Recv(); err != nil || event.GetRevision() != 1 || event.GetNode().GetInfo().GetCountryCode() != "" {
t.Fatalf("second client's node baseline = %v, %v", event, err)
}
if event, err := second.Recv(); err != nil || event.GetRevision() != 2 || event.GetPeers() == nil {
t.Fatalf("second client's peers baseline = %v, %v", event, err)
}
mu.Lock()
country = "IL"
peers = []*nodepb.Peer{peerEntry("12D3KooWa", true, true)}
mu.Unlock()
changes.Notify()
for _, watcher := range []nodepb.NodeService_WatchSharedStateClient{stream, second} {
nodeUpdate, err := watcher.Recv()
if err != nil || nodeUpdate.GetRevision() != 3 || nodeUpdate.GetSnapshot() || nodeUpdate.GetNode().GetInfo().GetCountryCode() != "IL" {
t.Fatalf("node update = %v, %v", nodeUpdate, err)
}
peerUpdate, err := watcher.Recv()
if err != nil || peerUpdate.GetRevision() != 4 || peerUpdate.GetSnapshot() || len(peerUpdate.GetPeers().GetPeers()) != 1 {
t.Fatalf("peer update = %v, %v", peerUpdate, err)
}
}
unauthorized, err := client.WatchSharedState(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if _, err := unauthorized.Recv(); status.Code(err) != codes.PermissionDenied {
t.Fatalf("unauthorized shared-state watcher error = %v, want permission denied", err)
}
}
func TestSharedStateDomainsAndSnapshots(t *testing.T) {
all, err := sharedStateDomains(nil)
if err != nil || len(all) != 5 || all[0] != nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE || all[4] != nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_JOINED_POOL {
t.Fatalf("default shared-state domains = %v, %v", all, err)
}
unique, err := sharedStateDomains([]nodepb.SharedStateDomain{
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE,
nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS,
})
if err != nil || len(unique) != 2 || unique[0] != nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE || unique[1] != nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PEERS {
t.Fatalf("deduplicated shared-state domains = %v, %v", unique, err)
}
if _, err := sharedStateDomains([]nodepb.SharedStateDomain{nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_UNSPECIFIED}); status.Code(err) != codes.InvalidArgument {
t.Fatalf("unspecified shared-state domain error = %v, want InvalidArgument", err)
}
store := &failingBrowserAccessStore{devices: []nodedb.BrowserDevice{{ID: "device", Name: "Phone"}}}
service := &localService{cfg: LocalConfig{
NodeID: "node", Token: "the-token", Peers: func() []*nodepb.Peer { return []*nodepb.Peer{peerEntry("peer", true, true)} },
Jobs: &office{nodes: []*pb.NodeInfo{{NodeId: "worker"}}}, BrowserAccess: store,
JoinedPool: func() *nodepb.JoinedPool { return &nodepb.JoinedPool{CoordinatorId: "coordinator"} },
}}
for _, domain := range all {
event := service.sharedStateSnapshot(withToken("the-token"), domain)
if event.GetError() != "" || event.GetState() == nil {
t.Fatalf("snapshot for %s = %v; want typed state", domain, event)
}
}
store.listErr = errors.New("device store unavailable")
failedDevices := (&localService{cfg: LocalConfig{BrowserAccess: store}}).sharedStateSnapshot(context.Background(), nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_PAIRED_BROWSERS)
if failedDevices.GetError() == "" || failedDevices.GetState() != nil {
t.Fatalf("failed paired-device snapshot = %v; want per-domain error", failedDevices)
}
unavailableWorkers := (&localService{}).sharedStateSnapshot(context.Background(), nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_WORKERS)
if unavailableWorkers.GetError() == "" || unavailableWorkers.GetState() != nil {
t.Fatalf("unavailable worker snapshot = %v; want per-domain error", unavailableWorkers)
}
noJoinedPool := (&localService{}).sharedStateSnapshot(context.Background(), nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_JOINED_POOL)
if noJoinedPool.GetJoinedPool() == nil {
t.Fatalf("standalone joined-pool snapshot = %v; want an empty typed state", noJoinedPool)
}
}
type sharedStateTestStream struct {
grpc.ServerStream
ctx context.Context
sendErr error
afterSend func(int)
sent []*nodepb.SharedStateEvent
}
func (s *sharedStateTestStream) Context() context.Context { return s.ctx }
func (s *sharedStateTestStream) Send(event *nodepb.SharedStateEvent) error {
if s.sendErr != nil {
return s.sendErr
}
s.sent = append(s.sent, event)
if s.afterSend != nil {
s.afterSend(len(s.sent))
}
return nil
}
func TestWatchSharedStateStopsOnSendFailureOrCancellation(t *testing.T) {
request := &nodepb.WatchSharedStateRequest{Domains: []nodepb.SharedStateDomain{nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_NODE}}
sendFailure := errors.New("shared state send failed")
failed := &sharedStateTestStream{ctx: incomingToken("the-token"), sendErr: sendFailure}
if err := (&localService{cfg: LocalConfig{Token: "the-token"}}).WatchSharedState(request, failed); !errors.Is(err, sendFailure) {
t.Fatalf("initial shared-state send error = %v; want %v", err, sendFailure)
}
invalid := &sharedStateTestStream{ctx: incomingToken("the-token")}
invalidRequest := &nodepb.WatchSharedStateRequest{Domains: []nodepb.SharedStateDomain{nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_UNSPECIFIED}}
if err := (&localService{cfg: LocalConfig{Token: "the-token"}}).WatchSharedState(invalidRequest, invalid); status.Code(err) != codes.InvalidArgument {
t.Fatalf("invalid shared-state request error = %v; want InvalidArgument", err)
}
changes := NewNodeInfoChanges()
joined := false
var updateFailure *sharedStateTestStream
updateFailure = &sharedStateTestStream{ctx: incomingToken("the-token"), afterSend: func(sent int) {
if sent == 1 {
updateFailure.sendErr = sendFailure
joined = true
changes.Notify()
}
}}
joinedPoolRequest := &nodepb.WatchSharedStateRequest{Domains: []nodepb.SharedStateDomain{nodepb.SharedStateDomain_SHARED_STATE_DOMAIN_JOINED_POOL}}
if err := (&localService{cfg: LocalConfig{Token: "the-token", NodeInfoChanges: changes, JoinedPool: func() *nodepb.JoinedPool {
if joined {
return &nodepb.JoinedPool{CoordinatorId: "coordinator"}
}
return &nodepb.JoinedPool{}
}}}).WatchSharedState(joinedPoolRequest, updateFailure); !errors.Is(err, sendFailure) || len(updateFailure.sent) != 1 {
t.Fatalf("updated shared-state send error = %v after %d events; want %v after baseline", err, len(updateFailure.sent), sendFailure)
}
unchangedCtx, cancelUnchanged := context.WithCancel(incomingToken("the-token"))
unchangedChanges := NewNodeInfoChanges()
unchanged := &sharedStateTestStream{ctx: unchangedCtx, afterSend: func(sent int) {
if sent == 1 {
unchangedChanges.Notify()
go func() {
time.Sleep(50 * time.Millisecond)
cancelUnchanged()
}()
}
}}
if err := (&localService{cfg: LocalConfig{Token: "the-token", NodeInfoChanges: unchangedChanges}}).WatchSharedState(request, unchanged); err != nil || len(unchanged.sent) != 1 {
t.Fatalf("unchanged shared-state notification = %v after %d events; want only the baseline", err, len(unchanged.sent))
}
ctx, cancel := context.WithCancel(incomingToken("the-token"))
stream := &sharedStateTestStream{ctx: ctx, afterSend: func(int) { cancel() }}
if err := (&localService{cfg: LocalConfig{Token: "the-token"}}).WatchSharedState(request, stream); err != nil || len(stream.sent) != 1 {
t.Fatalf("cancelled shared-state watch = %v, sent %d events; want clean stop after baseline", err, len(stream.sent))
}
}
func TestWatchPairedBrowsersPublishesChanges(t *testing.T) { func TestWatchPairedBrowsersPublishesChanges(t *testing.T) {
store := &mutableBrowserAccess{volatileBrowserAccess: volatileBrowserAccess{devices: map[string]nodedb.BrowserDevice{ store := &mutableBrowserAccess{volatileBrowserAccess: volatileBrowserAccess{devices: map[string]nodedb.BrowserDevice{
"device-b": {ID: "device-b", Name: "Second"}, "device-b": {ID: "device-b", Name: "Second"},
+5 -2
View File
@@ -313,7 +313,11 @@ func runDaemon(ctx context.Context, args []string) error {
stopped := make(chan error, 2) stopped := make(chan error, 2)
// local is what the desktop client on this machine is told and may do. // local is what the desktop client on this machine is told and may do.
local := api.LocalConfig{NodeID: ident.ID(), Version: version, Workloads: workloads, NodeInfoChanges: api.NewNodeInfoChanges(), BrowserDeviceChanges: api.NewNodeInfoChanges()} sharedStateChanges := api.NewNodeInfoChanges()
local := api.LocalConfig{
NodeID: ident.ID(), Version: version, Workloads: workloads,
NodeInfoChanges: sharedStateChanges, BrowserDeviceChanges: sharedStateChanges, JoinedPoolChanges: sharedStateChanges,
}
// coordinatorID is the ID of the node this one's worker answers to: the // coordinatorID is the ID of the node this one's worker answers to: the
// node itself, or for a worker-only node the coordinator it joined. // node itself, or for a worker-only node the coordinator it joined.
@@ -971,7 +975,6 @@ func runDaemon(ctx context.Context, args []string) error {
// A worker's pool, as far as it knows, is its coordinator. // A worker's pool, as far as it knows, is its coordinator.
coordinatorAddr := *join coordinatorAddr := *join
local.JoinedPool = func() *nodepb.JoinedPool { return joinedPool(w.JoinedPool(), coordinatorAddr) } local.JoinedPool = func() *nodepb.JoinedPool { return joinedPool(w.JoinedPool(), coordinatorAddr) }
local.JoinedPoolChanges = api.NewNodeInfoChanges()
jobs := newPoolJobs(ctx, pb.NewNodeServiceClient(poolConn), db, w.JoinedPool, local.NodeInfoChanges.Notify) jobs := newPoolJobs(ctx, pb.NewNodeServiceClient(poolConn), db, w.JoinedPool, local.NodeInfoChanges.Notify)
local.Jobs = jobs local.Jobs = jobs
// Its user's files go to that pool too, kept there for this // Its user's files go to that pool too, kept there for this
+1
View File
@@ -14,6 +14,7 @@ var rpcKinds = map[string]kind{
// The node, its peers and its settings, as they stand. // The node, its peers and its settings, as they stand.
"GetNodeInfo": reads, "GetNodeInfo": reads,
"WatchNodeState": reads, "WatchNodeState": reads,
"WatchSharedState": reads,
"WatchPairedBrowsers": reads, "WatchPairedBrowsers": reads,
"WatchPendingActionConfirmations": reads, "WatchPendingActionConfirmations": reads,
"GetLocationDiscoverySettings": reads, "GetLocationDiscoverySettings": reads,
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file sisyphus/node/v1/node.proto. * Describes the file sisyphus/node/v1/node.proto.
*/ */
export const file_sisyphus_node_v1_node: GenFile = /*@__PURE__*/ export const file_sisyphus_node_v1_node: GenFile = /*@__PURE__*/
fileDesc("ChtzaXN5cGh1cy9ub2RlL3YxL25vZGUucHJvdG8SEHNpc3lwaHVzLm5vZGUudjEiFAoSR2V0Tm9kZUluZm9SZXF1ZXN0Ip4CChNHZXROb2RlSW5mb1Jlc3BvbnNlEg8KB3BlZXJfaWQYASABKAkSFgoOZGFlbW9uX3ZlcnNpb24YAiABKAkSGAoQbGlzdGVuX2FkZHJlc3NlcxgDIAMoCRIUCgxjb3VudHJ5X2NvZGUYBCABKAkSEQoJd29ya2xvYWRzGAUgAygJEhQKDGNhcGFiaWxpdGllcxgGIAMoCRIWCg5jb3VudHJ5X3NvdXJjZRgHIAEoCRI2Cg5vd25lcl9sb2NhdGlvbhgIIAEoCzIeLnNpc3lwaHVzLm5vZGUudjEuTm9kZUxvY2F0aW9uEjUKDWNpdHlfZXN0aW1hdGUYCSABKAsyHi5zaXN5cGh1cy5ub2RlLnYxLkNpdHlFc3RpbWF0ZSKlAQoMQ2l0eUVzdGltYXRlEgwKBGNpdHkYASABKAkSDgoGcmVnaW9uGAIgASgJEhQKDGNvdW50cnlfY29kZRgDIAEoCRIQCghsYXRpdHVkZRgEIAEoARIRCglsb25naXR1ZGUYBSABKAESDgoGc291cmNlGAYgASgJEhMKC2F0dHJpYnV0aW9uGAcgASgJEhcKD2F0dHJpYnV0aW9uX3VybBgIIAEoCSIbChlHZXRXZWJBY2Nlc3NDb25maWdSZXF1ZXN0IqMBCg9XZWJBY2Nlc3NDb25maWcSDwoHZW5hYmxlZBgBIAEoCBIWCg5saXN0ZW5fYWRkcmVzcxgCIAEoCRIfChdjZXJ0aWZpY2F0ZV9maW5nZXJwcmludBgDIAEoCRIYChByZXN0YXJ0X3JlcXVpcmVkGAQgASgIEhIKCnB1YmxpY191cmwYBSABKAkSGAoQbGlzdGVuX2FkZHJlc3NlcxgGIAMoCSJyChlTZXRXZWJBY2Nlc3NDb25maWdSZXF1ZXN0Eg8KB2VuYWJsZWQYASABKAgSFgoObGlzdGVuX2FkZHJlc3MYAiABKAkSEgoKcHVibGljX3VybBgDIAEoCRIYChBsaXN0ZW5fYWRkcmVzc2VzGAQgAygJIh4KHExpc3ROZXR3b3JrSW50ZXJmYWNlc1JlcXVlc3QiVwodTGlzdE5ldHdvcmtJbnRlcmZhY2VzUmVzcG9uc2USNgoKaW50ZXJmYWNlcxgBIAMoCzIiLnNpc3lwaHVzLm5vZGUudjEuTmV0d29ya0ludGVyZmFjZSIxChBOZXR3b3JrSW50ZXJmYWNlEgwKBG5hbWUYASABKAkSDwoHYWRkcmVzcxgCIAEoCSISChBMaXN0UGVlcnNSZXF1ZXN0IhMKEVdhdGNoUGVlcnNSZXF1ZXN0IhoKGEdldEJvb3RzdHJhcFBlZXJzUmVxdWVzdCJLChlHZXRCb290c3RyYXBQZWVyc1Jlc3BvbnNlEi4KBXBlZXJzGAEgAygLMh8uc2lzeXBodXMubm9kZS52MS5Cb290c3RyYXBQZWVyIkoKGFNldEJvb3RzdHJhcFBlZXJzUmVxdWVzdBIuCgVwZWVycxgBIAMoCzIfLnNpc3lwaHVzLm5vZGUudjEuQm9vdHN0cmFwUGVlciJLChlTZXRCb290c3RyYXBQZWVyc1Jlc3BvbnNlEi4KBXBlZXJzGAEgAygLMh8uc2lzeXBodXMubm9kZS52MS5Cb290c3RyYXBQZWVyIiUKEkNvbm5lY3RQZWVyUmVxdWVzdBIPCgdhZGRyZXNzGAEgASgJIiYKE0Nvbm5lY3RQZWVyUmVzcG9uc2USDwoHcGVlcl9pZBgBIAEoCSI+ChpTZXRQZWVyQ29tcHV0ZVRydXN0UmVxdWVzdBIPCgdwZWVyX2lkGAEgASgJEg8KB3RydXN0ZWQYAiABKAgiLgobU2V0UGVlckNvbXB1dGVUcnVzdFJlc3BvbnNlEg8KB3RydXN0ZWQYASABKAgiWwogU2V0UGVlckNvbXB1dGVQZXJtaXNzaW9uc1JlcXVlc3QSDwoHcGVlcl9pZBgBIAEoCRISCgpnaXZlc193b3JrGAIgASgIEhIKCnRha2VzX3dvcmsYAyABKAgiSwohU2V0UGVlckNvbXB1dGVQZXJtaXNzaW9uc1Jlc3BvbnNlEhIKCmdpdmVzX3dvcmsYASABKAgSEgoKdGFrZXNfd29yaxgCIAEoCCIxCg1Cb290c3RyYXBQZWVyEg8KB3BlZXJfaWQYASABKAkSDwoHYWRkcmVzcxgCIAEoCSJMChFMaXN0UGVlcnNSZXNwb25zZRIlCgVwZWVycxgBIAMoCzIWLnNpc3lwaHVzLm5vZGUudjEuUGVlchIQCghyZXZpc2lvbhgCIAEoBCKGAgoEUGVlchIPCgdwZWVyX2lkGAEgASgJEj8KEGNvbm5lY3Rpb25fc3RhdGUYAiABKA4yJS5zaXN5cGh1cy5ub2RlLnYxLlBlZXJDb25uZWN0aW9uU3RhdGUSFwoPa25vd25fYWRkcmVzc2VzGAMgAygJEhsKE3RydXN0ZWRfZm9yX2NvbXB1dGUYBCABKAgSGwoTd29ya3NfZm9yX3RoaXNfbm9kZRgFIAEoCBIbChN0aGlzX25vZGVfd29ya3NfZm9yGAYgASgIEhIKCmdpdmVzX3dvcmsYByABKAgSEgoKdGFrZXNfd29yaxgIIAEoCBIUCgxjb3VudHJ5X2NvZGUYCSABKAkiFAoSTGlzdFdvcmtlcnNSZXF1ZXN0IlIKE0xpc3RXb3JrZXJzUmVzcG9uc2USKQoHd29ya2VycxgBIAMoCzIYLnNpc3lwaHVzLm5vZGUudjEuV29ya2VyEhAKCHJldmlzaW9uGAIgASgEIoQECgZXb3JrZXISDwoHcGVlcl9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEhAKCGhvc3RuYW1lGAMgASgJEgoKAm9zGAQgASgJEgwKBGFyY2gYBSABKAkSEQoJY3B1X2NvcmVzGAYgASgNEhIKCnRhc2tfc2xvdHMYByABKA0SFQoNcnVubmluZ190YXNrcxgIIAEoDRIRCgl3b3JrbG9hZHMYCSADKAkSDgoGcmVsYXlzGAogASgIEhsKE3JlbGF5ZWRfY29ubmVjdGlvbnMYCyABKAQSFQoNcmVsYXllZF9ieXRlcxgMIAEoBBIRCgljcHVfbW9kZWwYDSABKAkSFAoMbWVtb3J5X2J5dGVzGA4gASgEEikKBGdwdXMYDyADKAsyGy5zaXN5cGh1cy5ub2RlLnYxLldvcmtlckdwdRIOCgZtb2RlbHMYECADKAkSNAoNbG9hZGVkX21vZGVscxgUIAMoCzIdLnNpc3lwaHVzLm5vZGUudjEuTG9hZGVkTW9kZWwSFwoPdmVyaWZpZWRfYWdyZWVkGBEgASgEEhkKEXZlcmlmaWVkX291dHZvdGVkGBIgASgEEhEKCXByb2JhdGlvbhgTIAEoDRI5Cg9zaGFyZWRfbG9jYXRpb24YFSABKAsyIC5zaXN5cGh1cy5ub2RlLnYxLlNoYXJlZExvY2F0aW9uIjUKDlNoYXJlZExvY2F0aW9uEhAKCGxhdGl0dWRlGAEgASgBEhEKCWxvbmdpdHVkZRgCIAEoASIWChRMaXN0V29ya2xvYWRzUmVxdWVzdCJGChVMaXN0V29ya2xvYWRzUmVzcG9uc2USLQoJd29ya2xvYWRzGAEgAygLMhouc2lzeXBodXMubm9kZS52MS5Xb3JrbG9hZCKNAQoIV29ya2xvYWQSDAoEbmFtZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRIvCgZmaWVsZHMYAyADKAsyHy5zaXN5cGh1cy5ub2RlLnYxLldvcmtsb2FkRmllbGQSGgoSd29ya2Vyc19ydW5uaW5nX2l0GAQgASgNEhEKCWNvbXBvc2l0ZRgFIAEoCCL4AQoNV29ya2xvYWRGaWVsZBIMCgRuYW1lGAEgASgJEjEKBGtpbmQYAiABKA4yIy5zaXN5cGh1cy5ub2RlLnYxLldvcmtsb2FkRmllbGRLaW5kEg0KBWxhYmVsGAMgASgJEgwKBGhlbHAYBCABKAkSFAoMZGVmYXVsdF9qc29uGAUgASgJEhQKB21pbmltdW0YBiABKAFIAIgBARIUCgdtYXhpbXVtGAcgASgBSAGIAQESDAoEc3RlcBgIIAEoARIPCgdjaG9pY2VzGAkgAygJEhAKCHJlcXVpcmVkGAogASgIQgoKCF9taW5pbXVtQgoKCF9tYXhpbXVtIi8KCVdvcmtlckdwdRIMCgRuYW1lGAEgASgJEhQKDG1lbW9yeV9ieXRlcxgCIAEoBCI0CgtMb2FkZWRNb2RlbBIMCgRuYW1lGAEgASgJEhcKD29uX2NhcmRfcGVyY2VudBgCIAEoDSKPBAoDSm9iEg4KBmpvYl9pZBgBIAEoCRIQCgh3b3JrbG9hZBgCIAEoCRIOCgZwYXJhbXMYAyABKAwSJwoEbW9kZRgEIAEoDjIZLnNpc3lwaHVzLm5vZGUudjEuSm9iTW9kZRIpCgVzdGF0ZRgFIAEoDjIaLnNpc3lwaHVzLm5vZGUudjEuSm9iU3RhdGUSKAoFdGFza3MYBiADKAsyGS5zaXN5cGh1cy5ub2RlLnYxLkpvYlRhc2sSDgoGcmVzdWx0GAcgASgMEg0KBWVycm9yGAggASgJEhUKDWNyZWF0ZWRfYXRfbXMYCSABKAMSFgoOZmluaXNoZWRfYXRfbXMYCiABKAMSEwoLaW5wdXRfYmxvYnMYCyADKAkSFAoMb3V0cHV0X2Jsb2JzGAwgAygJEhAKCHByb2dyZXNzGA0gASgBEhwKFHRhc2tfdGltZW91dF9zZWNvbmRzGA4gASgNEhgKEG1pbl9tZW1vcnlfYnl0ZXMYDyABKAQSEAoIbWluX2dwdXMYECABKA0SDwoHcHJLine truncated fileDesc("ChtzaXN5cGh1cy9ub2RlL3YxL25vZGUucHJvdG8SEHNpc3lwaHVzLm5vZGUudjEiFAoSR2V0Tm9kZUluZm9SZXF1ZXN0Ip4CChNHZXROb2RlSW5mb1Jlc3BvbnNlEg8KB3BlZXJfaWQYASABKAkSFgoOZGFlbW9uX3ZlcnNpb24YAiABKAkSGAoQbGlzdGVuX2FkZHJlc3NlcxgDIAMoCRIUCgxjb3VudHJ5X2NvZGUYBCABKAkSEQoJd29ya2xvYWRzGAUgAygJEhQKDGNhcGFiaWxpdGllcxgGIAMoCRIWCg5jb3VudHJ5X3NvdXJjZRgHIAEoCRI2Cg5vd25lcl9sb2NhdGlvbhgIIAEoCzIeLnNpc3lwaHVzLm5vZGUudjEuTm9kZUxvY2F0aW9uEjUKDWNpdHlfZXN0aW1hdGUYCSABKAsyHi5zaXN5cGh1cy5ub2RlLnYxLkNpdHlFc3RpbWF0ZSKlAQoMQ2l0eUVzdGltYXRlEgwKBGNpdHkYASABKAkSDgoGcmVnaW9uGAIgASgJEhQKDGNvdW50cnlfY29kZRgDIAEoCRIQCghsYXRpdHVkZRgEIAEoARIRCglsb25naXR1ZGUYBSABKAESDgoGc291cmNlGAYgASgJEhMKC2F0dHJpYnV0aW9uGAcgASgJEhcKD2F0dHJpYnV0aW9uX3VybBgIIAEoCSIbChlHZXRXZWJBY2Nlc3NDb25maWdSZXF1ZXN0IqMBCg9XZWJBY2Nlc3NDb25maWcSDwoHZW5hYmxlZBgBIAEoCBIWCg5saXN0ZW5fYWRkcmVzcxgCIAEoCRIfChdjZXJ0aWZpY2F0ZV9maW5nZXJwcmludBgDIAEoCRIYChByZXN0YXJ0X3JlcXVpcmVkGAQgASgIEhIKCnB1YmxpY191cmwYBSABKAkSGAoQbGlzdGVuX2FkZHJlc3NlcxgGIAMoCSJyChlTZXRXZWJBY2Nlc3NDb25maWdSZXF1ZXN0Eg8KB2VuYWJsZWQYASABKAgSFgoObGlzdGVuX2FkZHJlc3MYAiABKAkSEgoKcHVibGljX3VybBgDIAEoCRIYChBsaXN0ZW5fYWRkcmVzc2VzGAQgAygJIh4KHExpc3ROZXR3b3JrSW50ZXJmYWNlc1JlcXVlc3QiVwodTGlzdE5ldHdvcmtJbnRlcmZhY2VzUmVzcG9uc2USNgoKaW50ZXJmYWNlcxgBIAMoCzIiLnNpc3lwaHVzLm5vZGUudjEuTmV0d29ya0ludGVyZmFjZSIxChBOZXR3b3JrSW50ZXJmYWNlEgwKBG5hbWUYASABKAkSDwoHYWRkcmVzcxgCIAEoCSISChBMaXN0UGVlcnNSZXF1ZXN0IhMKEVdhdGNoUGVlcnNSZXF1ZXN0IhoKGEdldEJvb3RzdHJhcFBlZXJzUmVxdWVzdCJLChlHZXRCb290c3RyYXBQZWVyc1Jlc3BvbnNlEi4KBXBlZXJzGAEgAygLMh8uc2lzeXBodXMubm9kZS52MS5Cb290c3RyYXBQZWVyIkoKGFNldEJvb3RzdHJhcFBlZXJzUmVxdWVzdBIuCgVwZWVycxgBIAMoCzIfLnNpc3lwaHVzLm5vZGUudjEuQm9vdHN0cmFwUGVlciJLChlTZXRCb290c3RyYXBQZWVyc1Jlc3BvbnNlEi4KBXBlZXJzGAEgAygLMh8uc2lzeXBodXMubm9kZS52MS5Cb290c3RyYXBQZWVyIiUKEkNvbm5lY3RQZWVyUmVxdWVzdBIPCgdhZGRyZXNzGAEgASgJIiYKE0Nvbm5lY3RQZWVyUmVzcG9uc2USDwoHcGVlcl9pZBgBIAEoCSI+ChpTZXRQZWVyQ29tcHV0ZVRydXN0UmVxdWVzdBIPCgdwZWVyX2lkGAEgASgJEg8KB3RydXN0ZWQYAiABKAgiLgobU2V0UGVlckNvbXB1dGVUcnVzdFJlc3BvbnNlEg8KB3RydXN0ZWQYASABKAgiWwogU2V0UGVlckNvbXB1dGVQZXJtaXNzaW9uc1JlcXVlc3QSDwoHcGVlcl9pZBgBIAEoCRISCgpnaXZlc193b3JrGAIgASgIEhIKCnRha2VzX3dvcmsYAyABKAgiSwohU2V0UGVlckNvbXB1dGVQZXJtaXNzaW9uc1Jlc3BvbnNlEhIKCmdpdmVzX3dvcmsYASABKAgSEgoKdGFrZXNfd29yaxgCIAEoCCIxCg1Cb290c3RyYXBQZWVyEg8KB3BlZXJfaWQYASABKAkSDwoHYWRkcmVzcxgCIAEoCSJMChFMaXN0UGVlcnNSZXNwb25zZRIlCgVwZWVycxgBIAMoCzIWLnNpc3lwaHVzLm5vZGUudjEuUGVlchIQCghyZXZpc2lvbhgCIAEoBCKGAgoEUGVlchIPCgdwZWVyX2lkGAEgASgJEj8KEGNvbm5lY3Rpb25fc3RhdGUYAiABKA4yJS5zaXN5cGh1cy5ub2RlLnYxLlBlZXJDb25uZWN0aW9uU3RhdGUSFwoPa25vd25fYWRkcmVzc2VzGAMgAygJEhsKE3RydXN0ZWRfZm9yX2NvbXB1dGUYBCABKAgSGwoTd29ya3NfZm9yX3RoaXNfbm9kZRgFIAEoCBIbChN0aGlzX25vZGVfd29ya3NfZm9yGAYgASgIEhIKCmdpdmVzX3dvcmsYByABKAgSEgoKdGFrZXNfd29yaxgIIAEoCBIUCgxjb3VudHJ5X2NvZGUYCSABKAkiFAoSTGlzdFdvcmtlcnNSZXF1ZXN0IlIKE0xpc3RXb3JrZXJzUmVzcG9uc2USKQoHd29ya2VycxgBIAMoCzIYLnNpc3lwaHVzLm5vZGUudjEuV29ya2VyEhAKCHJldmlzaW9uGAIgASgEIoQECgZXb3JrZXISDwoHcGVlcl9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEhAKCGhvc3RuYW1lGAMgASgJEgoKAm9zGAQgASgJEgwKBGFyY2gYBSABKAkSEQoJY3B1X2NvcmVzGAYgASgNEhIKCnRhc2tfc2xvdHMYByABKA0SFQoNcnVubmluZ190YXNrcxgIIAEoDRIRCgl3b3JrbG9hZHMYCSADKAkSDgoGcmVsYXlzGAogASgIEhsKE3JlbGF5ZWRfY29ubmVjdGlvbnMYCyABKAQSFQoNcmVsYXllZF9ieXRlcxgMIAEoBBIRCgljcHVfbW9kZWwYDSABKAkSFAoMbWVtb3J5X2J5dGVzGA4gASgEEikKBGdwdXMYDyADKAsyGy5zaXN5cGh1cy5ub2RlLnYxLldvcmtlckdwdRIOCgZtb2RlbHMYECADKAkSNAoNbG9hZGVkX21vZGVscxgUIAMoCzIdLnNpc3lwaHVzLm5vZGUudjEuTG9hZGVkTW9kZWwSFwoPdmVyaWZpZWRfYWdyZWVkGBEgASgEEhkKEXZlcmlmaWVkX291dHZvdGVkGBIgASgEEhEKCXByb2JhdGlvbhgTIAEoDRI5Cg9zaGFyZWRfbG9jYXRpb24YFSABKAsyIC5zaXN5cGh1cy5ub2RlLnYxLlNoYXJlZExvY2F0aW9uIjUKDlNoYXJlZExvY2F0aW9uEhAKCGxhdGl0dWRlGAEgASgBEhEKCWxvbmdpdHVkZRgCIAEoASIWChRMaXN0V29ya2xvYWRzUmVxdWVzdCJGChVMaXN0V29ya2xvYWRzUmVzcG9uc2USLQoJd29ya2xvYWRzGAEgAygLMhouc2lzeXBodXMubm9kZS52MS5Xb3JrbG9hZCKNAQoIV29ya2xvYWQSDAoEbmFtZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRIvCgZmaWVsZHMYAyADKAsyHy5zaXN5cGh1cy5ub2RlLnYxLldvcmtsb2FkRmllbGQSGgoSd29ya2Vyc19ydW5uaW5nX2l0GAQgASgNEhEKCWNvbXBvc2l0ZRgFIAEoCCL4AQoNV29ya2xvYWRGaWVsZBIMCgRuYW1lGAEgASgJEjEKBGtpbmQYAiABKA4yIy5zaXN5cGh1cy5ub2RlLnYxLldvcmtsb2FkRmllbGRLaW5kEg0KBWxhYmVsGAMgASgJEgwKBGhlbHAYBCABKAkSFAoMZGVmYXVsdF9qc29uGAUgASgJEhQKB21pbmltdW0YBiABKAFIAIgBARIUCgdtYXhpbXVtGAcgASgBSAGIAQESDAoEc3RlcBgIIAEoARIPCgdjaG9pY2VzGAkgAygJEhAKCHJlcXVpcmVkGAogASgIQgoKCF9taW5pbXVtQgoKCF9tYXhpbXVtIi8KCVdvcmtlckdwdRIMCgRuYW1lGAEgASgJEhQKDG1lbW9yeV9ieXRlcxgCIAEoBCI0CgtMb2FkZWRNb2RlbBIMCgRuYW1lGAEgASgJEhcKD29uX2NhcmRfcGVyY2VudBgCIAEoDSKPBAoDSm9iEg4KBmpvYl9pZBgBIAEoCRIQCgh3b3JrbG9hZBgCIAEoCRIOCgZwYXJhbXMYAyABKAwSJwoEbW9kZRgEIAEoDjIZLnNpc3lwaHVzLm5vZGUudjEuSm9iTW9kZRIpCgVzdGF0ZRgFIAEoDjIaLnNpc3lwaHVzLm5vZGUudjEuSm9iU3RhdGUSKAoFdGFza3MYBiADKAsyGS5zaXN5cGh1cy5ub2RlLnYxLkpvYlRhc2sSDgoGcmVzdWx0GAcgASgMEg0KBWVycm9yGAggASgJEhUKDWNyZWF0ZWRfYXRfbXMYCSABKAMSFgoOZmluaXNoZWRfYXRfbXMYCiABKAMSEwoLaW5wdXRfYmxvYnMYCyADKAkSFAoMb3V0cHV0X2Jsb2JzGAwgAygJEhAKCHByb2dyZXNzGA0gASgBEhwKFHRhc2tfdGltZW91dF9zZWNvbmRzGA4gASgNEhgKEG1pbl9tZW1vcnlfYnl0ZXMYDyABKAQSEAoIbWluX2dwdXMYECABKA0SDwoHcHJLine truncated
/** /**
* @generated from message sisyphus.node.v1.GetNodeInfoRequest * @generated from message sisyphus.node.v1.GetNodeInfoRequest
@@ -3447,6 +3447,99 @@ export type NodeState = Message<"sisyphus.node.v1.NodeState"> & {
export const NodeStateSchema: GenMessage<NodeState> = /*@__PURE__*/ export const NodeStateSchema: GenMessage<NodeState> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 121); messageDesc(file_sisyphus_node_v1_node, 121);
/**
* @generated from message sisyphus.node.v1.WatchSharedStateRequest
*/
export type WatchSharedStateRequest = Message<"sisyphus.node.v1.WatchSharedStateRequest"> & {
/**
* Empty requests every domain supported by this daemon. Repeated values are
* harmless and are treated as one subscription.
*
* @generated from field: repeated sisyphus.node.v1.SharedStateDomain domains = 1;
*/
domains: SharedStateDomain[];
};
/**
* Describes the message sisyphus.node.v1.WatchSharedStateRequest.
* Use `create(WatchSharedStateRequestSchema)` to create a new message.
*/
export const WatchSharedStateRequestSchema: GenMessage<WatchSharedStateRequest> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 122);
/**
* @generated from message sisyphus.node.v1.SharedStateEvent
*/
export type SharedStateEvent = Message<"sisyphus.node.v1.SharedStateEvent"> & {
/**
* Increases for each event on this stream. A reconnect starts with a new
* authoritative baseline and a new revision sequence.
*
* @generated from field: uint64 revision = 1;
*/
revision: bigint;
/**
* @generated from field: sisyphus.node.v1.SharedStateDomain domain = 2;
*/
domain: SharedStateDomain;
/**
* @generated from field: bool snapshot = 3;
*/
snapshot: boolean;
/**
* A domain can temporarily be unavailable (for example, workers on a node
* that coordinates no pool) without terminating other domain subscriptions.
*
* @generated from field: string error = 4;
*/
error: string;
/**
* @generated from oneof sisyphus.node.v1.SharedStateEvent.state
*/
state: {
/**
* @generated from field: sisyphus.node.v1.NodeState node = 10;
*/
value: NodeState;
case: "node";
} | {
/**
* @generated from field: sisyphus.node.v1.ListPeersResponse peers = 11;
*/
value: ListPeersResponse;
case: "peers";
} | {
/**
* @generated from field: sisyphus.node.v1.ListWorkersResponse workers = 12;
*/
value: ListWorkersResponse;
case: "workers";
} | {
/**
* @generated from field: sisyphus.node.v1.PairedBrowsersState paired_browsers = 13;
*/
value: PairedBrowsersState;
case: "pairedBrowsers";
} | {
/**
* @generated from field: sisyphus.node.v1.JoinedPool joined_pool = 14;
*/
value: JoinedPool;
case: "joinedPool";
} | { case: undefined; value?: undefined };
};
/**
* Describes the message sisyphus.node.v1.SharedStateEvent.
* Use `create(SharedStateEventSchema)` to create a new message.
*/
export const SharedStateEventSchema: GenMessage<SharedStateEvent> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 123);
/** /**
* @generated from message sisyphus.node.v1.WatchPairedBrowsersRequest * @generated from message sisyphus.node.v1.WatchPairedBrowsersRequest
*/ */
@@ -3458,7 +3551,7 @@ export type WatchPairedBrowsersRequest = Message<"sisyphus.node.v1.WatchPairedBr
* Use `create(WatchPairedBrowsersRequestSchema)` to create a new message. * Use `create(WatchPairedBrowsersRequestSchema)` to create a new message.
*/ */
export const WatchPairedBrowsersRequestSchema: GenMessage<WatchPairedBrowsersRequest> = /*@__PURE__*/ export const WatchPairedBrowsersRequestSchema: GenMessage<WatchPairedBrowsersRequest> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 122); messageDesc(file_sisyphus_node_v1_node, 124);
/** /**
* @generated from message sisyphus.node.v1.RequestActionConfirmationRequest * @generated from message sisyphus.node.v1.RequestActionConfirmationRequest
@@ -3475,7 +3568,7 @@ export type RequestActionConfirmationRequest = Message<"sisyphus.node.v1.Request
* Use `create(RequestActionConfirmationRequestSchema)` to create a new message. * Use `create(RequestActionConfirmationRequestSchema)` to create a new message.
*/ */
export const RequestActionConfirmationRequestSchema: GenMessage<RequestActionConfirmationRequest> = /*@__PURE__*/ export const RequestActionConfirmationRequestSchema: GenMessage<RequestActionConfirmationRequest> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 123); messageDesc(file_sisyphus_node_v1_node, 125);
/** /**
* @generated from message sisyphus.node.v1.RequestActionConfirmationResponse * @generated from message sisyphus.node.v1.RequestActionConfirmationResponse
@@ -3492,7 +3585,7 @@ export type RequestActionConfirmationResponse = Message<"sisyphus.node.v1.Reques
* Use `create(RequestActionConfirmationResponseSchema)` to create a new message. * Use `create(RequestActionConfirmationResponseSchema)` to create a new message.
*/ */
export const RequestActionConfirmationResponseSchema: GenMessage<RequestActionConfirmationResponse> = /*@__PURE__*/ export const RequestActionConfirmationResponseSchema: GenMessage<RequestActionConfirmationResponse> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 124); messageDesc(file_sisyphus_node_v1_node, 126);
/** /**
* @generated from message sisyphus.node.v1.WatchPendingActionConfirmationsRequest * @generated from message sisyphus.node.v1.WatchPendingActionConfirmationsRequest
@@ -3505,7 +3598,7 @@ export type WatchPendingActionConfirmationsRequest = Message<"sisyphus.node.v1.W
* Use `create(WatchPendingActionConfirmationsRequestSchema)` to create a new message. * Use `create(WatchPendingActionConfirmationsRequestSchema)` to create a new message.
*/ */
export const WatchPendingActionConfirmationsRequestSchema: GenMessage<WatchPendingActionConfirmationsRequest> = /*@__PURE__*/ export const WatchPendingActionConfirmationsRequestSchema: GenMessage<WatchPendingActionConfirmationsRequest> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 125); messageDesc(file_sisyphus_node_v1_node, 127);
/** /**
* @generated from message sisyphus.node.v1.PendingActionConfirmation * @generated from message sisyphus.node.v1.PendingActionConfirmation
@@ -3537,7 +3630,7 @@ export type PendingActionConfirmation = Message<"sisyphus.node.v1.PendingActionC
* Use `create(PendingActionConfirmationSchema)` to create a new message. * Use `create(PendingActionConfirmationSchema)` to create a new message.
*/ */
export const PendingActionConfirmationSchema: GenMessage<PendingActionConfirmation> = /*@__PURE__*/ export const PendingActionConfirmationSchema: GenMessage<PendingActionConfirmation> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 126); messageDesc(file_sisyphus_node_v1_node, 128);
/** /**
* @generated from message sisyphus.node.v1.PendingActionConfirmationsState * @generated from message sisyphus.node.v1.PendingActionConfirmationsState
@@ -3559,7 +3652,7 @@ export type PendingActionConfirmationsState = Message<"sisyphus.node.v1.PendingA
* Use `create(PendingActionConfirmationsStateSchema)` to create a new message. * Use `create(PendingActionConfirmationsStateSchema)` to create a new message.
*/ */
export const PendingActionConfirmationsStateSchema: GenMessage<PendingActionConfirmationsState> = /*@__PURE__*/ export const PendingActionConfirmationsStateSchema: GenMessage<PendingActionConfirmationsState> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 127); messageDesc(file_sisyphus_node_v1_node, 129);
/** /**
* @generated from message sisyphus.node.v1.ResolveActionConfirmationRequest * @generated from message sisyphus.node.v1.ResolveActionConfirmationRequest
@@ -3581,7 +3674,7 @@ export type ResolveActionConfirmationRequest = Message<"sisyphus.node.v1.Resolve
* Use `create(ResolveActionConfirmationRequestSchema)` to create a new message. * Use `create(ResolveActionConfirmationRequestSchema)` to create a new message.
*/ */
export const ResolveActionConfirmationRequestSchema: GenMessage<ResolveActionConfirmationRequest> = /*@__PURE__*/ export const ResolveActionConfirmationRequestSchema: GenMessage<ResolveActionConfirmationRequest> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 128); messageDesc(file_sisyphus_node_v1_node, 130);
/** /**
* @generated from message sisyphus.node.v1.ResolveActionConfirmationResponse * @generated from message sisyphus.node.v1.ResolveActionConfirmationResponse
@@ -3594,7 +3687,7 @@ export type ResolveActionConfirmationResponse = Message<"sisyphus.node.v1.Resolv
* Use `create(ResolveActionConfirmationResponseSchema)` to create a new message. * Use `create(ResolveActionConfirmationResponseSchema)` to create a new message.
*/ */
export const ResolveActionConfirmationResponseSchema: GenMessage<ResolveActionConfirmationResponse> = /*@__PURE__*/ export const ResolveActionConfirmationResponseSchema: GenMessage<ResolveActionConfirmationResponse> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 129); messageDesc(file_sisyphus_node_v1_node, 131);
/** /**
* @generated from message sisyphus.node.v1.PairedBrowser * @generated from message sisyphus.node.v1.PairedBrowser
@@ -3621,7 +3714,7 @@ export type PairedBrowser = Message<"sisyphus.node.v1.PairedBrowser"> & {
* Use `create(PairedBrowserSchema)` to create a new message. * Use `create(PairedBrowserSchema)` to create a new message.
*/ */
export const PairedBrowserSchema: GenMessage<PairedBrowser> = /*@__PURE__*/ export const PairedBrowserSchema: GenMessage<PairedBrowser> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 130); messageDesc(file_sisyphus_node_v1_node, 132);
/** /**
* @generated from message sisyphus.node.v1.PairedBrowsersState * @generated from message sisyphus.node.v1.PairedBrowsersState
@@ -3643,7 +3736,7 @@ export type PairedBrowsersState = Message<"sisyphus.node.v1.PairedBrowsersState"
* Use `create(PairedBrowsersStateSchema)` to create a new message. * Use `create(PairedBrowsersStateSchema)` to create a new message.
*/ */
export const PairedBrowsersStateSchema: GenMessage<PairedBrowsersState> = /*@__PURE__*/ export const PairedBrowsersStateSchema: GenMessage<PairedBrowsersState> = /*@__PURE__*/
messageDesc(file_sisyphus_node_v1_node, 131); messageDesc(file_sisyphus_node_v1_node, 133);
/** /**
* @generated from enum sisyphus.node.v1.PeerConnectionState * @generated from enum sisyphus.node.v1.PeerConnectionState
@@ -3954,6 +4047,47 @@ export enum LocationShare {
export const LocationShareSchema: GenEnum<LocationShare> = /*@__PURE__*/ export const LocationShareSchema: GenEnum<LocationShare> = /*@__PURE__*/
enumDesc(file_sisyphus_node_v1_node, 7); enumDesc(file_sisyphus_node_v1_node, 7);
/**
* @generated from enum sisyphus.node.v1.SharedStateDomain
*/
export enum SharedStateDomain {
/**
* @generated from enum value: SHARED_STATE_DOMAIN_UNSPECIFIED = 0;
*/
UNSPECIFIED = 0,
/**
* @generated from enum value: SHARED_STATE_DOMAIN_NODE = 1;
*/
NODE = 1,
/**
* @generated from enum value: SHARED_STATE_DOMAIN_PEERS = 2;
*/
PEERS = 2,
/**
* @generated from enum value: SHARED_STATE_DOMAIN_WORKERS = 3;
*/
WORKERS = 3,
/**
* @generated from enum value: SHARED_STATE_DOMAIN_PAIRED_BROWSERS = 4;
*/
PAIRED_BROWSERS = 4,
/**
* @generated from enum value: SHARED_STATE_DOMAIN_JOINED_POOL = 5;
*/
JOINED_POOL = 5,
}
/**
* Describes the enum sisyphus.node.v1.SharedStateDomain.
*/
export const SharedStateDomainSchema: GenEnum<SharedStateDomain> = /*@__PURE__*/
enumDesc(file_sisyphus_node_v1_node, 8);
/** /**
* @generated from service sisyphus.node.v1.NodeService * @generated from service sisyphus.node.v1.NodeService
*/ */
@@ -3977,6 +4111,18 @@ export const NodeService: GenService<{
input: typeof GetNodeInfoRequestSchema; input: typeof GetNodeInfoRequestSchema;
output: typeof NodeStateSchema; output: typeof NodeStateSchema;
}, },
/**
* WatchSharedState sends one initial snapshot per requested domain, then
* complete domain snapshots whenever shared node state changes. Reconnect
* starts a new stream with a fresh baseline.
*
* @generated from rpc sisyphus.node.v1.NodeService.WatchSharedState
*/
watchSharedState: {
methodKind: "server_streaming";
input: typeof WatchSharedStateRequestSchema;
output: typeof SharedStateEventSchema;
},
/** /**
* WatchPairedBrowsers sends the authorized device list at once and whenever * WatchPairedBrowsers sends the authorized device list at once and whenever
* a browser is paired or revoked. * a browser is paired or revoked.
File diff suppressed because it is too large. Load diff
@@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9
const ( const (
NodeService_GetNodeInfo_FullMethodName = "/sisyphus.node.v1.NodeService/GetNodeInfo" NodeService_GetNodeInfo_FullMethodName = "/sisyphus.node.v1.NodeService/GetNodeInfo"
NodeService_WatchNodeState_FullMethodName = "/sisyphus.node.v1.NodeService/WatchNodeState" NodeService_WatchNodeState_FullMethodName = "/sisyphus.node.v1.NodeService/WatchNodeState"
NodeService_WatchSharedState_FullMethodName = "/sisyphus.node.v1.NodeService/WatchSharedState"
NodeService_WatchPairedBrowsers_FullMethodName = "/sisyphus.node.v1.NodeService/WatchPairedBrowsers" NodeService_WatchPairedBrowsers_FullMethodName = "/sisyphus.node.v1.NodeService/WatchPairedBrowsers"
NodeService_RequestActionConfirmation_FullMethodName = "/sisyphus.node.v1.NodeService/RequestActionConfirmation" NodeService_RequestActionConfirmation_FullMethodName = "/sisyphus.node.v1.NodeService/RequestActionConfirmation"
NodeService_WatchPendingActionConfirmations_FullMethodName = "/sisyphus.node.v1.NodeService/WatchPendingActionConfirmations" NodeService_WatchPendingActionConfirmations_FullMethodName = "/sisyphus.node.v1.NodeService/WatchPendingActionConfirmations"
@@ -90,6 +91,10 @@ type NodeServiceClient interface {
// WatchNodeState sends a complete node-and-peer snapshot at once and // WatchNodeState sends a complete node-and-peer snapshot at once and
// again whenever node details or peers change. // again whenever node details or peers change.
WatchNodeState(ctx context.Context, in *GetNodeInfoRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NodeState], error) WatchNodeState(ctx context.Context, in *GetNodeInfoRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NodeState], error)
// WatchSharedState sends one initial snapshot per requested domain, then
// complete domain snapshots whenever shared node state changes. Reconnect
// starts a new stream with a fresh baseline.
WatchSharedState(ctx context.Context, in *WatchSharedStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SharedStateEvent], error)
// WatchPairedBrowsers sends the authorized device list at once and whenever // WatchPairedBrowsers sends the authorized device list at once and whenever
// a browser is paired or revoked. // a browser is paired or revoked.
WatchPairedBrowsers(ctx context.Context, in *WatchPairedBrowsersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PairedBrowsersState], error) WatchPairedBrowsers(ctx context.Context, in *WatchPairedBrowsersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PairedBrowsersState], error)
@@ -289,9 +294,28 @@ func (c *nodeServiceClient) WatchNodeState(ctx context.Context, in *GetNodeInfoR
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type NodeService_WatchNodeStateClient = grpc.ServerStreamingClient[NodeState] type NodeService_WatchNodeStateClient = grpc.ServerStreamingClient[NodeState]
func (c *nodeServiceClient) WatchSharedState(ctx context.Context, in *WatchSharedStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SharedStateEvent], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[1], NodeService_WatchSharedState_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[WatchSharedStateRequest, SharedStateEvent]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type NodeService_WatchSharedStateClient = grpc.ServerStreamingClient[SharedStateEvent]
func (c *nodeServiceClient) WatchPairedBrowsers(ctx context.Context, in *WatchPairedBrowsersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PairedBrowsersState], error) { func (c *nodeServiceClient) WatchPairedBrowsers(ctx context.Context, in *WatchPairedBrowsersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PairedBrowsersState], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[1], NodeService_WatchPairedBrowsers_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[2], NodeService_WatchPairedBrowsers_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -320,7 +344,7 @@ func (c *nodeServiceClient) RequestActionConfirmation(ctx context.Context, in *R
func (c *nodeServiceClient) WatchPendingActionConfirmations(ctx context.Context, in *WatchPendingActionConfirmationsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PendingActionConfirmationsState], error) { func (c *nodeServiceClient) WatchPendingActionConfirmations(ctx context.Context, in *WatchPendingActionConfirmationsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PendingActionConfirmationsState], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[2], NodeService_WatchPendingActionConfirmations_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[3], NodeService_WatchPendingActionConfirmations_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -479,7 +503,7 @@ func (c *nodeServiceClient) ListPeers(ctx context.Context, in *ListPeersRequest,
func (c *nodeServiceClient) WatchPeers(ctx context.Context, in *WatchPeersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListPeersResponse], error) { func (c *nodeServiceClient) WatchPeers(ctx context.Context, in *WatchPeersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListPeersResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[3], NodeService_WatchPeers_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[4], NodeService_WatchPeers_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -558,7 +582,7 @@ func (c *nodeServiceClient) ListWorkers(ctx context.Context, in *ListWorkersRequ
func (c *nodeServiceClient) WatchWorkers(ctx context.Context, in *ListWorkersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListWorkersResponse], error) { func (c *nodeServiceClient) WatchWorkers(ctx context.Context, in *ListWorkersRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListWorkersResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[4], NodeService_WatchWorkers_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[5], NodeService_WatchWorkers_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -617,7 +641,7 @@ func (c *nodeServiceClient) ListJobs(ctx context.Context, in *ListJobsRequest, o
func (c *nodeServiceClient) WatchJobs(ctx context.Context, in *WatchJobsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListJobsResponse], error) { func (c *nodeServiceClient) WatchJobs(ctx context.Context, in *WatchJobsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ListJobsResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[5], NodeService_WatchJobs_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[6], NodeService_WatchJobs_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -646,7 +670,7 @@ func (c *nodeServiceClient) CancelJob(ctx context.Context, in *CancelJobRequest,
func (c *nodeServiceClient) WatchJobEvents(ctx context.Context, in *WatchJobEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JobEvent], error) { func (c *nodeServiceClient) WatchJobEvents(ctx context.Context, in *WatchJobEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JobEvent], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[6], NodeService_WatchJobEvents_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[7], NodeService_WatchJobEvents_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -765,7 +789,7 @@ func (c *nodeServiceClient) ListModels(ctx context.Context, in *ListModelsReques
func (c *nodeServiceClient) PullModel(ctx context.Context, in *PullModelRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PullModelProgress], error) { func (c *nodeServiceClient) PullModel(ctx context.Context, in *PullModelRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PullModelProgress], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[7], NodeService_PullModel_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[8], NodeService_PullModel_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -794,7 +818,7 @@ func (c *nodeServiceClient) RemoveModel(ctx context.Context, in *RemoveModelRequ
func (c *nodeServiceClient) Ask(ctx context.Context, in *AskRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AskEvent], error) { func (c *nodeServiceClient) Ask(ctx context.Context, in *AskRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AskEvent], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[8], NodeService_Ask_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[9], NodeService_Ask_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -843,7 +867,7 @@ func (c *nodeServiceClient) DeleteChat(ctx context.Context, in *DeleteChatReques
func (c *nodeServiceClient) StoreFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[StoreFileRequest, File], error) { func (c *nodeServiceClient) StoreFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[StoreFileRequest, File], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[9], NodeService_StoreFile_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[10], NodeService_StoreFile_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -856,7 +880,7 @@ type NodeService_StoreFileClient = grpc.ClientStreamingClient[StoreFileRequest,
func (c *nodeServiceClient) FetchFile(ctx context.Context, in *FetchFileRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FetchFileResponse], error) { func (c *nodeServiceClient) FetchFile(ctx context.Context, in *FetchFileRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FetchFileResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[10], NodeService_FetchFile_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[11], NodeService_FetchFile_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -955,7 +979,7 @@ func (c *nodeServiceClient) GetJoinedPool(ctx context.Context, in *GetJoinedPool
func (c *nodeServiceClient) WatchJoinedPool(ctx context.Context, in *GetJoinedPoolRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JoinedPool], error) { func (c *nodeServiceClient) WatchJoinedPool(ctx context.Context, in *GetJoinedPoolRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JoinedPool], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[11], NodeService_WatchJoinedPool_FullMethodName, cOpts...) stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[12], NodeService_WatchJoinedPool_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -980,6 +1004,10 @@ type NodeServiceServer interface {
// WatchNodeState sends a complete node-and-peer snapshot at once and // WatchNodeState sends a complete node-and-peer snapshot at once and
// again whenever node details or peers change. // again whenever node details or peers change.
WatchNodeState(*GetNodeInfoRequest, grpc.ServerStreamingServer[NodeState]) error WatchNodeState(*GetNodeInfoRequest, grpc.ServerStreamingServer[NodeState]) error
// WatchSharedState sends one initial snapshot per requested domain, then
// complete domain snapshots whenever shared node state changes. Reconnect
// starts a new stream with a fresh baseline.
WatchSharedState(*WatchSharedStateRequest, grpc.ServerStreamingServer[SharedStateEvent]) error
// WatchPairedBrowsers sends the authorized device list at once and whenever // WatchPairedBrowsers sends the authorized device list at once and whenever
// a browser is paired or revoked. // a browser is paired or revoked.
WatchPairedBrowsers(*WatchPairedBrowsersRequest, grpc.ServerStreamingServer[PairedBrowsersState]) error WatchPairedBrowsers(*WatchPairedBrowsersRequest, grpc.ServerStreamingServer[PairedBrowsersState]) error
@@ -1156,6 +1184,9 @@ func (UnimplementedNodeServiceServer) GetNodeInfo(context.Context, *GetNodeInfoR
func (UnimplementedNodeServiceServer) WatchNodeState(*GetNodeInfoRequest, grpc.ServerStreamingServer[NodeState]) error { func (UnimplementedNodeServiceServer) WatchNodeState(*GetNodeInfoRequest, grpc.ServerStreamingServer[NodeState]) error {
return status.Error(codes.Unimplemented, "method WatchNodeState not implemented") return status.Error(codes.Unimplemented, "method WatchNodeState not implemented")
} }
func (UnimplementedNodeServiceServer) WatchSharedState(*WatchSharedStateRequest, grpc.ServerStreamingServer[SharedStateEvent]) error {
return status.Error(codes.Unimplemented, "method WatchSharedState not implemented")
}
func (UnimplementedNodeServiceServer) WatchPairedBrowsers(*WatchPairedBrowsersRequest, grpc.ServerStreamingServer[PairedBrowsersState]) error { func (UnimplementedNodeServiceServer) WatchPairedBrowsers(*WatchPairedBrowsersRequest, grpc.ServerStreamingServer[PairedBrowsersState]) error {
return status.Error(codes.Unimplemented, "method WatchPairedBrowsers not implemented") return status.Error(codes.Unimplemented, "method WatchPairedBrowsers not implemented")
} }
@@ -1383,6 +1414,17 @@ func _NodeService_WatchNodeState_Handler(srv interface{}, stream grpc.ServerStre
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type NodeService_WatchNodeStateServer = grpc.ServerStreamingServer[NodeState] type NodeService_WatchNodeStateServer = grpc.ServerStreamingServer[NodeState]
func _NodeService_WatchSharedState_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(WatchSharedStateRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(NodeServiceServer).WatchSharedState(m, &grpc.GenericServerStream[WatchSharedStateRequest, SharedStateEvent]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type NodeService_WatchSharedStateServer = grpc.ServerStreamingServer[SharedStateEvent]
func _NodeService_WatchPairedBrowsers_Handler(srv interface{}, stream grpc.ServerStream) error { func _NodeService_WatchPairedBrowsers_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(WatchPairedBrowsersRequest) m := new(WatchPairedBrowsersRequest)
if err := stream.RecvMsg(m); err != nil { if err := stream.RecvMsg(m); err != nil {
@@ -2574,6 +2616,11 @@ var NodeService_ServiceDesc = grpc.ServiceDesc{
Handler: _NodeService_WatchNodeState_Handler, Handler: _NodeService_WatchNodeState_Handler,
ServerStreams: true, ServerStreams: true,
}, },
{
StreamName: "WatchSharedState",
Handler: _NodeService_WatchSharedState_Handler,
ServerStreams: true,
},
{ {
StreamName: "WatchPairedBrowsers", StreamName: "WatchPairedBrowsers",
Handler: _NodeService_WatchPairedBrowsers_Handler, Handler: _NodeService_WatchPairedBrowsers_Handler,
+37
View File
@@ -9,6 +9,10 @@ service NodeService {
// WatchNodeState sends a complete node-and-peer snapshot at once and // WatchNodeState sends a complete node-and-peer snapshot at once and
// again whenever node details or peers change. // again whenever node details or peers change.
rpc WatchNodeState(GetNodeInfoRequest) returns (stream NodeState); rpc WatchNodeState(GetNodeInfoRequest) returns (stream NodeState);
// WatchSharedState sends one initial snapshot per requested domain, then
// complete domain snapshots whenever shared node state changes. Reconnect
// starts a new stream with a fresh baseline.
rpc WatchSharedState(WatchSharedStateRequest) returns (stream SharedStateEvent);
// WatchPairedBrowsers sends the authorized device list at once and whenever // WatchPairedBrowsers sends the authorized device list at once and whenever
// a browser is paired or revoked. // a browser is paired or revoked.
rpc WatchPairedBrowsers(WatchPairedBrowsersRequest) returns (stream PairedBrowsersState); rpc WatchPairedBrowsers(WatchPairedBrowsersRequest) returns (stream PairedBrowsersState);
@@ -1088,6 +1092,39 @@ message NodeState {
uint64 revision = 3; uint64 revision = 3;
} }
enum SharedStateDomain {
SHARED_STATE_DOMAIN_UNSPECIFIED = 0;
SHARED_STATE_DOMAIN_NODE = 1;
SHARED_STATE_DOMAIN_PEERS = 2;
SHARED_STATE_DOMAIN_WORKERS = 3;
SHARED_STATE_DOMAIN_PAIRED_BROWSERS = 4;
SHARED_STATE_DOMAIN_JOINED_POOL = 5;
}
message WatchSharedStateRequest {
// Empty requests every domain supported by this daemon. Repeated values are
// harmless and are treated as one subscription.
repeated SharedStateDomain domains = 1;
}
message SharedStateEvent {
// Increases for each event on this stream. A reconnect starts with a new
// authoritative baseline and a new revision sequence.
uint64 revision = 1;
SharedStateDomain domain = 2;
bool snapshot = 3;
// A domain can temporarily be unavailable (for example, workers on a node
// that coordinates no pool) without terminating other domain subscriptions.
string error = 4;
oneof state {
NodeState node = 10;
ListPeersResponse peers = 11;
ListWorkersResponse workers = 12;
PairedBrowsersState paired_browsers = 13;
JoinedPool joined_pool = 14;
}
}
message WatchPairedBrowsersRequest {} message WatchPairedBrowsersRequest {}
message RequestActionConfirmationRequest { message RequestActionConfirmationRequest {