From 3b705d7e882fdf7b000fe49620144daa33fd6938 Mon Sep 17 00:00:00 2001 From: Quick104 <31828688+Quick104@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:13:35 -0400 Subject: [PATCH 1/5] feat(watchsync): expand provider plugin contract --- README.md | 30 +- .../silo/plugin/v1/watch_sync_provider.pb.go | 1015 ++++++++++++++--- .../plugin/v1/watch_sync_provider_grpc.pb.go | 90 +- .../plugin/v1/watch_sync_provider_test.go | 29 +- .../convert/watch_sync_provider_test.go | 8 +- pkg/pluginsdk/manifest/manifest.go | 10 +- .../manifest/watch_sync_provider_test.go | 22 + .../silo/plugin/v1/watch_sync_provider.proto | 86 ++ 8 files changed, 1087 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index b355010..ac81154 100644 --- a/README.md +++ b/README.md @@ -126,15 +126,16 @@ The `auth_provider.v1` capability also exposes OAuth-flow RPCs (`InitAuthorize`, `watch_sync_provider.v1` lets external plugins participate in Silo's host-owned watch-provider pipeline. The host owns encrypted per-profile credentials, -OAuth state, durable desired-state events, retries, ordering, and -reconciliation. Plugins are stateless protocol adapters: they receive secrets -only for the duration of an RPC, map rich movie/episode identity to an upstream -service, and return typed apply or retry outcomes. +authorization-code and device-code flow state, durable desired-state events, +retries, ordering, and reconciliation. Plugins are stateless protocol adapters: +they receive secrets only for the duration of an RPC, map rich movie/episode +identity to an upstream service, and return typed apply or retry outcomes. Watch-sync plugins must not persist or log credentials, authorization codes, provider flow state, or secret configuration. `ApplyEvents` is an at-least-once contract; plugins must treat `event_id` as stable across retries and implement -convergent desired-state updates rather than increments. +convergent desired-state updates rather than increments. That rule also applies +to scrobble stops: replaying the same event ID must not create another play. Authenticated RPCs receive the same host-owned capability, configuration, and credential data through `WatchSyncAuthenticatedContext`. The context exists @@ -144,6 +145,12 @@ patches. The host validates and persists them before consuming results, pages, or faults—even when the response contains a fault. If credential persistence fails, the host commits no other response data. +`WatchSyncProviderConfig` is keyed by manifest config key and field, for example +`provider.client_id`. Scalar values are sent as strings and structured values +as JSON. Fields marked secret in the manifest are sent through `secret_values`; +undeclared fields are treated as secret. Plugins must accept configuration from +the RPC context rather than relying on process-global state. + Descriptors and events use the shared `WatchSyncMediaType` enum so advertised support and delivered media cannot drift between string conventions. Apply results pair their delivery status with a typed fault: successful results omit @@ -153,11 +160,14 @@ Connection-wide faults such as invalid credentials belong on the RPC response. `ListRemoteState` returns provider-neutral typed subrecords. `watched` carries a play count and last-watched time; `progress` carries a fractional percentage and -paused time. An item may contain either or both. The host keeps the request -`cursor` fixed while following ephemeral page tokens, commits each successful -page, and only then persists the final `next_cursor`. `complete_snapshot=true` -means the traversal is authoritative; when false, missing items are not -deletions. +paused time; `favorite` and `watchlist` carry list membership. An item may +contain multiple state families. The host requests only the state families a +sync phase needs, keeps that phase's `cursor` fixed while following ephemeral +page tokens, commits each successful page, and only then persists the final +`next_cursor`. `complete_snapshot=true` means the traversal is authoritative; +when false, missing items are not deletions. When +`provides_watchlist_order=true`, the order of returned watchlist states is the +remote list order. ## Scan sources diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go index 7b21e9c..2f928f0 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go @@ -30,6 +30,7 @@ const ( WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_UNSPECIFIED WatchSyncAuthMethod = 0 WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE WatchSyncAuthMethod = 1 WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_API_KEY WatchSyncAuthMethod = 2 + WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_DEVICE_CODE WatchSyncAuthMethod = 3 ) // Enum value maps for WatchSyncAuthMethod. @@ -38,11 +39,13 @@ var ( 0: "WATCH_SYNC_AUTH_METHOD_UNSPECIFIED", 1: "WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE", 2: "WATCH_SYNC_AUTH_METHOD_API_KEY", + 3: "WATCH_SYNC_AUTH_METHOD_DEVICE_CODE", } WatchSyncAuthMethod_value = map[string]int32{ "WATCH_SYNC_AUTH_METHOD_UNSPECIFIED": 0, "WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE": 1, "WATCH_SYNC_AUTH_METHOD_API_KEY": 2, + "WATCH_SYNC_AUTH_METHOD_DEVICE_CODE": 3, } ) @@ -125,9 +128,16 @@ func (WatchSyncMediaType) EnumDescriptor() ([]byte, []int) { type WatchSyncOperation int32 const ( - WatchSyncOperation_WATCH_SYNC_OPERATION_UNSPECIFIED WatchSyncOperation = 0 - WatchSyncOperation_WATCH_SYNC_OPERATION_MARK_WATCHED WatchSyncOperation = 1 - WatchSyncOperation_WATCH_SYNC_OPERATION_MARK_UNWATCHED WatchSyncOperation = 2 + WatchSyncOperation_WATCH_SYNC_OPERATION_UNSPECIFIED WatchSyncOperation = 0 + WatchSyncOperation_WATCH_SYNC_OPERATION_MARK_WATCHED WatchSyncOperation = 1 + WatchSyncOperation_WATCH_SYNC_OPERATION_MARK_UNWATCHED WatchSyncOperation = 2 + WatchSyncOperation_WATCH_SYNC_OPERATION_ADD_FAVORITE WatchSyncOperation = 3 + WatchSyncOperation_WATCH_SYNC_OPERATION_REMOVE_FAVORITE WatchSyncOperation = 4 + WatchSyncOperation_WATCH_SYNC_OPERATION_ADD_TO_WATCHLIST WatchSyncOperation = 5 + WatchSyncOperation_WATCH_SYNC_OPERATION_REMOVE_FROM_WATCHLIST WatchSyncOperation = 6 + WatchSyncOperation_WATCH_SYNC_OPERATION_SCROBBLE_START WatchSyncOperation = 7 + WatchSyncOperation_WATCH_SYNC_OPERATION_SCROBBLE_PAUSE WatchSyncOperation = 8 + WatchSyncOperation_WATCH_SYNC_OPERATION_SCROBBLE_STOP WatchSyncOperation = 9 ) // Enum value maps for WatchSyncOperation. @@ -136,11 +146,25 @@ var ( 0: "WATCH_SYNC_OPERATION_UNSPECIFIED", 1: "WATCH_SYNC_OPERATION_MARK_WATCHED", 2: "WATCH_SYNC_OPERATION_MARK_UNWATCHED", + 3: "WATCH_SYNC_OPERATION_ADD_FAVORITE", + 4: "WATCH_SYNC_OPERATION_REMOVE_FAVORITE", + 5: "WATCH_SYNC_OPERATION_ADD_TO_WATCHLIST", + 6: "WATCH_SYNC_OPERATION_REMOVE_FROM_WATCHLIST", + 7: "WATCH_SYNC_OPERATION_SCROBBLE_START", + 8: "WATCH_SYNC_OPERATION_SCROBBLE_PAUSE", + 9: "WATCH_SYNC_OPERATION_SCROBBLE_STOP", } WatchSyncOperation_value = map[string]int32{ - "WATCH_SYNC_OPERATION_UNSPECIFIED": 0, - "WATCH_SYNC_OPERATION_MARK_WATCHED": 1, - "WATCH_SYNC_OPERATION_MARK_UNWATCHED": 2, + "WATCH_SYNC_OPERATION_UNSPECIFIED": 0, + "WATCH_SYNC_OPERATION_MARK_WATCHED": 1, + "WATCH_SYNC_OPERATION_MARK_UNWATCHED": 2, + "WATCH_SYNC_OPERATION_ADD_FAVORITE": 3, + "WATCH_SYNC_OPERATION_REMOVE_FAVORITE": 4, + "WATCH_SYNC_OPERATION_ADD_TO_WATCHLIST": 5, + "WATCH_SYNC_OPERATION_REMOVE_FROM_WATCHLIST": 6, + "WATCH_SYNC_OPERATION_SCROBBLE_START": 7, + "WATCH_SYNC_OPERATION_SCROBBLE_PAUSE": 8, + "WATCH_SYNC_OPERATION_SCROBBLE_STOP": 9, } ) @@ -171,6 +195,116 @@ func (WatchSyncOperation) EnumDescriptor() ([]byte, []int) { return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{2} } +type WatchSyncRemoteStateKind int32 + +const ( + WatchSyncRemoteStateKind_WATCH_SYNC_REMOTE_STATE_KIND_UNSPECIFIED WatchSyncRemoteStateKind = 0 + WatchSyncRemoteStateKind_WATCH_SYNC_REMOTE_STATE_KIND_WATCHED WatchSyncRemoteStateKind = 1 + WatchSyncRemoteStateKind_WATCH_SYNC_REMOTE_STATE_KIND_PROGRESS WatchSyncRemoteStateKind = 2 + WatchSyncRemoteStateKind_WATCH_SYNC_REMOTE_STATE_KIND_FAVORITE WatchSyncRemoteStateKind = 3 + WatchSyncRemoteStateKind_WATCH_SYNC_REMOTE_STATE_KIND_WATCHLIST WatchSyncRemoteStateKind = 4 +) + +// Enum value maps for WatchSyncRemoteStateKind. +var ( + WatchSyncRemoteStateKind_name = map[int32]string{ + 0: "WATCH_SYNC_REMOTE_STATE_KIND_UNSPECIFIED", + 1: "WATCH_SYNC_REMOTE_STATE_KIND_WATCHED", + 2: "WATCH_SYNC_REMOTE_STATE_KIND_PROGRESS", + 3: "WATCH_SYNC_REMOTE_STATE_KIND_FAVORITE", + 4: "WATCH_SYNC_REMOTE_STATE_KIND_WATCHLIST", + } + WatchSyncRemoteStateKind_value = map[string]int32{ + "WATCH_SYNC_REMOTE_STATE_KIND_UNSPECIFIED": 0, + "WATCH_SYNC_REMOTE_STATE_KIND_WATCHED": 1, + "WATCH_SYNC_REMOTE_STATE_KIND_PROGRESS": 2, + "WATCH_SYNC_REMOTE_STATE_KIND_FAVORITE": 3, + "WATCH_SYNC_REMOTE_STATE_KIND_WATCHLIST": 4, + } +) + +func (x WatchSyncRemoteStateKind) Enum() *WatchSyncRemoteStateKind { + p := new(WatchSyncRemoteStateKind) + *p = x + return p +} + +func (x WatchSyncRemoteStateKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WatchSyncRemoteStateKind) Descriptor() protoreflect.EnumDescriptor { + return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[3].Descriptor() +} + +func (WatchSyncRemoteStateKind) Type() protoreflect.EnumType { + return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[3] +} + +func (x WatchSyncRemoteStateKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WatchSyncRemoteStateKind.Descriptor instead. +func (WatchSyncRemoteStateKind) EnumDescriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{3} +} + +type WatchSyncDeviceAuthorizationStatus int32 + +const ( + WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED WatchSyncDeviceAuthorizationStatus = 0 + WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_PENDING WatchSyncDeviceAuthorizationStatus = 1 + WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_AUTHORIZED WatchSyncDeviceAuthorizationStatus = 2 + WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_DENIED WatchSyncDeviceAuthorizationStatus = 3 + WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_EXPIRED WatchSyncDeviceAuthorizationStatus = 4 +) + +// Enum value maps for WatchSyncDeviceAuthorizationStatus. +var ( + WatchSyncDeviceAuthorizationStatus_name = map[int32]string{ + 0: "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED", + 1: "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_PENDING", + 2: "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_AUTHORIZED", + 3: "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_DENIED", + 4: "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_EXPIRED", + } + WatchSyncDeviceAuthorizationStatus_value = map[string]int32{ + "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED": 0, + "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_PENDING": 1, + "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_AUTHORIZED": 2, + "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_DENIED": 3, + "WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_EXPIRED": 4, + } +) + +func (x WatchSyncDeviceAuthorizationStatus) Enum() *WatchSyncDeviceAuthorizationStatus { + p := new(WatchSyncDeviceAuthorizationStatus) + *p = x + return p +} + +func (x WatchSyncDeviceAuthorizationStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WatchSyncDeviceAuthorizationStatus) Descriptor() protoreflect.EnumDescriptor { + return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[4].Descriptor() +} + +func (WatchSyncDeviceAuthorizationStatus) Type() protoreflect.EnumType { + return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[4] +} + +func (x WatchSyncDeviceAuthorizationStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WatchSyncDeviceAuthorizationStatus.Descriptor instead. +func (WatchSyncDeviceAuthorizationStatus) EnumDescriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{4} +} + type WatchSyncOrigin int32 const ( @@ -207,11 +341,11 @@ func (x WatchSyncOrigin) String() string { } func (WatchSyncOrigin) Descriptor() protoreflect.EnumDescriptor { - return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[3].Descriptor() + return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[5].Descriptor() } func (WatchSyncOrigin) Type() protoreflect.EnumType { - return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[3] + return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[5] } func (x WatchSyncOrigin) Number() protoreflect.EnumNumber { @@ -220,7 +354,7 @@ func (x WatchSyncOrigin) Number() protoreflect.EnumNumber { // Deprecated: Use WatchSyncOrigin.Descriptor instead. func (WatchSyncOrigin) EnumDescriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{3} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{5} } type WatchSyncApplyStatus int32 @@ -262,11 +396,11 @@ func (x WatchSyncApplyStatus) String() string { } func (WatchSyncApplyStatus) Descriptor() protoreflect.EnumDescriptor { - return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[4].Descriptor() + return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[6].Descriptor() } func (WatchSyncApplyStatus) Type() protoreflect.EnumType { - return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[4] + return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[6] } func (x WatchSyncApplyStatus) Number() protoreflect.EnumNumber { @@ -275,7 +409,7 @@ func (x WatchSyncApplyStatus) Number() protoreflect.EnumNumber { // Deprecated: Use WatchSyncApplyStatus.Descriptor instead. func (WatchSyncApplyStatus) EnumDescriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{4} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{6} } type WatchSyncFaultCode int32 @@ -323,11 +457,11 @@ func (x WatchSyncFaultCode) String() string { } func (WatchSyncFaultCode) Descriptor() protoreflect.EnumDescriptor { - return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[5].Descriptor() + return file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[7].Descriptor() } func (WatchSyncFaultCode) Type() protoreflect.EnumType { - return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[5] + return &file_silo_plugin_v1_watch_sync_provider_proto_enumTypes[7] } func (x WatchSyncFaultCode) Number() protoreflect.EnumNumber { @@ -336,7 +470,7 @@ func (x WatchSyncFaultCode) Number() protoreflect.EnumNumber { // Deprecated: Use WatchSyncFaultCode.Descriptor instead. func (WatchSyncFaultCode) EnumDescriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{5} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{7} } type WatchSyncProviderDescriptor struct { @@ -349,8 +483,17 @@ type WatchSyncProviderDescriptor struct { SupportedMediaTypes []WatchSyncMediaType `protobuf:"varint,6,rep,packed,name=supported_media_types,json=supportedMediaTypes,proto3,enum=silo.plugin.v1.WatchSyncMediaType" json:"supported_media_types,omitempty"` ExternalIdNamespaces []string `protobuf:"bytes,7,rep,name=external_id_namespaces,json=externalIdNamespaces,proto3" json:"external_id_namespaces,omitempty"` MaxBatchSize int32 `protobuf:"varint,8,opt,name=max_batch_size,json=maxBatchSize,proto3" json:"max_batch_size,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ImportFavorites bool `protobuf:"varint,9,opt,name=import_favorites,json=importFavorites,proto3" json:"import_favorites,omitempty"` + ExportFavorites bool `protobuf:"varint,10,opt,name=export_favorites,json=exportFavorites,proto3" json:"export_favorites,omitempty"` + RemoveFavorites bool `protobuf:"varint,11,opt,name=remove_favorites,json=removeFavorites,proto3" json:"remove_favorites,omitempty"` + ImportWatchlist bool `protobuf:"varint,12,opt,name=import_watchlist,json=importWatchlist,proto3" json:"import_watchlist,omitempty"` + ExportWatchlist bool `protobuf:"varint,13,opt,name=export_watchlist,json=exportWatchlist,proto3" json:"export_watchlist,omitempty"` + RemoveWatchlist bool `protobuf:"varint,14,opt,name=remove_watchlist,json=removeWatchlist,proto3" json:"remove_watchlist,omitempty"` + // The order of WATCHLIST states returned by ListRemoteState is meaningful. + ProvidesWatchlistOrder bool `protobuf:"varint,15,opt,name=provides_watchlist_order,json=providesWatchlistOrder,proto3" json:"provides_watchlist_order,omitempty"` + ScrobblePlayback bool `protobuf:"varint,16,opt,name=scrobble_playback,json=scrobblePlayback,proto3" json:"scrobble_playback,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WatchSyncProviderDescriptor) Reset() { @@ -439,6 +582,62 @@ func (x *WatchSyncProviderDescriptor) GetMaxBatchSize() int32 { return 0 } +func (x *WatchSyncProviderDescriptor) GetImportFavorites() bool { + if x != nil { + return x.ImportFavorites + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetExportFavorites() bool { + if x != nil { + return x.ExportFavorites + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetRemoveFavorites() bool { + if x != nil { + return x.RemoveFavorites + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetImportWatchlist() bool { + if x != nil { + return x.ImportWatchlist + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetExportWatchlist() bool { + if x != nil { + return x.ExportWatchlist + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetRemoveWatchlist() bool { + if x != nil { + return x.RemoveWatchlist + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetProvidesWatchlistOrder() bool { + if x != nil { + return x.ProvidesWatchlistOrder + } + return false +} + +func (x *WatchSyncProviderDescriptor) GetScrobblePlayback() bool { + if x != nil { + return x.ScrobblePlayback + } + return false +} + type WatchSyncProviderConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Values map[string]string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` @@ -1055,6 +1254,284 @@ func (x *WatchSyncExchangeAPIKeyRequest) GetApiKey() string { return "" } +type WatchSyncStartDeviceAuthorizationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CapabilityId string `protobuf:"bytes,1,opt,name=capability_id,json=capabilityId,proto3" json:"capability_id,omitempty"` + ProviderConfig *WatchSyncProviderConfig `protobuf:"bytes,2,opt,name=provider_config,json=providerConfig,proto3" json:"provider_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSyncStartDeviceAuthorizationRequest) Reset() { + *x = WatchSyncStartDeviceAuthorizationRequest{} + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSyncStartDeviceAuthorizationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSyncStartDeviceAuthorizationRequest) ProtoMessage() {} + +func (x *WatchSyncStartDeviceAuthorizationRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSyncStartDeviceAuthorizationRequest.ProtoReflect.Descriptor instead. +func (*WatchSyncStartDeviceAuthorizationRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{10} +} + +func (x *WatchSyncStartDeviceAuthorizationRequest) GetCapabilityId() string { + if x != nil { + return x.CapabilityId + } + return "" +} + +func (x *WatchSyncStartDeviceAuthorizationRequest) GetProviderConfig() *WatchSyncProviderConfig { + if x != nil { + return x.ProviderConfig + } + return nil +} + +type WatchSyncStartDeviceAuthorizationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserCode string `protobuf:"bytes,1,opt,name=user_code,json=userCode,proto3" json:"user_code,omitempty"` + VerificationUrl string `protobuf:"bytes,2,opt,name=verification_url,json=verificationUrl,proto3" json:"verification_url,omitempty"` + VerificationUrlComplete string `protobuf:"bytes,3,opt,name=verification_url_complete,json=verificationUrlComplete,proto3" json:"verification_url_complete,omitempty"` + // Plugin-defined flow data. The host encrypts it until authorization + // completes and never returns it through the public HTTP API. + ProviderState []byte `protobuf:"bytes,4,opt,name=provider_state,json=providerState,proto3" json:"provider_state,omitempty"` + PollingInterval *durationpb.Duration `protobuf:"bytes,5,opt,name=polling_interval,json=pollingInterval,proto3" json:"polling_interval,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + Fault *WatchSyncFault `protobuf:"bytes,7,opt,name=fault,proto3" json:"fault,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) Reset() { + *x = WatchSyncStartDeviceAuthorizationResponse{} + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSyncStartDeviceAuthorizationResponse) ProtoMessage() {} + +func (x *WatchSyncStartDeviceAuthorizationResponse) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSyncStartDeviceAuthorizationResponse.ProtoReflect.Descriptor instead. +func (*WatchSyncStartDeviceAuthorizationResponse) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{11} +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetUserCode() string { + if x != nil { + return x.UserCode + } + return "" +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetVerificationUrl() string { + if x != nil { + return x.VerificationUrl + } + return "" +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetVerificationUrlComplete() string { + if x != nil { + return x.VerificationUrlComplete + } + return "" +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetProviderState() []byte { + if x != nil { + return x.ProviderState + } + return nil +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetPollingInterval() *durationpb.Duration { + if x != nil { + return x.PollingInterval + } + return nil +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +func (x *WatchSyncStartDeviceAuthorizationResponse) GetFault() *WatchSyncFault { + if x != nil { + return x.Fault + } + return nil +} + +type WatchSyncPollDeviceAuthorizationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CapabilityId string `protobuf:"bytes,1,opt,name=capability_id,json=capabilityId,proto3" json:"capability_id,omitempty"` + ProviderConfig *WatchSyncProviderConfig `protobuf:"bytes,2,opt,name=provider_config,json=providerConfig,proto3" json:"provider_config,omitempty"` + ProviderState []byte `protobuf:"bytes,3,opt,name=provider_state,json=providerState,proto3" json:"provider_state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSyncPollDeviceAuthorizationRequest) Reset() { + *x = WatchSyncPollDeviceAuthorizationRequest{} + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSyncPollDeviceAuthorizationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSyncPollDeviceAuthorizationRequest) ProtoMessage() {} + +func (x *WatchSyncPollDeviceAuthorizationRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSyncPollDeviceAuthorizationRequest.ProtoReflect.Descriptor instead. +func (*WatchSyncPollDeviceAuthorizationRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{12} +} + +func (x *WatchSyncPollDeviceAuthorizationRequest) GetCapabilityId() string { + if x != nil { + return x.CapabilityId + } + return "" +} + +func (x *WatchSyncPollDeviceAuthorizationRequest) GetProviderConfig() *WatchSyncProviderConfig { + if x != nil { + return x.ProviderConfig + } + return nil +} + +func (x *WatchSyncPollDeviceAuthorizationRequest) GetProviderState() []byte { + if x != nil { + return x.ProviderState + } + return nil +} + +type WatchSyncPollDeviceAuthorizationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status WatchSyncDeviceAuthorizationStatus `protobuf:"varint,1,opt,name=status,proto3,enum=silo.plugin.v1.WatchSyncDeviceAuthorizationStatus" json:"status,omitempty"` + // Complete authoritative replacement when status is AUTHORIZED. The host + // persists it before consuming account or fault. + Credentials *WatchSyncCredentials `protobuf:"bytes,2,opt,name=credentials,proto3" json:"credentials,omitempty"` + Account *WatchSyncAccount `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` + // PENDING normally omits fault. DENIED and EXPIRED may use a safe, + // non-retryable fault. Connection-wide and transport failures use fault. + Fault *WatchSyncFault `protobuf:"bytes,4,opt,name=fault,proto3" json:"fault,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSyncPollDeviceAuthorizationResponse) Reset() { + *x = WatchSyncPollDeviceAuthorizationResponse{} + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSyncPollDeviceAuthorizationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSyncPollDeviceAuthorizationResponse) ProtoMessage() {} + +func (x *WatchSyncPollDeviceAuthorizationResponse) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSyncPollDeviceAuthorizationResponse.ProtoReflect.Descriptor instead. +func (*WatchSyncPollDeviceAuthorizationResponse) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{13} +} + +func (x *WatchSyncPollDeviceAuthorizationResponse) GetStatus() WatchSyncDeviceAuthorizationStatus { + if x != nil { + return x.Status + } + return WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED +} + +func (x *WatchSyncPollDeviceAuthorizationResponse) GetCredentials() *WatchSyncCredentials { + if x != nil { + return x.Credentials + } + return nil +} + +func (x *WatchSyncPollDeviceAuthorizationResponse) GetAccount() *WatchSyncAccount { + if x != nil { + return x.Account + } + return nil +} + +func (x *WatchSyncPollDeviceAuthorizationResponse) GetFault() *WatchSyncFault { + if x != nil { + return x.Fault + } + return nil +} + type WatchSyncRefreshCredentialsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Context *WatchSyncAuthenticatedContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` @@ -1064,7 +1541,7 @@ type WatchSyncRefreshCredentialsRequest struct { func (x *WatchSyncRefreshCredentialsRequest) Reset() { *x = WatchSyncRefreshCredentialsRequest{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[10] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1076,7 +1553,7 @@ func (x *WatchSyncRefreshCredentialsRequest) String() string { func (*WatchSyncRefreshCredentialsRequest) ProtoMessage() {} func (x *WatchSyncRefreshCredentialsRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[10] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1089,7 +1566,7 @@ func (x *WatchSyncRefreshCredentialsRequest) ProtoReflect() protoreflect.Message // Deprecated: Use WatchSyncRefreshCredentialsRequest.ProtoReflect.Descriptor instead. func (*WatchSyncRefreshCredentialsRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{10} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{14} } func (x *WatchSyncRefreshCredentialsRequest) GetContext() *WatchSyncAuthenticatedContext { @@ -1114,7 +1591,7 @@ type WatchSyncCredentialResponse struct { func (x *WatchSyncCredentialResponse) Reset() { *x = WatchSyncCredentialResponse{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[11] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1126,7 +1603,7 @@ func (x *WatchSyncCredentialResponse) String() string { func (*WatchSyncCredentialResponse) ProtoMessage() {} func (x *WatchSyncCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[11] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1139,7 +1616,7 @@ func (x *WatchSyncCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncCredentialResponse.ProtoReflect.Descriptor instead. func (*WatchSyncCredentialResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{11} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{15} } func (x *WatchSyncCredentialResponse) GetCredentials() *WatchSyncCredentials { @@ -1172,7 +1649,7 @@ type WatchSyncGetAccountRequest struct { func (x *WatchSyncGetAccountRequest) Reset() { *x = WatchSyncGetAccountRequest{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[12] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1184,7 +1661,7 @@ func (x *WatchSyncGetAccountRequest) String() string { func (*WatchSyncGetAccountRequest) ProtoMessage() {} func (x *WatchSyncGetAccountRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[12] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1197,7 +1674,7 @@ func (x *WatchSyncGetAccountRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncGetAccountRequest.ProtoReflect.Descriptor instead. func (*WatchSyncGetAccountRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{12} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{16} } func (x *WatchSyncGetAccountRequest) GetContext() *WatchSyncAuthenticatedContext { @@ -1217,7 +1694,7 @@ type WatchSyncGetAccountResponse struct { func (x *WatchSyncGetAccountResponse) Reset() { *x = WatchSyncGetAccountResponse{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1229,7 +1706,7 @@ func (x *WatchSyncGetAccountResponse) String() string { func (*WatchSyncGetAccountResponse) ProtoMessage() {} func (x *WatchSyncGetAccountResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1242,7 +1719,7 @@ func (x *WatchSyncGetAccountResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncGetAccountResponse.ProtoReflect.Descriptor instead. func (*WatchSyncGetAccountResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{13} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{17} } func (x *WatchSyncGetAccountResponse) GetAccount() *WatchSyncAccount { @@ -1282,7 +1759,7 @@ type WatchSyncMedia struct { func (x *WatchSyncMedia) Reset() { *x = WatchSyncMedia{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[14] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1294,7 +1771,7 @@ func (x *WatchSyncMedia) String() string { func (*WatchSyncMedia) ProtoMessage() {} func (x *WatchSyncMedia) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[14] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1307,7 +1784,7 @@ func (x *WatchSyncMedia) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncMedia.ProtoReflect.Descriptor instead. func (*WatchSyncMedia) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{14} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{18} } func (x *WatchSyncMedia) GetMediaItemId() string { @@ -1429,13 +1906,20 @@ type WatchSyncEvent struct { DurationSeconds float64 `protobuf:"fixed64,9,opt,name=duration_seconds,json=durationSeconds,proto3" json:"duration_seconds,omitempty"` CompletionPercent float64 `protobuf:"fixed64,10,opt,name=completion_percent,json=completionPercent,proto3" json:"completion_percent,omitempty"` PlayCount int32 `protobuf:"varint,11,opt,name=play_count,json=playCount,proto3" json:"play_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Optional zero-based position for list providers that accept explicit + // ordering. Providers may ignore it unless their API supports order writes. + ListPosition int32 `protobuf:"varint,12,opt,name=list_position,json=listPosition,proto3" json:"list_position,omitempty"` + // Provider-native key previously returned by ListRemoteState. It lets a + // removal address the exact upstream record when external IDs are absent or + // no longer resolve. + ProviderItemKey string `protobuf:"bytes,13,opt,name=provider_item_key,json=providerItemKey,proto3" json:"provider_item_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WatchSyncEvent) Reset() { *x = WatchSyncEvent{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[15] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1447,7 +1931,7 @@ func (x *WatchSyncEvent) String() string { func (*WatchSyncEvent) ProtoMessage() {} func (x *WatchSyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[15] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1460,7 +1944,7 @@ func (x *WatchSyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncEvent.ProtoReflect.Descriptor instead. func (*WatchSyncEvent) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{15} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{19} } func (x *WatchSyncEvent) GetEventId() string { @@ -1540,6 +2024,20 @@ func (x *WatchSyncEvent) GetPlayCount() int32 { return 0 } +func (x *WatchSyncEvent) GetListPosition() int32 { + if x != nil { + return x.ListPosition + } + return 0 +} + +func (x *WatchSyncEvent) GetProviderItemKey() string { + if x != nil { + return x.ProviderItemKey + } + return "" +} + type WatchSyncApplyEventsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Context *WatchSyncAuthenticatedContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` @@ -1552,7 +2050,7 @@ type WatchSyncApplyEventsRequest struct { func (x *WatchSyncApplyEventsRequest) Reset() { *x = WatchSyncApplyEventsRequest{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[16] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1564,7 +2062,7 @@ func (x *WatchSyncApplyEventsRequest) String() string { func (*WatchSyncApplyEventsRequest) ProtoMessage() {} func (x *WatchSyncApplyEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[16] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1577,7 +2075,7 @@ func (x *WatchSyncApplyEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncApplyEventsRequest.ProtoReflect.Descriptor instead. func (*WatchSyncApplyEventsRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{16} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{20} } func (x *WatchSyncApplyEventsRequest) GetContext() *WatchSyncAuthenticatedContext { @@ -1609,7 +2107,7 @@ type WatchSyncApplyResult struct { func (x *WatchSyncApplyResult) Reset() { *x = WatchSyncApplyResult{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[17] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1621,7 +2119,7 @@ func (x *WatchSyncApplyResult) String() string { func (*WatchSyncApplyResult) ProtoMessage() {} func (x *WatchSyncApplyResult) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[17] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1634,7 +2132,7 @@ func (x *WatchSyncApplyResult) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncApplyResult.ProtoReflect.Descriptor instead. func (*WatchSyncApplyResult) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{17} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{21} } func (x *WatchSyncApplyResult) GetEventId() string { @@ -1673,7 +2171,7 @@ type WatchSyncApplyEventsResponse struct { func (x *WatchSyncApplyEventsResponse) Reset() { *x = WatchSyncApplyEventsResponse{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[18] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1685,7 +2183,7 @@ func (x *WatchSyncApplyEventsResponse) String() string { func (*WatchSyncApplyEventsResponse) ProtoMessage() {} func (x *WatchSyncApplyEventsResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[18] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1698,7 +2196,7 @@ func (x *WatchSyncApplyEventsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncApplyEventsResponse.ProtoReflect.Descriptor instead. func (*WatchSyncApplyEventsResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{18} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{22} } func (x *WatchSyncApplyEventsResponse) GetResults() []*WatchSyncApplyResult { @@ -1734,14 +2232,17 @@ type WatchSyncListRemoteStateRequest struct { // after the traversal completes or fails. PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` // Requested maximum number of items. Plugins may return fewer. - PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // State families requested by the host. Empty means every family advertised + // by the descriptor, preserving compatibility with v0.12 hosts and plugins. + StateKinds []WatchSyncRemoteStateKind `protobuf:"varint,5,rep,packed,name=state_kinds,json=stateKinds,proto3,enum=silo.plugin.v1.WatchSyncRemoteStateKind" json:"state_kinds,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *WatchSyncListRemoteStateRequest) Reset() { *x = WatchSyncListRemoteStateRequest{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[19] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1753,7 +2254,7 @@ func (x *WatchSyncListRemoteStateRequest) String() string { func (*WatchSyncListRemoteStateRequest) ProtoMessage() {} func (x *WatchSyncListRemoteStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[19] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1766,7 +2267,7 @@ func (x *WatchSyncListRemoteStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncListRemoteStateRequest.ProtoReflect.Descriptor instead. func (*WatchSyncListRemoteStateRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{19} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{23} } func (x *WatchSyncListRemoteStateRequest) GetContext() *WatchSyncAuthenticatedContext { @@ -1797,6 +2298,13 @@ func (x *WatchSyncListRemoteStateRequest) GetPageSize() int32 { return 0 } +func (x *WatchSyncListRemoteStateRequest) GetStateKinds() []WatchSyncRemoteStateKind { + if x != nil { + return x.StateKinds + } + return nil +} + type WatchSyncRemoteWatchedState struct { state protoimpl.MessageState `protogen:"open.v1"` // Number of completed plays known to the provider. Must be at least one when @@ -1810,7 +2318,7 @@ type WatchSyncRemoteWatchedState struct { func (x *WatchSyncRemoteWatchedState) Reset() { *x = WatchSyncRemoteWatchedState{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[20] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1822,7 +2330,7 @@ func (x *WatchSyncRemoteWatchedState) String() string { func (*WatchSyncRemoteWatchedState) ProtoMessage() {} func (x *WatchSyncRemoteWatchedState) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[20] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1835,7 +2343,7 @@ func (x *WatchSyncRemoteWatchedState) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncRemoteWatchedState.ProtoReflect.Descriptor instead. func (*WatchSyncRemoteWatchedState) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{20} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{24} } func (x *WatchSyncRemoteWatchedState) GetPlayCount() int32 { @@ -1864,7 +2372,7 @@ type WatchSyncRemoteProgressState struct { func (x *WatchSyncRemoteProgressState) Reset() { *x = WatchSyncRemoteProgressState{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[21] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1876,7 +2384,7 @@ func (x *WatchSyncRemoteProgressState) String() string { func (*WatchSyncRemoteProgressState) ProtoMessage() {} func (x *WatchSyncRemoteProgressState) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[21] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1889,7 +2397,7 @@ func (x *WatchSyncRemoteProgressState) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncRemoteProgressState.ProtoReflect.Descriptor instead. func (*WatchSyncRemoteProgressState) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{21} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{25} } func (x *WatchSyncRemoteProgressState) GetProgressPercent() float64 { @@ -1906,6 +2414,51 @@ func (x *WatchSyncRemoteProgressState) GetPausedAt() *timestamppb.Timestamp { return nil } +type WatchSyncRemoteListState struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Provider timestamp for when the item was added, when available. + ListedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=listed_at,json=listedAt,proto3" json:"listed_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSyncRemoteListState) Reset() { + *x = WatchSyncRemoteListState{} + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSyncRemoteListState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSyncRemoteListState) ProtoMessage() {} + +func (x *WatchSyncRemoteListState) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSyncRemoteListState.ProtoReflect.Descriptor instead. +func (*WatchSyncRemoteListState) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{26} +} + +func (x *WatchSyncRemoteListState) GetListedAt() *timestamppb.Timestamp { + if x != nil { + return x.ListedAt + } + return nil +} + type WatchSyncRemoteState struct { state protoimpl.MessageState `protogen:"open.v1"` ProviderItemKey string `protobuf:"bytes,1,opt,name=provider_item_key,json=providerItemKey,proto3" json:"provider_item_key,omitempty"` @@ -1914,13 +2467,15 @@ type WatchSyncRemoteState struct { // provider reports a completed play and a separate resume point. Watched *WatchSyncRemoteWatchedState `protobuf:"bytes,3,opt,name=watched,proto3" json:"watched,omitempty"` Progress *WatchSyncRemoteProgressState `protobuf:"bytes,4,opt,name=progress,proto3" json:"progress,omitempty"` + Favorite *WatchSyncRemoteListState `protobuf:"bytes,5,opt,name=favorite,proto3" json:"favorite,omitempty"` + Watchlist *WatchSyncRemoteListState `protobuf:"bytes,6,opt,name=watchlist,proto3" json:"watchlist,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *WatchSyncRemoteState) Reset() { *x = WatchSyncRemoteState{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[22] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1932,7 +2487,7 @@ func (x *WatchSyncRemoteState) String() string { func (*WatchSyncRemoteState) ProtoMessage() {} func (x *WatchSyncRemoteState) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[22] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1945,7 +2500,7 @@ func (x *WatchSyncRemoteState) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncRemoteState.ProtoReflect.Descriptor instead. func (*WatchSyncRemoteState) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{22} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{27} } func (x *WatchSyncRemoteState) GetProviderItemKey() string { @@ -1976,6 +2531,20 @@ func (x *WatchSyncRemoteState) GetProgress() *WatchSyncRemoteProgressState { return nil } +func (x *WatchSyncRemoteState) GetFavorite() *WatchSyncRemoteListState { + if x != nil { + return x.Favorite + } + return nil +} + +func (x *WatchSyncRemoteState) GetWatchlist() *WatchSyncRemoteListState { + if x != nil { + return x.Watchlist + } + return nil +} + type WatchSyncListRemoteStateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Items []*WatchSyncRemoteState `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` @@ -2002,7 +2571,7 @@ type WatchSyncListRemoteStateResponse struct { func (x *WatchSyncListRemoteStateResponse) Reset() { *x = WatchSyncListRemoteStateResponse{} - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[23] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2014,7 +2583,7 @@ func (x *WatchSyncListRemoteStateResponse) String() string { func (*WatchSyncListRemoteStateResponse) ProtoMessage() {} func (x *WatchSyncListRemoteStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[23] + mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2027,7 +2596,7 @@ func (x *WatchSyncListRemoteStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSyncListRemoteStateResponse.ProtoReflect.Descriptor instead. func (*WatchSyncListRemoteStateResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{23} + return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{28} } func (x *WatchSyncListRemoteStateResponse) GetItems() []*WatchSyncRemoteState { @@ -2076,7 +2645,7 @@ var File_silo_plugin_v1_watch_sync_provider_proto protoreflect.FileDescriptor const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\n" + - "(silo/plugin/v1/watch_sync_provider.proto\x12\x0esilo.plugin.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xbb\x03\n" + + "(silo/plugin/v1/watch_sync_provider.proto\x12\x0esilo.plugin.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xa4\x06\n" + "\x1bWatchSyncProviderDescriptor\x12F\n" + "\fauth_methods\x18\x01 \x03(\x0e2#.silo.plugin.v1.WatchSyncAuthMethodR\vauthMethods\x12%\n" + "\x0eexport_watched\x18\x02 \x01(\bR\rexportWatched\x12)\n" + @@ -2085,7 +2654,16 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x0fimport_progress\x18\x05 \x01(\bR\x0eimportProgress\x12V\n" + "\x15supported_media_types\x18\x06 \x03(\x0e2\".silo.plugin.v1.WatchSyncMediaTypeR\x13supportedMediaTypes\x124\n" + "\x16external_id_namespaces\x18\a \x03(\tR\x14externalIdNamespaces\x12$\n" + - "\x0emax_batch_size\x18\b \x01(\x05R\fmaxBatchSize\"\xc2\x02\n" + + "\x0emax_batch_size\x18\b \x01(\x05R\fmaxBatchSize\x12)\n" + + "\x10import_favorites\x18\t \x01(\bR\x0fimportFavorites\x12)\n" + + "\x10export_favorites\x18\n" + + " \x01(\bR\x0fexportFavorites\x12)\n" + + "\x10remove_favorites\x18\v \x01(\bR\x0fremoveFavorites\x12)\n" + + "\x10import_watchlist\x18\f \x01(\bR\x0fimportWatchlist\x12)\n" + + "\x10export_watchlist\x18\r \x01(\bR\x0fexportWatchlist\x12)\n" + + "\x10remove_watchlist\x18\x0e \x01(\bR\x0fremoveWatchlist\x128\n" + + "\x18provides_watchlist_order\x18\x0f \x01(\bR\x16providesWatchlistOrder\x12+\n" + + "\x11scrobble_playback\x18\x10 \x01(\bR\x10scrobblePlayback\"\xc2\x02\n" + "\x17WatchSyncProviderConfig\x12K\n" + "\x06values\x18\x01 \x03(\v23.silo.plugin.v1.WatchSyncProviderConfig.ValuesEntryR\x06values\x12^\n" + "\rsecret_values\x18\x02 \x03(\v29.silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntryR\fsecretValues\x1a9\n" + @@ -2142,7 +2720,28 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x1eWatchSyncExchangeAPIKeyRequest\x12#\n" + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\x12\x17\n" + - "\aapi_key\x18\x03 \x01(\tR\x06apiKey\"m\n" + + "\aapi_key\x18\x03 \x01(\tR\x06apiKey\"\xa1\x01\n" + + "(WatchSyncStartDeviceAuthorizationRequest\x12#\n" + + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\"\x8d\x03\n" + + ")WatchSyncStartDeviceAuthorizationResponse\x12\x1b\n" + + "\tuser_code\x18\x01 \x01(\tR\buserCode\x12)\n" + + "\x10verification_url\x18\x02 \x01(\tR\x0fverificationUrl\x12:\n" + + "\x19verification_url_complete\x18\x03 \x01(\tR\x17verificationUrlComplete\x12%\n" + + "\x0eprovider_state\x18\x04 \x01(\fR\rproviderState\x12D\n" + + "\x10polling_interval\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\x0fpollingInterval\x129\n" + + "\n" + + "expires_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + + "\x05fault\x18\a \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"\xc7\x01\n" + + "'WatchSyncPollDeviceAuthorizationRequest\x12#\n" + + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\x12%\n" + + "\x0eprovider_state\x18\x03 \x01(\fR\rproviderState\"\xb0\x02\n" + + "(WatchSyncPollDeviceAuthorizationResponse\x12J\n" + + "\x06status\x18\x01 \x01(\x0e22.silo.plugin.v1.WatchSyncDeviceAuthorizationStatusR\x06status\x12F\n" + + "\vcredentials\x18\x02 \x01(\v2$.silo.plugin.v1.WatchSyncCredentialsR\vcredentials\x12:\n" + + "\aaccount\x18\x03 \x01(\v2 .silo.plugin.v1.WatchSyncAccountR\aaccount\x124\n" + + "\x05fault\x18\x04 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"m\n" + "\"WatchSyncRefreshCredentialsRequest\x12G\n" + "\acontext\x18\x01 \x01(\v2-.silo.plugin.v1.WatchSyncAuthenticatedContextR\acontext\"\xd7\x01\n" + "\x1bWatchSyncCredentialResponse\x12F\n" + @@ -2178,7 +2777,7 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aD\n" + "\x16SeriesExternalIdsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x97\x04\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x04\n" + "\x0eWatchSyncEvent\x12\x19\n" + "\bevent_id\x18\x01 \x01(\tR\aeventId\x12@\n" + "\toperation\x18\x02 \x01(\x0e2\".silo.plugin.v1.WatchSyncOperationR\toperation\x127\n" + @@ -2193,7 +2792,9 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x12completion_percent\x18\n" + " \x01(\x01R\x11completionPercent\x12\x1d\n" + "\n" + - "play_count\x18\v \x01(\x05R\tplayCount\"\x9e\x01\n" + + "play_count\x18\v \x01(\x05R\tplayCount\x12#\n" + + "\rlist_position\x18\f \x01(\x05R\flistPosition\x12*\n" + + "\x11provider_item_key\x18\r \x01(\tR\x0fproviderItemKey\"\x9e\x01\n" + "\x1bWatchSyncApplyEventsRequest\x12G\n" + "\acontext\x18\x01 \x01(\v2-.silo.plugin.v1.WatchSyncAuthenticatedContextR\acontext\x126\n" + "\x06events\x18\x02 \x03(\v2\x1e.silo.plugin.v1.WatchSyncEventR\x06events\"\xa5\x01\n" + @@ -2204,25 +2805,31 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x1cWatchSyncApplyEventsResponse\x12>\n" + "\aresults\x18\x01 \x03(\v2$.silo.plugin.v1.WatchSyncApplyResultR\aresults\x12U\n" + "\x13updated_credentials\x18\x02 \x01(\v2$.silo.plugin.v1.WatchSyncCredentialsR\x12updatedCredentials\x124\n" + - "\x05fault\x18\x03 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"\xbe\x01\n" + + "\x05fault\x18\x03 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"\x89\x02\n" + "\x1fWatchSyncListRemoteStateRequest\x12G\n" + "\acontext\x18\x01 \x01(\v2-.silo.plugin.v1.WatchSyncAuthenticatedContextR\acontext\x12\x16\n" + "\x06cursor\x18\x02 \x01(\tR\x06cursor\x12\x1d\n" + "\n" + "page_token\x18\x03 \x01(\tR\tpageToken\x12\x1b\n" + - "\tpage_size\x18\x04 \x01(\x05R\bpageSize\"\x80\x01\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12I\n" + + "\vstate_kinds\x18\x05 \x03(\x0e2(.silo.plugin.v1.WatchSyncRemoteStateKindR\n" + + "stateKinds\"\x80\x01\n" + "\x1bWatchSyncRemoteWatchedState\x12\x1d\n" + "\n" + "play_count\x18\x01 \x01(\x05R\tplayCount\x12B\n" + "\x0flast_watched_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\rlastWatchedAt\"\x82\x01\n" + "\x1cWatchSyncRemoteProgressState\x12)\n" + "\x10progress_percent\x18\x01 \x01(\x01R\x0fprogressPercent\x127\n" + - "\tpaused_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bpausedAt\"\x89\x02\n" + + "\tpaused_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bpausedAt\"S\n" + + "\x18WatchSyncRemoteListState\x127\n" + + "\tlisted_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\blistedAt\"\x97\x03\n" + "\x14WatchSyncRemoteState\x12*\n" + "\x11provider_item_key\x18\x01 \x01(\tR\x0fproviderItemKey\x124\n" + "\x05media\x18\x02 \x01(\v2\x1e.silo.plugin.v1.WatchSyncMediaR\x05media\x12E\n" + "\awatched\x18\x03 \x01(\v2+.silo.plugin.v1.WatchSyncRemoteWatchedStateR\awatched\x12H\n" + - "\bprogress\x18\x04 \x01(\v2,.silo.plugin.v1.WatchSyncRemoteProgressStateR\bprogress\"\xe1\x02\n" + + "\bprogress\x18\x04 \x01(\v2,.silo.plugin.v1.WatchSyncRemoteProgressStateR\bprogress\x12D\n" + + "\bfavorite\x18\x05 \x01(\v2(.silo.plugin.v1.WatchSyncRemoteListStateR\bfavorite\x12F\n" + + "\twatchlist\x18\x06 \x01(\v2(.silo.plugin.v1.WatchSyncRemoteListStateR\twatchlist\"\xe1\x02\n" + " WatchSyncListRemoteStateResponse\x12:\n" + "\x05items\x18\x01 \x03(\v2$.silo.plugin.v1.WatchSyncRemoteStateR\x05items\x12&\n" + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1f\n" + @@ -2230,19 +2837,39 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "nextCursor\x12+\n" + "\x11complete_snapshot\x18\x04 \x01(\bR\x10completeSnapshot\x12U\n" + "\x13updated_credentials\x18\x05 \x01(\v2$.silo.plugin.v1.WatchSyncCredentialsR\x12updatedCredentials\x124\n" + - "\x05fault\x18\x06 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault*\x90\x01\n" + + "\x05fault\x18\x06 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault*\xb8\x01\n" + "\x13WatchSyncAuthMethod\x12&\n" + "\"WATCH_SYNC_AUTH_METHOD_UNSPECIFIED\x10\x00\x12-\n" + ")WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE\x10\x01\x12\"\n" + - "\x1eWATCH_SYNC_AUTH_METHOD_API_KEY\x10\x02*\x7f\n" + + "\x1eWATCH_SYNC_AUTH_METHOD_API_KEY\x10\x02\x12&\n" + + "\"WATCH_SYNC_AUTH_METHOD_DEVICE_CODE\x10\x03*\x7f\n" + "\x12WatchSyncMediaType\x12%\n" + "!WATCH_SYNC_MEDIA_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n" + "\x1bWATCH_SYNC_MEDIA_TYPE_MOVIE\x10\x01\x12!\n" + - "\x1dWATCH_SYNC_MEDIA_TYPE_EPISODE\x10\x02*\x8a\x01\n" + + "\x1dWATCH_SYNC_MEDIA_TYPE_EPISODE\x10\x02*\xb0\x03\n" + "\x12WatchSyncOperation\x12$\n" + " WATCH_SYNC_OPERATION_UNSPECIFIED\x10\x00\x12%\n" + "!WATCH_SYNC_OPERATION_MARK_WATCHED\x10\x01\x12'\n" + - "#WATCH_SYNC_OPERATION_MARK_UNWATCHED\x10\x02*\xa3\x01\n" + + "#WATCH_SYNC_OPERATION_MARK_UNWATCHED\x10\x02\x12%\n" + + "!WATCH_SYNC_OPERATION_ADD_FAVORITE\x10\x03\x12(\n" + + "$WATCH_SYNC_OPERATION_REMOVE_FAVORITE\x10\x04\x12)\n" + + "%WATCH_SYNC_OPERATION_ADD_TO_WATCHLIST\x10\x05\x12.\n" + + "*WATCH_SYNC_OPERATION_REMOVE_FROM_WATCHLIST\x10\x06\x12'\n" + + "#WATCH_SYNC_OPERATION_SCROBBLE_START\x10\a\x12'\n" + + "#WATCH_SYNC_OPERATION_SCROBBLE_PAUSE\x10\b\x12&\n" + + "\"WATCH_SYNC_OPERATION_SCROBBLE_STOP\x10\t*\xf4\x01\n" + + "\x18WatchSyncRemoteStateKind\x12,\n" + + "(WATCH_SYNC_REMOTE_STATE_KIND_UNSPECIFIED\x10\x00\x12(\n" + + "$WATCH_SYNC_REMOTE_STATE_KIND_WATCHED\x10\x01\x12)\n" + + "%WATCH_SYNC_REMOTE_STATE_KIND_PROGRESS\x10\x02\x12)\n" + + "%WATCH_SYNC_REMOTE_STATE_KIND_FAVORITE\x10\x03\x12*\n" + + "&WATCH_SYNC_REMOTE_STATE_KIND_WATCHLIST\x10\x04*\xae\x02\n" + + "\"WatchSyncDeviceAuthorizationStatus\x126\n" + + "2WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED\x10\x00\x122\n" + + ".WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_PENDING\x10\x01\x125\n" + + "1WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_AUTHORIZED\x10\x02\x121\n" + + "-WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_DENIED\x10\x03\x122\n" + + ".WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_EXPIRED\x10\x04*\xa3\x01\n" + "\x0fWatchSyncOrigin\x12!\n" + "\x1dWATCH_SYNC_ORIGIN_UNSPECIFIED\x10\x00\x12)\n" + "%WATCH_SYNC_ORIGIN_PLAYBACK_COMPLETION\x10\x01\x12\x1c\n" + @@ -2261,11 +2888,13 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "(WATCH_SYNC_FAULT_CODE_INVALID_CREDENTIAL\x10\x03\x12+\n" + "'WATCH_SYNC_FAULT_CODE_PERMISSION_DENIED\x10\x04\x12)\n" + "%WATCH_SYNC_FAULT_CODE_INVALID_REQUEST\x10\x05\x12#\n" + - "\x1fWATCH_SYNC_FAULT_CODE_PERMANENT\x10\x062\x9b\x06\n" + + "\x1fWATCH_SYNC_FAULT_CODE_PERMANENT\x10\x062\xbc\b\n" + "\x11WatchSyncProvider\x12n\n" + "\rInitAuthorize\x12-.silo.plugin.v1.WatchSyncInitAuthorizeRequest\x1a..silo.plugin.v1.WatchSyncInitAuthorizeResponse\x12i\n" + "\fExchangeCode\x12,.silo.plugin.v1.WatchSyncExchangeCodeRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12m\n" + - "\x0eExchangeAPIKey\x12..silo.plugin.v1.WatchSyncExchangeAPIKeyRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12u\n" + + "\x0eExchangeAPIKey\x12..silo.plugin.v1.WatchSyncExchangeAPIKeyRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12\x8f\x01\n" + + "\x18StartDeviceAuthorization\x128.silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest\x1a9.silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse\x12\x8c\x01\n" + + "\x17PollDeviceAuthorization\x127.silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest\x1a8.silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse\x12u\n" + "\x12RefreshCredentials\x122.silo.plugin.v1.WatchSyncRefreshCredentialsRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12e\n" + "\n" + "GetAccount\x12*.silo.plugin.v1.WatchSyncGetAccountRequest\x1a+.silo.plugin.v1.WatchSyncGetAccountResponse\x12h\n" + @@ -2284,113 +2913,137 @@ func file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP() []byte { return file_silo_plugin_v1_watch_sync_provider_proto_rawDescData } -var file_silo_plugin_v1_watch_sync_provider_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_silo_plugin_v1_watch_sync_provider_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_silo_plugin_v1_watch_sync_provider_proto_enumTypes = make([]protoimpl.EnumInfo, 8) +var file_silo_plugin_v1_watch_sync_provider_proto_msgTypes = make([]protoimpl.MessageInfo, 34) var file_silo_plugin_v1_watch_sync_provider_proto_goTypes = []any{ - (WatchSyncAuthMethod)(0), // 0: silo.plugin.v1.WatchSyncAuthMethod - (WatchSyncMediaType)(0), // 1: silo.plugin.v1.WatchSyncMediaType - (WatchSyncOperation)(0), // 2: silo.plugin.v1.WatchSyncOperation - (WatchSyncOrigin)(0), // 3: silo.plugin.v1.WatchSyncOrigin - (WatchSyncApplyStatus)(0), // 4: silo.plugin.v1.WatchSyncApplyStatus - (WatchSyncFaultCode)(0), // 5: silo.plugin.v1.WatchSyncFaultCode - (*WatchSyncProviderDescriptor)(nil), // 6: silo.plugin.v1.WatchSyncProviderDescriptor - (*WatchSyncProviderConfig)(nil), // 7: silo.plugin.v1.WatchSyncProviderConfig - (*WatchSyncCredentials)(nil), // 8: silo.plugin.v1.WatchSyncCredentials - (*WatchSyncAuthenticatedContext)(nil), // 9: silo.plugin.v1.WatchSyncAuthenticatedContext - (*WatchSyncAccount)(nil), // 10: silo.plugin.v1.WatchSyncAccount - (*WatchSyncFault)(nil), // 11: silo.plugin.v1.WatchSyncFault - (*WatchSyncInitAuthorizeRequest)(nil), // 12: silo.plugin.v1.WatchSyncInitAuthorizeRequest - (*WatchSyncInitAuthorizeResponse)(nil), // 13: silo.plugin.v1.WatchSyncInitAuthorizeResponse - (*WatchSyncExchangeCodeRequest)(nil), // 14: silo.plugin.v1.WatchSyncExchangeCodeRequest - (*WatchSyncExchangeAPIKeyRequest)(nil), // 15: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest - (*WatchSyncRefreshCredentialsRequest)(nil), // 16: silo.plugin.v1.WatchSyncRefreshCredentialsRequest - (*WatchSyncCredentialResponse)(nil), // 17: silo.plugin.v1.WatchSyncCredentialResponse - (*WatchSyncGetAccountRequest)(nil), // 18: silo.plugin.v1.WatchSyncGetAccountRequest - (*WatchSyncGetAccountResponse)(nil), // 19: silo.plugin.v1.WatchSyncGetAccountResponse - (*WatchSyncMedia)(nil), // 20: silo.plugin.v1.WatchSyncMedia - (*WatchSyncEvent)(nil), // 21: silo.plugin.v1.WatchSyncEvent - (*WatchSyncApplyEventsRequest)(nil), // 22: silo.plugin.v1.WatchSyncApplyEventsRequest - (*WatchSyncApplyResult)(nil), // 23: silo.plugin.v1.WatchSyncApplyResult - (*WatchSyncApplyEventsResponse)(nil), // 24: silo.plugin.v1.WatchSyncApplyEventsResponse - (*WatchSyncListRemoteStateRequest)(nil), // 25: silo.plugin.v1.WatchSyncListRemoteStateRequest - (*WatchSyncRemoteWatchedState)(nil), // 26: silo.plugin.v1.WatchSyncRemoteWatchedState - (*WatchSyncRemoteProgressState)(nil), // 27: silo.plugin.v1.WatchSyncRemoteProgressState - (*WatchSyncRemoteState)(nil), // 28: silo.plugin.v1.WatchSyncRemoteState - (*WatchSyncListRemoteStateResponse)(nil), // 29: silo.plugin.v1.WatchSyncListRemoteStateResponse - nil, // 30: silo.plugin.v1.WatchSyncProviderConfig.ValuesEntry - nil, // 31: silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntry - nil, // 32: silo.plugin.v1.WatchSyncCredentials.SecretAttributesEntry - nil, // 33: silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry - nil, // 34: silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry - (*timestamppb.Timestamp)(nil), // 35: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 36: google.protobuf.Duration - (*structpb.Struct)(nil), // 37: google.protobuf.Struct + (WatchSyncAuthMethod)(0), // 0: silo.plugin.v1.WatchSyncAuthMethod + (WatchSyncMediaType)(0), // 1: silo.plugin.v1.WatchSyncMediaType + (WatchSyncOperation)(0), // 2: silo.plugin.v1.WatchSyncOperation + (WatchSyncRemoteStateKind)(0), // 3: silo.plugin.v1.WatchSyncRemoteStateKind + (WatchSyncDeviceAuthorizationStatus)(0), // 4: silo.plugin.v1.WatchSyncDeviceAuthorizationStatus + (WatchSyncOrigin)(0), // 5: silo.plugin.v1.WatchSyncOrigin + (WatchSyncApplyStatus)(0), // 6: silo.plugin.v1.WatchSyncApplyStatus + (WatchSyncFaultCode)(0), // 7: silo.plugin.v1.WatchSyncFaultCode + (*WatchSyncProviderDescriptor)(nil), // 8: silo.plugin.v1.WatchSyncProviderDescriptor + (*WatchSyncProviderConfig)(nil), // 9: silo.plugin.v1.WatchSyncProviderConfig + (*WatchSyncCredentials)(nil), // 10: silo.plugin.v1.WatchSyncCredentials + (*WatchSyncAuthenticatedContext)(nil), // 11: silo.plugin.v1.WatchSyncAuthenticatedContext + (*WatchSyncAccount)(nil), // 12: silo.plugin.v1.WatchSyncAccount + (*WatchSyncFault)(nil), // 13: silo.plugin.v1.WatchSyncFault + (*WatchSyncInitAuthorizeRequest)(nil), // 14: silo.plugin.v1.WatchSyncInitAuthorizeRequest + (*WatchSyncInitAuthorizeResponse)(nil), // 15: silo.plugin.v1.WatchSyncInitAuthorizeResponse + (*WatchSyncExchangeCodeRequest)(nil), // 16: silo.plugin.v1.WatchSyncExchangeCodeRequest + (*WatchSyncExchangeAPIKeyRequest)(nil), // 17: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest + (*WatchSyncStartDeviceAuthorizationRequest)(nil), // 18: silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest + (*WatchSyncStartDeviceAuthorizationResponse)(nil), // 19: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse + (*WatchSyncPollDeviceAuthorizationRequest)(nil), // 20: silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest + (*WatchSyncPollDeviceAuthorizationResponse)(nil), // 21: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse + (*WatchSyncRefreshCredentialsRequest)(nil), // 22: silo.plugin.v1.WatchSyncRefreshCredentialsRequest + (*WatchSyncCredentialResponse)(nil), // 23: silo.plugin.v1.WatchSyncCredentialResponse + (*WatchSyncGetAccountRequest)(nil), // 24: silo.plugin.v1.WatchSyncGetAccountRequest + (*WatchSyncGetAccountResponse)(nil), // 25: silo.plugin.v1.WatchSyncGetAccountResponse + (*WatchSyncMedia)(nil), // 26: silo.plugin.v1.WatchSyncMedia + (*WatchSyncEvent)(nil), // 27: silo.plugin.v1.WatchSyncEvent + (*WatchSyncApplyEventsRequest)(nil), // 28: silo.plugin.v1.WatchSyncApplyEventsRequest + (*WatchSyncApplyResult)(nil), // 29: silo.plugin.v1.WatchSyncApplyResult + (*WatchSyncApplyEventsResponse)(nil), // 30: silo.plugin.v1.WatchSyncApplyEventsResponse + (*WatchSyncListRemoteStateRequest)(nil), // 31: silo.plugin.v1.WatchSyncListRemoteStateRequest + (*WatchSyncRemoteWatchedState)(nil), // 32: silo.plugin.v1.WatchSyncRemoteWatchedState + (*WatchSyncRemoteProgressState)(nil), // 33: silo.plugin.v1.WatchSyncRemoteProgressState + (*WatchSyncRemoteListState)(nil), // 34: silo.plugin.v1.WatchSyncRemoteListState + (*WatchSyncRemoteState)(nil), // 35: silo.plugin.v1.WatchSyncRemoteState + (*WatchSyncListRemoteStateResponse)(nil), // 36: silo.plugin.v1.WatchSyncListRemoteStateResponse + nil, // 37: silo.plugin.v1.WatchSyncProviderConfig.ValuesEntry + nil, // 38: silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntry + nil, // 39: silo.plugin.v1.WatchSyncCredentials.SecretAttributesEntry + nil, // 40: silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry + nil, // 41: silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 43: google.protobuf.Duration + (*structpb.Struct)(nil), // 44: google.protobuf.Struct } var file_silo_plugin_v1_watch_sync_provider_proto_depIdxs = []int32{ 0, // 0: silo.plugin.v1.WatchSyncProviderDescriptor.auth_methods:type_name -> silo.plugin.v1.WatchSyncAuthMethod 1, // 1: silo.plugin.v1.WatchSyncProviderDescriptor.supported_media_types:type_name -> silo.plugin.v1.WatchSyncMediaType - 30, // 2: silo.plugin.v1.WatchSyncProviderConfig.values:type_name -> silo.plugin.v1.WatchSyncProviderConfig.ValuesEntry - 31, // 3: silo.plugin.v1.WatchSyncProviderConfig.secret_values:type_name -> silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntry - 35, // 4: silo.plugin.v1.WatchSyncCredentials.expires_at:type_name -> google.protobuf.Timestamp - 32, // 5: silo.plugin.v1.WatchSyncCredentials.secret_attributes:type_name -> silo.plugin.v1.WatchSyncCredentials.SecretAttributesEntry - 7, // 6: silo.plugin.v1.WatchSyncAuthenticatedContext.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 8, // 7: silo.plugin.v1.WatchSyncAuthenticatedContext.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 5, // 8: silo.plugin.v1.WatchSyncFault.code:type_name -> silo.plugin.v1.WatchSyncFaultCode - 36, // 9: silo.plugin.v1.WatchSyncFault.retry_after:type_name -> google.protobuf.Duration - 7, // 10: silo.plugin.v1.WatchSyncInitAuthorizeRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 11, // 11: silo.plugin.v1.WatchSyncInitAuthorizeResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 7, // 12: silo.plugin.v1.WatchSyncExchangeCodeRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 7, // 13: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 9, // 14: silo.plugin.v1.WatchSyncRefreshCredentialsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 8, // 15: silo.plugin.v1.WatchSyncCredentialResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 10, // 16: silo.plugin.v1.WatchSyncCredentialResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount - 11, // 17: silo.plugin.v1.WatchSyncCredentialResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 9, // 18: silo.plugin.v1.WatchSyncGetAccountRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 10, // 19: silo.plugin.v1.WatchSyncGetAccountResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount - 11, // 20: silo.plugin.v1.WatchSyncGetAccountResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 1, // 21: silo.plugin.v1.WatchSyncMedia.media_type:type_name -> silo.plugin.v1.WatchSyncMediaType - 33, // 22: silo.plugin.v1.WatchSyncMedia.external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry - 34, // 23: silo.plugin.v1.WatchSyncMedia.series_external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry - 37, // 24: silo.plugin.v1.WatchSyncMedia.metadata:type_name -> google.protobuf.Struct - 2, // 25: silo.plugin.v1.WatchSyncEvent.operation:type_name -> silo.plugin.v1.WatchSyncOperation - 3, // 26: silo.plugin.v1.WatchSyncEvent.origin:type_name -> silo.plugin.v1.WatchSyncOrigin - 35, // 27: silo.plugin.v1.WatchSyncEvent.occurred_at:type_name -> google.protobuf.Timestamp - 20, // 28: silo.plugin.v1.WatchSyncEvent.media:type_name -> silo.plugin.v1.WatchSyncMedia - 9, // 29: silo.plugin.v1.WatchSyncApplyEventsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 21, // 30: silo.plugin.v1.WatchSyncApplyEventsRequest.events:type_name -> silo.plugin.v1.WatchSyncEvent - 4, // 31: silo.plugin.v1.WatchSyncApplyResult.status:type_name -> silo.plugin.v1.WatchSyncApplyStatus - 11, // 32: silo.plugin.v1.WatchSyncApplyResult.fault:type_name -> silo.plugin.v1.WatchSyncFault - 23, // 33: silo.plugin.v1.WatchSyncApplyEventsResponse.results:type_name -> silo.plugin.v1.WatchSyncApplyResult - 8, // 34: silo.plugin.v1.WatchSyncApplyEventsResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 11, // 35: silo.plugin.v1.WatchSyncApplyEventsResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 9, // 36: silo.plugin.v1.WatchSyncListRemoteStateRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 35, // 37: silo.plugin.v1.WatchSyncRemoteWatchedState.last_watched_at:type_name -> google.protobuf.Timestamp - 35, // 38: silo.plugin.v1.WatchSyncRemoteProgressState.paused_at:type_name -> google.protobuf.Timestamp - 20, // 39: silo.plugin.v1.WatchSyncRemoteState.media:type_name -> silo.plugin.v1.WatchSyncMedia - 26, // 40: silo.plugin.v1.WatchSyncRemoteState.watched:type_name -> silo.plugin.v1.WatchSyncRemoteWatchedState - 27, // 41: silo.plugin.v1.WatchSyncRemoteState.progress:type_name -> silo.plugin.v1.WatchSyncRemoteProgressState - 28, // 42: silo.plugin.v1.WatchSyncListRemoteStateResponse.items:type_name -> silo.plugin.v1.WatchSyncRemoteState - 8, // 43: silo.plugin.v1.WatchSyncListRemoteStateResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 11, // 44: silo.plugin.v1.WatchSyncListRemoteStateResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 12, // 45: silo.plugin.v1.WatchSyncProvider.InitAuthorize:input_type -> silo.plugin.v1.WatchSyncInitAuthorizeRequest - 14, // 46: silo.plugin.v1.WatchSyncProvider.ExchangeCode:input_type -> silo.plugin.v1.WatchSyncExchangeCodeRequest - 15, // 47: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:input_type -> silo.plugin.v1.WatchSyncExchangeAPIKeyRequest - 16, // 48: silo.plugin.v1.WatchSyncProvider.RefreshCredentials:input_type -> silo.plugin.v1.WatchSyncRefreshCredentialsRequest - 18, // 49: silo.plugin.v1.WatchSyncProvider.GetAccount:input_type -> silo.plugin.v1.WatchSyncGetAccountRequest - 22, // 50: silo.plugin.v1.WatchSyncProvider.ApplyEvents:input_type -> silo.plugin.v1.WatchSyncApplyEventsRequest - 25, // 51: silo.plugin.v1.WatchSyncProvider.ListRemoteState:input_type -> silo.plugin.v1.WatchSyncListRemoteStateRequest - 13, // 52: silo.plugin.v1.WatchSyncProvider.InitAuthorize:output_type -> silo.plugin.v1.WatchSyncInitAuthorizeResponse - 17, // 53: silo.plugin.v1.WatchSyncProvider.ExchangeCode:output_type -> silo.plugin.v1.WatchSyncCredentialResponse - 17, // 54: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:output_type -> silo.plugin.v1.WatchSyncCredentialResponse - 17, // 55: silo.plugin.v1.WatchSyncProvider.RefreshCredentials:output_type -> silo.plugin.v1.WatchSyncCredentialResponse - 19, // 56: silo.plugin.v1.WatchSyncProvider.GetAccount:output_type -> silo.plugin.v1.WatchSyncGetAccountResponse - 24, // 57: silo.plugin.v1.WatchSyncProvider.ApplyEvents:output_type -> silo.plugin.v1.WatchSyncApplyEventsResponse - 29, // 58: silo.plugin.v1.WatchSyncProvider.ListRemoteState:output_type -> silo.plugin.v1.WatchSyncListRemoteStateResponse - 52, // [52:59] is the sub-list for method output_type - 45, // [45:52] is the sub-list for method input_type - 45, // [45:45] is the sub-list for extension type_name - 45, // [45:45] is the sub-list for extension extendee - 0, // [0:45] is the sub-list for field type_name + 37, // 2: silo.plugin.v1.WatchSyncProviderConfig.values:type_name -> silo.plugin.v1.WatchSyncProviderConfig.ValuesEntry + 38, // 3: silo.plugin.v1.WatchSyncProviderConfig.secret_values:type_name -> silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntry + 42, // 4: silo.plugin.v1.WatchSyncCredentials.expires_at:type_name -> google.protobuf.Timestamp + 39, // 5: silo.plugin.v1.WatchSyncCredentials.secret_attributes:type_name -> silo.plugin.v1.WatchSyncCredentials.SecretAttributesEntry + 9, // 6: silo.plugin.v1.WatchSyncAuthenticatedContext.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 10, // 7: silo.plugin.v1.WatchSyncAuthenticatedContext.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 7, // 8: silo.plugin.v1.WatchSyncFault.code:type_name -> silo.plugin.v1.WatchSyncFaultCode + 43, // 9: silo.plugin.v1.WatchSyncFault.retry_after:type_name -> google.protobuf.Duration + 9, // 10: silo.plugin.v1.WatchSyncInitAuthorizeRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 13, // 11: silo.plugin.v1.WatchSyncInitAuthorizeResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 9, // 12: silo.plugin.v1.WatchSyncExchangeCodeRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 9, // 13: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 9, // 14: silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 43, // 15: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse.polling_interval:type_name -> google.protobuf.Duration + 42, // 16: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse.expires_at:type_name -> google.protobuf.Timestamp + 13, // 17: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 9, // 18: silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 4, // 19: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.status:type_name -> silo.plugin.v1.WatchSyncDeviceAuthorizationStatus + 10, // 20: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 12, // 21: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount + 13, // 22: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 11, // 23: silo.plugin.v1.WatchSyncRefreshCredentialsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 10, // 24: silo.plugin.v1.WatchSyncCredentialResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 12, // 25: silo.plugin.v1.WatchSyncCredentialResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount + 13, // 26: silo.plugin.v1.WatchSyncCredentialResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 11, // 27: silo.plugin.v1.WatchSyncGetAccountRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 12, // 28: silo.plugin.v1.WatchSyncGetAccountResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount + 13, // 29: silo.plugin.v1.WatchSyncGetAccountResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 1, // 30: silo.plugin.v1.WatchSyncMedia.media_type:type_name -> silo.plugin.v1.WatchSyncMediaType + 40, // 31: silo.plugin.v1.WatchSyncMedia.external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry + 41, // 32: silo.plugin.v1.WatchSyncMedia.series_external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry + 44, // 33: silo.plugin.v1.WatchSyncMedia.metadata:type_name -> google.protobuf.Struct + 2, // 34: silo.plugin.v1.WatchSyncEvent.operation:type_name -> silo.plugin.v1.WatchSyncOperation + 5, // 35: silo.plugin.v1.WatchSyncEvent.origin:type_name -> silo.plugin.v1.WatchSyncOrigin + 42, // 36: silo.plugin.v1.WatchSyncEvent.occurred_at:type_name -> google.protobuf.Timestamp + 26, // 37: silo.plugin.v1.WatchSyncEvent.media:type_name -> silo.plugin.v1.WatchSyncMedia + 11, // 38: silo.plugin.v1.WatchSyncApplyEventsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 27, // 39: silo.plugin.v1.WatchSyncApplyEventsRequest.events:type_name -> silo.plugin.v1.WatchSyncEvent + 6, // 40: silo.plugin.v1.WatchSyncApplyResult.status:type_name -> silo.plugin.v1.WatchSyncApplyStatus + 13, // 41: silo.plugin.v1.WatchSyncApplyResult.fault:type_name -> silo.plugin.v1.WatchSyncFault + 29, // 42: silo.plugin.v1.WatchSyncApplyEventsResponse.results:type_name -> silo.plugin.v1.WatchSyncApplyResult + 10, // 43: silo.plugin.v1.WatchSyncApplyEventsResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 13, // 44: silo.plugin.v1.WatchSyncApplyEventsResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 11, // 45: silo.plugin.v1.WatchSyncListRemoteStateRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 3, // 46: silo.plugin.v1.WatchSyncListRemoteStateRequest.state_kinds:type_name -> silo.plugin.v1.WatchSyncRemoteStateKind + 42, // 47: silo.plugin.v1.WatchSyncRemoteWatchedState.last_watched_at:type_name -> google.protobuf.Timestamp + 42, // 48: silo.plugin.v1.WatchSyncRemoteProgressState.paused_at:type_name -> google.protobuf.Timestamp + 42, // 49: silo.plugin.v1.WatchSyncRemoteListState.listed_at:type_name -> google.protobuf.Timestamp + 26, // 50: silo.plugin.v1.WatchSyncRemoteState.media:type_name -> silo.plugin.v1.WatchSyncMedia + 32, // 51: silo.plugin.v1.WatchSyncRemoteState.watched:type_name -> silo.plugin.v1.WatchSyncRemoteWatchedState + 33, // 52: silo.plugin.v1.WatchSyncRemoteState.progress:type_name -> silo.plugin.v1.WatchSyncRemoteProgressState + 34, // 53: silo.plugin.v1.WatchSyncRemoteState.favorite:type_name -> silo.plugin.v1.WatchSyncRemoteListState + 34, // 54: silo.plugin.v1.WatchSyncRemoteState.watchlist:type_name -> silo.plugin.v1.WatchSyncRemoteListState + 35, // 55: silo.plugin.v1.WatchSyncListRemoteStateResponse.items:type_name -> silo.plugin.v1.WatchSyncRemoteState + 10, // 56: silo.plugin.v1.WatchSyncListRemoteStateResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 13, // 57: silo.plugin.v1.WatchSyncListRemoteStateResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 14, // 58: silo.plugin.v1.WatchSyncProvider.InitAuthorize:input_type -> silo.plugin.v1.WatchSyncInitAuthorizeRequest + 16, // 59: silo.plugin.v1.WatchSyncProvider.ExchangeCode:input_type -> silo.plugin.v1.WatchSyncExchangeCodeRequest + 17, // 60: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:input_type -> silo.plugin.v1.WatchSyncExchangeAPIKeyRequest + 18, // 61: silo.plugin.v1.WatchSyncProvider.StartDeviceAuthorization:input_type -> silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest + 20, // 62: silo.plugin.v1.WatchSyncProvider.PollDeviceAuthorization:input_type -> silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest + 22, // 63: silo.plugin.v1.WatchSyncProvider.RefreshCredentials:input_type -> silo.plugin.v1.WatchSyncRefreshCredentialsRequest + 24, // 64: silo.plugin.v1.WatchSyncProvider.GetAccount:input_type -> silo.plugin.v1.WatchSyncGetAccountRequest + 28, // 65: silo.plugin.v1.WatchSyncProvider.ApplyEvents:input_type -> silo.plugin.v1.WatchSyncApplyEventsRequest + 31, // 66: silo.plugin.v1.WatchSyncProvider.ListRemoteState:input_type -> silo.plugin.v1.WatchSyncListRemoteStateRequest + 15, // 67: silo.plugin.v1.WatchSyncProvider.InitAuthorize:output_type -> silo.plugin.v1.WatchSyncInitAuthorizeResponse + 23, // 68: silo.plugin.v1.WatchSyncProvider.ExchangeCode:output_type -> silo.plugin.v1.WatchSyncCredentialResponse + 23, // 69: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:output_type -> silo.plugin.v1.WatchSyncCredentialResponse + 19, // 70: silo.plugin.v1.WatchSyncProvider.StartDeviceAuthorization:output_type -> silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse + 21, // 71: silo.plugin.v1.WatchSyncProvider.PollDeviceAuthorization:output_type -> silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse + 23, // 72: silo.plugin.v1.WatchSyncProvider.RefreshCredentials:output_type -> silo.plugin.v1.WatchSyncCredentialResponse + 25, // 73: silo.plugin.v1.WatchSyncProvider.GetAccount:output_type -> silo.plugin.v1.WatchSyncGetAccountResponse + 30, // 74: silo.plugin.v1.WatchSyncProvider.ApplyEvents:output_type -> silo.plugin.v1.WatchSyncApplyEventsResponse + 36, // 75: silo.plugin.v1.WatchSyncProvider.ListRemoteState:output_type -> silo.plugin.v1.WatchSyncListRemoteStateResponse + 67, // [67:76] is the sub-list for method output_type + 58, // [58:67] is the sub-list for method input_type + 58, // [58:58] is the sub-list for extension type_name + 58, // [58:58] is the sub-list for extension extendee + 0, // [0:58] is the sub-list for field type_name } func init() { file_silo_plugin_v1_watch_sync_provider_proto_init() } @@ -2403,8 +3056,8 @@ func file_silo_plugin_v1_watch_sync_provider_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_silo_plugin_v1_watch_sync_provider_proto_rawDesc), len(file_silo_plugin_v1_watch_sync_provider_proto_rawDesc)), - NumEnums: 6, - NumMessages: 29, + NumEnums: 8, + NumMessages: 34, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go index 4257841..3e8b6a9 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go @@ -19,13 +19,15 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - WatchSyncProvider_InitAuthorize_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/InitAuthorize" - WatchSyncProvider_ExchangeCode_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeCode" - WatchSyncProvider_ExchangeAPIKey_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeAPIKey" - WatchSyncProvider_RefreshCredentials_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/RefreshCredentials" - WatchSyncProvider_GetAccount_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/GetAccount" - WatchSyncProvider_ApplyEvents_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ApplyEvents" - WatchSyncProvider_ListRemoteState_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ListRemoteState" + WatchSyncProvider_InitAuthorize_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/InitAuthorize" + WatchSyncProvider_ExchangeCode_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeCode" + WatchSyncProvider_ExchangeAPIKey_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeAPIKey" + WatchSyncProvider_StartDeviceAuthorization_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/StartDeviceAuthorization" + WatchSyncProvider_PollDeviceAuthorization_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/PollDeviceAuthorization" + WatchSyncProvider_RefreshCredentials_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/RefreshCredentials" + WatchSyncProvider_GetAccount_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/GetAccount" + WatchSyncProvider_ApplyEvents_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ApplyEvents" + WatchSyncProvider_ListRemoteState_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ListRemoteState" ) // WatchSyncProviderClient is the client API for WatchSyncProvider service. @@ -40,6 +42,8 @@ type WatchSyncProviderClient interface { InitAuthorize(ctx context.Context, in *WatchSyncInitAuthorizeRequest, opts ...grpc.CallOption) (*WatchSyncInitAuthorizeResponse, error) ExchangeCode(ctx context.Context, in *WatchSyncExchangeCodeRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) ExchangeAPIKey(ctx context.Context, in *WatchSyncExchangeAPIKeyRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) + StartDeviceAuthorization(ctx context.Context, in *WatchSyncStartDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncStartDeviceAuthorizationResponse, error) + PollDeviceAuthorization(ctx context.Context, in *WatchSyncPollDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncPollDeviceAuthorizationResponse, error) RefreshCredentials(ctx context.Context, in *WatchSyncRefreshCredentialsRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) GetAccount(ctx context.Context, in *WatchSyncGetAccountRequest, opts ...grpc.CallOption) (*WatchSyncGetAccountResponse, error) ApplyEvents(ctx context.Context, in *WatchSyncApplyEventsRequest, opts ...grpc.CallOption) (*WatchSyncApplyEventsResponse, error) @@ -84,6 +88,26 @@ func (c *watchSyncProviderClient) ExchangeAPIKey(ctx context.Context, in *WatchS return out, nil } +func (c *watchSyncProviderClient) StartDeviceAuthorization(ctx context.Context, in *WatchSyncStartDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncStartDeviceAuthorizationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WatchSyncStartDeviceAuthorizationResponse) + err := c.cc.Invoke(ctx, WatchSyncProvider_StartDeviceAuthorization_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *watchSyncProviderClient) PollDeviceAuthorization(ctx context.Context, in *WatchSyncPollDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncPollDeviceAuthorizationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WatchSyncPollDeviceAuthorizationResponse) + err := c.cc.Invoke(ctx, WatchSyncProvider_PollDeviceAuthorization_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *watchSyncProviderClient) RefreshCredentials(ctx context.Context, in *WatchSyncRefreshCredentialsRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(WatchSyncCredentialResponse) @@ -136,6 +160,8 @@ type WatchSyncProviderServer interface { InitAuthorize(context.Context, *WatchSyncInitAuthorizeRequest) (*WatchSyncInitAuthorizeResponse, error) ExchangeCode(context.Context, *WatchSyncExchangeCodeRequest) (*WatchSyncCredentialResponse, error) ExchangeAPIKey(context.Context, *WatchSyncExchangeAPIKeyRequest) (*WatchSyncCredentialResponse, error) + StartDeviceAuthorization(context.Context, *WatchSyncStartDeviceAuthorizationRequest) (*WatchSyncStartDeviceAuthorizationResponse, error) + PollDeviceAuthorization(context.Context, *WatchSyncPollDeviceAuthorizationRequest) (*WatchSyncPollDeviceAuthorizationResponse, error) RefreshCredentials(context.Context, *WatchSyncRefreshCredentialsRequest) (*WatchSyncCredentialResponse, error) GetAccount(context.Context, *WatchSyncGetAccountRequest) (*WatchSyncGetAccountResponse, error) ApplyEvents(context.Context, *WatchSyncApplyEventsRequest) (*WatchSyncApplyEventsResponse, error) @@ -158,6 +184,12 @@ func (UnimplementedWatchSyncProviderServer) ExchangeCode(context.Context, *Watch func (UnimplementedWatchSyncProviderServer) ExchangeAPIKey(context.Context, *WatchSyncExchangeAPIKeyRequest) (*WatchSyncCredentialResponse, error) { return nil, status.Error(codes.Unimplemented, "method ExchangeAPIKey not implemented") } +func (UnimplementedWatchSyncProviderServer) StartDeviceAuthorization(context.Context, *WatchSyncStartDeviceAuthorizationRequest) (*WatchSyncStartDeviceAuthorizationResponse, error) { + return nil, status.Error(codes.Unimplemented, "method StartDeviceAuthorization not implemented") +} +func (UnimplementedWatchSyncProviderServer) PollDeviceAuthorization(context.Context, *WatchSyncPollDeviceAuthorizationRequest) (*WatchSyncPollDeviceAuthorizationResponse, error) { + return nil, status.Error(codes.Unimplemented, "method PollDeviceAuthorization not implemented") +} func (UnimplementedWatchSyncProviderServer) RefreshCredentials(context.Context, *WatchSyncRefreshCredentialsRequest) (*WatchSyncCredentialResponse, error) { return nil, status.Error(codes.Unimplemented, "method RefreshCredentials not implemented") } @@ -244,6 +276,42 @@ func _WatchSyncProvider_ExchangeAPIKey_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _WatchSyncProvider_StartDeviceAuthorization_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WatchSyncStartDeviceAuthorizationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WatchSyncProviderServer).StartDeviceAuthorization(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WatchSyncProvider_StartDeviceAuthorization_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WatchSyncProviderServer).StartDeviceAuthorization(ctx, req.(*WatchSyncStartDeviceAuthorizationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WatchSyncProvider_PollDeviceAuthorization_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WatchSyncPollDeviceAuthorizationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WatchSyncProviderServer).PollDeviceAuthorization(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WatchSyncProvider_PollDeviceAuthorization_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WatchSyncProviderServer).PollDeviceAuthorization(ctx, req.(*WatchSyncPollDeviceAuthorizationRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WatchSyncProvider_RefreshCredentials_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(WatchSyncRefreshCredentialsRequest) if err := dec(in); err != nil { @@ -335,6 +403,14 @@ var WatchSyncProvider_ServiceDesc = grpc.ServiceDesc{ MethodName: "ExchangeAPIKey", Handler: _WatchSyncProvider_ExchangeAPIKey_Handler, }, + { + MethodName: "StartDeviceAuthorization", + Handler: _WatchSyncProvider_StartDeviceAuthorization_Handler, + }, + { + MethodName: "PollDeviceAuthorization", + Handler: _WatchSyncProvider_PollDeviceAuthorization_Handler, + }, { MethodName: "RefreshCredentials", Handler: _WatchSyncProvider_RefreshCredentials_Handler, diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go index dff73a3..663cd7c 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go @@ -26,6 +26,7 @@ func TestWatchSyncTypedRemoteStateRoundTrip(t *testing.T) { ProgressPercent: 42.5, PausedAt: timestamppb.New(pausedAt), }, + Watchlist: &WatchSyncRemoteListState{ListedAt: timestamppb.New(pausedAt.Add(-2 * time.Hour))}, }}, NextCursor: "checkpoint-2", CompleteSnapshot: true, @@ -44,11 +45,37 @@ func TestWatchSyncTypedRemoteStateRoundTrip(t *testing.T) { if item.GetMedia().GetMediaType() != WatchSyncMediaType_WATCH_SYNC_MEDIA_TYPE_EPISODE || item.GetWatched().GetPlayCount() != 2 || item.GetProgress().GetProgressPercent() != 42.5 || - !item.GetProgress().GetPausedAt().AsTime().Equal(pausedAt) { + !item.GetProgress().GetPausedAt().AsTime().Equal(pausedAt) || + !item.GetWatchlist().GetListedAt().AsTime().Equal(pausedAt.Add(-2*time.Hour)) { t.Fatalf("remote state = %#v", item) } } +func TestWatchSyncDeviceAuthorizationRoundTrip(t *testing.T) { + expiresAt := time.Unix(1_800_000_000, 0).UTC() + input := &WatchSyncStartDeviceAuthorizationResponse{ + UserCode: "ABCD-1234", + VerificationUrl: "https://provider.example/activate", + VerificationUrlComplete: "https://provider.example/activate?code=ABCD-1234", + ProviderState: []byte(`{"device_code":"secret"}`), + PollingInterval: durationpb.New(5 * time.Second), + ExpiresAt: timestamppb.New(expiresAt), + } + data, err := proto.Marshal(input) + if err != nil { + t.Fatal(err) + } + var output WatchSyncStartDeviceAuthorizationResponse + if err := proto.Unmarshal(data, &output); err != nil { + t.Fatal(err) + } + if output.GetUserCode() != input.GetUserCode() || + output.GetPollingInterval().AsDuration() != 5*time.Second || + !output.GetExpiresAt().AsTime().Equal(expiresAt) { + t.Fatalf("device authorization = %#v", &output) + } +} + func TestWatchSyncApplyResultCarriesTypedRateLimit(t *testing.T) { retryAfter := 45 * time.Second request := &WatchSyncApplyEventsRequest{ diff --git a/pkg/pluginsdk/convert/watch_sync_provider_test.go b/pkg/pluginsdk/convert/watch_sync_provider_test.go index e554dc4..ca2b54e 100644 --- a/pkg/pluginsdk/convert/watch_sync_provider_test.go +++ b/pkg/pluginsdk/convert/watch_sync_provider_test.go @@ -12,8 +12,10 @@ func TestWatchSyncProviderDescriptorRoundTrip(t *testing.T) { Type: "watch_sync_provider.v1", Id: "anilist", WatchSyncProvider: &pluginv1.WatchSyncProviderDescriptor{ - AuthMethods: []pluginv1.WatchSyncAuthMethod{pluginv1.WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE}, - ExportWatched: true, + AuthMethods: []pluginv1.WatchSyncAuthMethod{pluginv1.WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_DEVICE_CODE}, + ExportWatched: true, + ImportWatchlist: true, + ScrobblePlayback: true, SupportedMediaTypes: []pluginv1.WatchSyncMediaType{ pluginv1.WatchSyncMediaType_WATCH_SYNC_MEDIA_TYPE_MOVIE, pluginv1.WatchSyncMediaType_WATCH_SYNC_MEDIA_TYPE_EPISODE, @@ -30,7 +32,7 @@ func TestWatchSyncProviderDescriptorRoundTrip(t *testing.T) { t.Fatal(err) } got := decoded.GetWatchSyncProvider() - if got == nil || !got.GetExportWatched() || got.GetMaxBatchSize() != 25 || + if got == nil || !got.GetExportWatched() || !got.GetImportWatchlist() || !got.GetScrobblePlayback() || got.GetMaxBatchSize() != 25 || len(got.GetAuthMethods()) != 1 || len(got.GetSupportedMediaTypes()) != 2 || got.GetSupportedMediaTypes()[1] != pluginv1.WatchSyncMediaType_WATCH_SYNC_MEDIA_TYPE_EPISODE { t.Fatalf("decoded descriptor = %#v", got) diff --git a/pkg/pluginsdk/manifest/manifest.go b/pkg/pluginsdk/manifest/manifest.go index a5bd670..f6de2c0 100644 --- a/pkg/pluginsdk/manifest/manifest.go +++ b/pkg/pluginsdk/manifest/manifest.go @@ -156,9 +156,17 @@ func validateWatchSyncCapability(descriptor *pluginv1.CapabilityDescriptor) erro return fmt.Errorf("plugin capability %q: watch sync auth method cannot be unspecified", descriptor.GetId()) } } - if !watchSync.GetExportWatched() && !watchSync.GetExportUnwatched() && !watchSync.GetImportWatched() && !watchSync.GetImportProgress() { + if !watchSync.GetExportWatched() && !watchSync.GetExportUnwatched() && + !watchSync.GetImportWatched() && !watchSync.GetImportProgress() && + !watchSync.GetImportFavorites() && !watchSync.GetExportFavorites() && + !watchSync.GetRemoveFavorites() && !watchSync.GetImportWatchlist() && + !watchSync.GetExportWatchlist() && !watchSync.GetRemoveWatchlist() && + !watchSync.GetScrobblePlayback() { return fmt.Errorf("plugin capability %q: at least one watch sync operation is required", descriptor.GetId()) } + if watchSync.GetProvidesWatchlistOrder() && !watchSync.GetImportWatchlist() { + return fmt.Errorf("plugin capability %q: watchlist order requires watchlist import", descriptor.GetId()) + } if watchSync.GetMaxBatchSize() < 1 || watchSync.GetMaxBatchSize() > 100 { return fmt.Errorf("plugin capability %q: watch sync max_batch_size must be between 1 and 100", descriptor.GetId()) } diff --git a/pkg/pluginsdk/manifest/watch_sync_provider_test.go b/pkg/pluginsdk/manifest/watch_sync_provider_test.go index 97f93b7..115b992 100644 --- a/pkg/pluginsdk/manifest/watch_sync_provider_test.go +++ b/pkg/pluginsdk/manifest/watch_sync_provider_test.go @@ -87,6 +87,28 @@ func TestValidateWatchSyncProviderAllowsUnknownFutureEnums(t *testing.T) { } } +func TestValidateWatchSyncProviderAllowsDeviceCodeAndListOnly(t *testing.T) { + manifest := validWatchSyncManifest() + descriptor := manifest.Capabilities[0].WatchSyncProvider + descriptor.AuthMethods = []pluginv1.WatchSyncAuthMethod{ + pluginv1.WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_DEVICE_CODE, + } + descriptor.ExportWatched = false + descriptor.ImportWatchlist = true + descriptor.ProvidesWatchlistOrder = true + if err := publicmanifest.Validate(manifest); err != nil { + t.Fatalf("device-code list provider should be valid: %v", err) + } +} + +func TestValidateWatchSyncProviderRejectsOrderWithoutImport(t *testing.T) { + manifest := validWatchSyncManifest() + manifest.Capabilities[0].WatchSyncProvider.ProvidesWatchlistOrder = true + if err := publicmanifest.Validate(manifest); err == nil { + t.Fatal("expected watchlist order without import to fail") + } +} + func validWatchSyncManifest() *pluginv1.PluginManifest { return &pluginv1.PluginManifest{ PluginId: "silo.valid", Version: "1.0.0", diff --git a/proto/silo/plugin/v1/watch_sync_provider.proto b/proto/silo/plugin/v1/watch_sync_provider.proto index eb959d2..ead759c 100644 --- a/proto/silo/plugin/v1/watch_sync_provider.proto +++ b/proto/silo/plugin/v1/watch_sync_provider.proto @@ -16,6 +16,8 @@ service WatchSyncProvider { rpc InitAuthorize(WatchSyncInitAuthorizeRequest) returns (WatchSyncInitAuthorizeResponse); rpc ExchangeCode(WatchSyncExchangeCodeRequest) returns (WatchSyncCredentialResponse); rpc ExchangeAPIKey(WatchSyncExchangeAPIKeyRequest) returns (WatchSyncCredentialResponse); + rpc StartDeviceAuthorization(WatchSyncStartDeviceAuthorizationRequest) returns (WatchSyncStartDeviceAuthorizationResponse); + rpc PollDeviceAuthorization(WatchSyncPollDeviceAuthorizationRequest) returns (WatchSyncPollDeviceAuthorizationResponse); rpc RefreshCredentials(WatchSyncRefreshCredentialsRequest) returns (WatchSyncCredentialResponse); rpc GetAccount(WatchSyncGetAccountRequest) returns (WatchSyncGetAccountResponse); rpc ApplyEvents(WatchSyncApplyEventsRequest) returns (WatchSyncApplyEventsResponse); @@ -26,6 +28,7 @@ enum WatchSyncAuthMethod { WATCH_SYNC_AUTH_METHOD_UNSPECIFIED = 0; WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE = 1; WATCH_SYNC_AUTH_METHOD_API_KEY = 2; + WATCH_SYNC_AUTH_METHOD_DEVICE_CODE = 3; } enum WatchSyncMediaType { @@ -38,6 +41,29 @@ enum WatchSyncOperation { WATCH_SYNC_OPERATION_UNSPECIFIED = 0; WATCH_SYNC_OPERATION_MARK_WATCHED = 1; WATCH_SYNC_OPERATION_MARK_UNWATCHED = 2; + WATCH_SYNC_OPERATION_ADD_FAVORITE = 3; + WATCH_SYNC_OPERATION_REMOVE_FAVORITE = 4; + WATCH_SYNC_OPERATION_ADD_TO_WATCHLIST = 5; + WATCH_SYNC_OPERATION_REMOVE_FROM_WATCHLIST = 6; + WATCH_SYNC_OPERATION_SCROBBLE_START = 7; + WATCH_SYNC_OPERATION_SCROBBLE_PAUSE = 8; + WATCH_SYNC_OPERATION_SCROBBLE_STOP = 9; +} + +enum WatchSyncRemoteStateKind { + WATCH_SYNC_REMOTE_STATE_KIND_UNSPECIFIED = 0; + WATCH_SYNC_REMOTE_STATE_KIND_WATCHED = 1; + WATCH_SYNC_REMOTE_STATE_KIND_PROGRESS = 2; + WATCH_SYNC_REMOTE_STATE_KIND_FAVORITE = 3; + WATCH_SYNC_REMOTE_STATE_KIND_WATCHLIST = 4; +} + +enum WatchSyncDeviceAuthorizationStatus { + WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED = 0; + WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_PENDING = 1; + WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_AUTHORIZED = 2; + WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_DENIED = 3; + WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_EXPIRED = 4; } enum WatchSyncOrigin { @@ -74,6 +100,15 @@ message WatchSyncProviderDescriptor { repeated WatchSyncMediaType supported_media_types = 6; repeated string external_id_namespaces = 7; int32 max_batch_size = 8; + bool import_favorites = 9; + bool export_favorites = 10; + bool remove_favorites = 11; + bool import_watchlist = 12; + bool export_watchlist = 13; + bool remove_watchlist = 14; + // The order of WATCHLIST states returned by ListRemoteState is meaningful. + bool provides_watchlist_order = 15; + bool scrobble_playback = 16; } message WatchSyncProviderConfig { @@ -157,6 +192,40 @@ message WatchSyncExchangeAPIKeyRequest { string api_key = 3; } +message WatchSyncStartDeviceAuthorizationRequest { + string capability_id = 1; + WatchSyncProviderConfig provider_config = 2; +} + +message WatchSyncStartDeviceAuthorizationResponse { + string user_code = 1; + string verification_url = 2; + string verification_url_complete = 3; + // Plugin-defined flow data. The host encrypts it until authorization + // completes and never returns it through the public HTTP API. + bytes provider_state = 4; + google.protobuf.Duration polling_interval = 5; + google.protobuf.Timestamp expires_at = 6; + WatchSyncFault fault = 7; +} + +message WatchSyncPollDeviceAuthorizationRequest { + string capability_id = 1; + WatchSyncProviderConfig provider_config = 2; + bytes provider_state = 3; +} + +message WatchSyncPollDeviceAuthorizationResponse { + WatchSyncDeviceAuthorizationStatus status = 1; + // Complete authoritative replacement when status is AUTHORIZED. The host + // persists it before consuming account or fault. + WatchSyncCredentials credentials = 2; + WatchSyncAccount account = 3; + // PENDING normally omits fault. DENIED and EXPIRED may use a safe, + // non-retryable fault. Connection-wide and transport failures use fault. + WatchSyncFault fault = 4; +} + message WatchSyncRefreshCredentialsRequest { WatchSyncAuthenticatedContext context = 1; } @@ -211,6 +280,13 @@ message WatchSyncEvent { double duration_seconds = 9; double completion_percent = 10; int32 play_count = 11; + // Optional zero-based position for list providers that accept explicit + // ordering. Providers may ignore it unless their API supports order writes. + int32 list_position = 12; + // Provider-native key previously returned by ListRemoteState. It lets a + // removal address the exact upstream record when external IDs are absent or + // no longer resolve. + string provider_item_key = 13; } message WatchSyncApplyEventsRequest { @@ -252,6 +328,9 @@ message WatchSyncListRemoteStateRequest { string page_token = 3; // Requested maximum number of items. Plugins may return fewer. int32 page_size = 4; + // State families requested by the host. Empty means every family advertised + // by the descriptor, preserving compatibility with v0.12 hosts and plugins. + repeated WatchSyncRemoteStateKind state_kinds = 5; } message WatchSyncRemoteWatchedState { @@ -269,6 +348,11 @@ message WatchSyncRemoteProgressState { google.protobuf.Timestamp paused_at = 2; } +message WatchSyncRemoteListState { + // Provider timestamp for when the item was added, when available. + google.protobuf.Timestamp listed_at = 1; +} + message WatchSyncRemoteState { string provider_item_key = 1; WatchSyncMedia media = 2; @@ -276,6 +360,8 @@ message WatchSyncRemoteState { // provider reports a completed play and a separate resume point. WatchSyncRemoteWatchedState watched = 3; WatchSyncRemoteProgressState progress = 4; + WatchSyncRemoteListState favorite = 5; + WatchSyncRemoteListState watchlist = 6; } message WatchSyncListRemoteStateResponse { From 8aa005a7c195a1330a1e8c4530fe2c2b8e6c7ad0 Mon Sep 17 00:00:00 2001 From: Quick104 <31828688+Quick104@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:39:51 -0400 Subject: [PATCH 2/5] fix(watchsync): preserve provider compatibility --- README.md | 14 +- .../silo/plugin/v1/watch_sync_provider.pb.go | 405 ++++++++++-------- .../v1/watch_sync_provider_compat_test.go | 39 ++ .../plugin/v1/watch_sync_provider_grpc.pb.go | 236 ++++++---- .../plugin/v1/watch_sync_provider_test.go | 53 ++- .../convert/watch_sync_provider_test.go | 3 +- pkg/pluginsdk/runtime/runtime.go | 10 + .../runtime/watch_sync_provider_test.go | 16 +- .../silo/plugin/v1/watch_sync_provider.proto | 31 +- 9 files changed, 528 insertions(+), 279 deletions(-) create mode 100644 pkg/pluginproto/silo/plugin/v1/watch_sync_provider_compat_test.go diff --git a/README.md b/README.md index ac81154..37d16ba 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,13 @@ patches. The host validates and persists them before consuming results, pages, or faults—even when the response contains a fault. If credential persistence fails, the host commits no other response data. +Device-code plugins register both `WatchSyncProvider` and the separate +`WatchSyncDeviceAuthorizationService`. Keeping device authorization in a +second service preserves source compatibility for v0.12 Go providers that +implemented `WatchSyncProviderServer` directly. A pending poll may replace its +opaque provider state, polling interval, and expiry; the host encrypts and +persists those values before the next poll. + `WatchSyncProviderConfig` is keyed by manifest config key and field, for example `provider.client_id`. Scalar values are sent as strings and structured values as JSON. Fields marked secret in the manifest are sent through `secret_values`; @@ -165,9 +172,12 @@ contain multiple state families. The host requests only the state families a sync phase needs, keeps that phase's `cursor` fixed while following ephemeral page tokens, commits each successful page, and only then persists the final `next_cursor`. `complete_snapshot=true` means the traversal is authoritative; -when false, missing items are not deletions. When +when false, missing items are not deletions. An incremental favorite or +watchlist removal is an item whose corresponding list state has `removed=true`. +When `provides_watchlist_order=true`, the order of returned watchlist states is the -remote list order. +remote list order. Event `list_position` is presence-aware: an explicit zero +means the first position, while omission means no requested ordering. ## Scan sources diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go index 2f928f0..b18efdf 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go @@ -1254,7 +1254,7 @@ func (x *WatchSyncExchangeAPIKeyRequest) GetApiKey() string { return "" } -type WatchSyncStartDeviceAuthorizationRequest struct { +type WatchSyncDeviceAuthorizationServiceStartRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CapabilityId string `protobuf:"bytes,1,opt,name=capability_id,json=capabilityId,proto3" json:"capability_id,omitempty"` ProviderConfig *WatchSyncProviderConfig `protobuf:"bytes,2,opt,name=provider_config,json=providerConfig,proto3" json:"provider_config,omitempty"` @@ -1262,20 +1262,20 @@ type WatchSyncStartDeviceAuthorizationRequest struct { sizeCache protoimpl.SizeCache } -func (x *WatchSyncStartDeviceAuthorizationRequest) Reset() { - *x = WatchSyncStartDeviceAuthorizationRequest{} +func (x *WatchSyncDeviceAuthorizationServiceStartRequest) Reset() { + *x = WatchSyncDeviceAuthorizationServiceStartRequest{} mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *WatchSyncStartDeviceAuthorizationRequest) String() string { +func (x *WatchSyncDeviceAuthorizationServiceStartRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WatchSyncStartDeviceAuthorizationRequest) ProtoMessage() {} +func (*WatchSyncDeviceAuthorizationServiceStartRequest) ProtoMessage() {} -func (x *WatchSyncStartDeviceAuthorizationRequest) ProtoReflect() protoreflect.Message { +func (x *WatchSyncDeviceAuthorizationServiceStartRequest) ProtoReflect() protoreflect.Message { mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1287,26 +1287,26 @@ func (x *WatchSyncStartDeviceAuthorizationRequest) ProtoReflect() protoreflect.M return mi.MessageOf(x) } -// Deprecated: Use WatchSyncStartDeviceAuthorizationRequest.ProtoReflect.Descriptor instead. -func (*WatchSyncStartDeviceAuthorizationRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use WatchSyncDeviceAuthorizationServiceStartRequest.ProtoReflect.Descriptor instead. +func (*WatchSyncDeviceAuthorizationServiceStartRequest) Descriptor() ([]byte, []int) { return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{10} } -func (x *WatchSyncStartDeviceAuthorizationRequest) GetCapabilityId() string { +func (x *WatchSyncDeviceAuthorizationServiceStartRequest) GetCapabilityId() string { if x != nil { return x.CapabilityId } return "" } -func (x *WatchSyncStartDeviceAuthorizationRequest) GetProviderConfig() *WatchSyncProviderConfig { +func (x *WatchSyncDeviceAuthorizationServiceStartRequest) GetProviderConfig() *WatchSyncProviderConfig { if x != nil { return x.ProviderConfig } return nil } -type WatchSyncStartDeviceAuthorizationResponse struct { +type WatchSyncDeviceAuthorizationServiceStartResponse struct { state protoimpl.MessageState `protogen:"open.v1"` UserCode string `protobuf:"bytes,1,opt,name=user_code,json=userCode,proto3" json:"user_code,omitempty"` VerificationUrl string `protobuf:"bytes,2,opt,name=verification_url,json=verificationUrl,proto3" json:"verification_url,omitempty"` @@ -1321,20 +1321,20 @@ type WatchSyncStartDeviceAuthorizationResponse struct { sizeCache protoimpl.SizeCache } -func (x *WatchSyncStartDeviceAuthorizationResponse) Reset() { - *x = WatchSyncStartDeviceAuthorizationResponse{} +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) Reset() { + *x = WatchSyncDeviceAuthorizationServiceStartResponse{} mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *WatchSyncStartDeviceAuthorizationResponse) String() string { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WatchSyncStartDeviceAuthorizationResponse) ProtoMessage() {} +func (*WatchSyncDeviceAuthorizationServiceStartResponse) ProtoMessage() {} -func (x *WatchSyncStartDeviceAuthorizationResponse) ProtoReflect() protoreflect.Message { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) ProtoReflect() protoreflect.Message { mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1346,61 +1346,61 @@ func (x *WatchSyncStartDeviceAuthorizationResponse) ProtoReflect() protoreflect. return mi.MessageOf(x) } -// Deprecated: Use WatchSyncStartDeviceAuthorizationResponse.ProtoReflect.Descriptor instead. -func (*WatchSyncStartDeviceAuthorizationResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use WatchSyncDeviceAuthorizationServiceStartResponse.ProtoReflect.Descriptor instead. +func (*WatchSyncDeviceAuthorizationServiceStartResponse) Descriptor() ([]byte, []int) { return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{11} } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetUserCode() string { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetUserCode() string { if x != nil { return x.UserCode } return "" } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetVerificationUrl() string { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetVerificationUrl() string { if x != nil { return x.VerificationUrl } return "" } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetVerificationUrlComplete() string { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetVerificationUrlComplete() string { if x != nil { return x.VerificationUrlComplete } return "" } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetProviderState() []byte { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetProviderState() []byte { if x != nil { return x.ProviderState } return nil } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetPollingInterval() *durationpb.Duration { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetPollingInterval() *durationpb.Duration { if x != nil { return x.PollingInterval } return nil } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetExpiresAt() *timestamppb.Timestamp { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } return nil } -func (x *WatchSyncStartDeviceAuthorizationResponse) GetFault() *WatchSyncFault { +func (x *WatchSyncDeviceAuthorizationServiceStartResponse) GetFault() *WatchSyncFault { if x != nil { return x.Fault } return nil } -type WatchSyncPollDeviceAuthorizationRequest struct { +type WatchSyncDeviceAuthorizationServicePollRequest struct { state protoimpl.MessageState `protogen:"open.v1"` CapabilityId string `protobuf:"bytes,1,opt,name=capability_id,json=capabilityId,proto3" json:"capability_id,omitempty"` ProviderConfig *WatchSyncProviderConfig `protobuf:"bytes,2,opt,name=provider_config,json=providerConfig,proto3" json:"provider_config,omitempty"` @@ -1409,20 +1409,20 @@ type WatchSyncPollDeviceAuthorizationRequest struct { sizeCache protoimpl.SizeCache } -func (x *WatchSyncPollDeviceAuthorizationRequest) Reset() { - *x = WatchSyncPollDeviceAuthorizationRequest{} +func (x *WatchSyncDeviceAuthorizationServicePollRequest) Reset() { + *x = WatchSyncDeviceAuthorizationServicePollRequest{} mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *WatchSyncPollDeviceAuthorizationRequest) String() string { +func (x *WatchSyncDeviceAuthorizationServicePollRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WatchSyncPollDeviceAuthorizationRequest) ProtoMessage() {} +func (*WatchSyncDeviceAuthorizationServicePollRequest) ProtoMessage() {} -func (x *WatchSyncPollDeviceAuthorizationRequest) ProtoReflect() protoreflect.Message { +func (x *WatchSyncDeviceAuthorizationServicePollRequest) ProtoReflect() protoreflect.Message { mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1434,33 +1434,33 @@ func (x *WatchSyncPollDeviceAuthorizationRequest) ProtoReflect() protoreflect.Me return mi.MessageOf(x) } -// Deprecated: Use WatchSyncPollDeviceAuthorizationRequest.ProtoReflect.Descriptor instead. -func (*WatchSyncPollDeviceAuthorizationRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use WatchSyncDeviceAuthorizationServicePollRequest.ProtoReflect.Descriptor instead. +func (*WatchSyncDeviceAuthorizationServicePollRequest) Descriptor() ([]byte, []int) { return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{12} } -func (x *WatchSyncPollDeviceAuthorizationRequest) GetCapabilityId() string { +func (x *WatchSyncDeviceAuthorizationServicePollRequest) GetCapabilityId() string { if x != nil { return x.CapabilityId } return "" } -func (x *WatchSyncPollDeviceAuthorizationRequest) GetProviderConfig() *WatchSyncProviderConfig { +func (x *WatchSyncDeviceAuthorizationServicePollRequest) GetProviderConfig() *WatchSyncProviderConfig { if x != nil { return x.ProviderConfig } return nil } -func (x *WatchSyncPollDeviceAuthorizationRequest) GetProviderState() []byte { +func (x *WatchSyncDeviceAuthorizationServicePollRequest) GetProviderState() []byte { if x != nil { return x.ProviderState } return nil } -type WatchSyncPollDeviceAuthorizationResponse struct { +type WatchSyncDeviceAuthorizationServicePollResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Status WatchSyncDeviceAuthorizationStatus `protobuf:"varint,1,opt,name=status,proto3,enum=silo.plugin.v1.WatchSyncDeviceAuthorizationStatus" json:"status,omitempty"` // Complete authoritative replacement when status is AUTHORIZED. The host @@ -1469,25 +1469,33 @@ type WatchSyncPollDeviceAuthorizationResponse struct { Account *WatchSyncAccount `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` // PENDING normally omits fault. DENIED and EXPIRED may use a safe, // non-retryable fault. Connection-wide and transport failures use fault. - Fault *WatchSyncFault `protobuf:"bytes,4,opt,name=fault,proto3" json:"fault,omitempty"` + Fault *WatchSyncFault `protobuf:"bytes,4,opt,name=fault,proto3" json:"fault,omitempty"` + // Optional authoritative replacement for provider_state while PENDING. The + // host encrypts and uses it for the next poll. + ProviderState []byte `protobuf:"bytes,5,opt,name=provider_state,json=providerState,proto3" json:"provider_state,omitempty"` + // Optional replacement polling interval while PENDING, for example after an + // OAuth device-flow slow_down response. + PollingInterval *durationpb.Duration `protobuf:"bytes,6,opt,name=polling_interval,json=pollingInterval,proto3" json:"polling_interval,omitempty"` + // Optional replacement expiry for a rotated provider challenge. + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *WatchSyncPollDeviceAuthorizationResponse) Reset() { - *x = WatchSyncPollDeviceAuthorizationResponse{} +func (x *WatchSyncDeviceAuthorizationServicePollResponse) Reset() { + *x = WatchSyncDeviceAuthorizationServicePollResponse{} mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *WatchSyncPollDeviceAuthorizationResponse) String() string { +func (x *WatchSyncDeviceAuthorizationServicePollResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WatchSyncPollDeviceAuthorizationResponse) ProtoMessage() {} +func (*WatchSyncDeviceAuthorizationServicePollResponse) ProtoMessage() {} -func (x *WatchSyncPollDeviceAuthorizationResponse) ProtoReflect() protoreflect.Message { +func (x *WatchSyncDeviceAuthorizationServicePollResponse) ProtoReflect() protoreflect.Message { mi := &file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1499,39 +1507,60 @@ func (x *WatchSyncPollDeviceAuthorizationResponse) ProtoReflect() protoreflect.M return mi.MessageOf(x) } -// Deprecated: Use WatchSyncPollDeviceAuthorizationResponse.ProtoReflect.Descriptor instead. -func (*WatchSyncPollDeviceAuthorizationResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use WatchSyncDeviceAuthorizationServicePollResponse.ProtoReflect.Descriptor instead. +func (*WatchSyncDeviceAuthorizationServicePollResponse) Descriptor() ([]byte, []int) { return file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP(), []int{13} } -func (x *WatchSyncPollDeviceAuthorizationResponse) GetStatus() WatchSyncDeviceAuthorizationStatus { +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetStatus() WatchSyncDeviceAuthorizationStatus { if x != nil { return x.Status } return WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_UNSPECIFIED } -func (x *WatchSyncPollDeviceAuthorizationResponse) GetCredentials() *WatchSyncCredentials { +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetCredentials() *WatchSyncCredentials { if x != nil { return x.Credentials } return nil } -func (x *WatchSyncPollDeviceAuthorizationResponse) GetAccount() *WatchSyncAccount { +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetAccount() *WatchSyncAccount { if x != nil { return x.Account } return nil } -func (x *WatchSyncPollDeviceAuthorizationResponse) GetFault() *WatchSyncFault { +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetFault() *WatchSyncFault { if x != nil { return x.Fault } return nil } +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetProviderState() []byte { + if x != nil { + return x.ProviderState + } + return nil +} + +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetPollingInterval() *durationpb.Duration { + if x != nil { + return x.PollingInterval + } + return nil +} + +func (x *WatchSyncDeviceAuthorizationServicePollResponse) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + type WatchSyncRefreshCredentialsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Context *WatchSyncAuthenticatedContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` @@ -1908,7 +1937,7 @@ type WatchSyncEvent struct { PlayCount int32 `protobuf:"varint,11,opt,name=play_count,json=playCount,proto3" json:"play_count,omitempty"` // Optional zero-based position for list providers that accept explicit // ordering. Providers may ignore it unless their API supports order writes. - ListPosition int32 `protobuf:"varint,12,opt,name=list_position,json=listPosition,proto3" json:"list_position,omitempty"` + ListPosition *int32 `protobuf:"varint,12,opt,name=list_position,json=listPosition,proto3,oneof" json:"list_position,omitempty"` // Provider-native key previously returned by ListRemoteState. It lets a // removal address the exact upstream record when external IDs are absent or // no longer resolve. @@ -2025,8 +2054,8 @@ func (x *WatchSyncEvent) GetPlayCount() int32 { } func (x *WatchSyncEvent) GetListPosition() int32 { - if x != nil { - return x.ListPosition + if x != nil && x.ListPosition != nil { + return *x.ListPosition } return 0 } @@ -2417,7 +2446,10 @@ func (x *WatchSyncRemoteProgressState) GetPausedAt() *timestamppb.Timestamp { type WatchSyncRemoteListState struct { state protoimpl.MessageState `protogen:"open.v1"` // Provider timestamp for when the item was added, when available. - ListedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=listed_at,json=listedAt,proto3" json:"listed_at,omitempty"` + ListedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=listed_at,json=listedAt,proto3" json:"listed_at,omitempty"` + // True is an explicit tombstone. It is meaningful for incremental + // traversals, where omitting an item is not a deletion. + Removed bool `protobuf:"varint,2,opt,name=removed,proto3" json:"removed,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2459,6 +2491,13 @@ func (x *WatchSyncRemoteListState) GetListedAt() *timestamppb.Timestamp { return nil } +func (x *WatchSyncRemoteListState) GetRemoved() bool { + if x != nil { + return x.Removed + } + return false +} + type WatchSyncRemoteState struct { state protoimpl.MessageState `protogen:"open.v1"` ProviderItemKey string `protobuf:"bytes,1,opt,name=provider_item_key,json=providerItemKey,proto3" json:"provider_item_key,omitempty"` @@ -2720,11 +2759,11 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x1eWatchSyncExchangeAPIKeyRequest\x12#\n" + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\x12\x17\n" + - "\aapi_key\x18\x03 \x01(\tR\x06apiKey\"\xa1\x01\n" + - "(WatchSyncStartDeviceAuthorizationRequest\x12#\n" + + "\aapi_key\x18\x03 \x01(\tR\x06apiKey\"\xa8\x01\n" + + "/WatchSyncDeviceAuthorizationServiceStartRequest\x12#\n" + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + - "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\"\x8d\x03\n" + - ")WatchSyncStartDeviceAuthorizationResponse\x12\x1b\n" + + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\"\x94\x03\n" + + "0WatchSyncDeviceAuthorizationServiceStartResponse\x12\x1b\n" + "\tuser_code\x18\x01 \x01(\tR\buserCode\x12)\n" + "\x10verification_url\x18\x02 \x01(\tR\x0fverificationUrl\x12:\n" + "\x19verification_url_complete\x18\x03 \x01(\tR\x17verificationUrlComplete\x12%\n" + @@ -2732,16 +2771,20 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x10polling_interval\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\x0fpollingInterval\x129\n" + "\n" + "expires_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x124\n" + - "\x05fault\x18\a \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"\xc7\x01\n" + - "'WatchSyncPollDeviceAuthorizationRequest\x12#\n" + + "\x05fault\x18\a \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"\xce\x01\n" + + ".WatchSyncDeviceAuthorizationServicePollRequest\x12#\n" + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\x12%\n" + - "\x0eprovider_state\x18\x03 \x01(\fR\rproviderState\"\xb0\x02\n" + - "(WatchSyncPollDeviceAuthorizationResponse\x12J\n" + + "\x0eprovider_state\x18\x03 \x01(\fR\rproviderState\"\xdf\x03\n" + + "/WatchSyncDeviceAuthorizationServicePollResponse\x12J\n" + "\x06status\x18\x01 \x01(\x0e22.silo.plugin.v1.WatchSyncDeviceAuthorizationStatusR\x06status\x12F\n" + "\vcredentials\x18\x02 \x01(\v2$.silo.plugin.v1.WatchSyncCredentialsR\vcredentials\x12:\n" + "\aaccount\x18\x03 \x01(\v2 .silo.plugin.v1.WatchSyncAccountR\aaccount\x124\n" + - "\x05fault\x18\x04 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\"m\n" + + "\x05fault\x18\x04 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\x12%\n" + + "\x0eprovider_state\x18\x05 \x01(\fR\rproviderState\x12D\n" + + "\x10polling_interval\x18\x06 \x01(\v2\x19.google.protobuf.DurationR\x0fpollingInterval\x129\n" + + "\n" + + "expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"m\n" + "\"WatchSyncRefreshCredentialsRequest\x12G\n" + "\acontext\x18\x01 \x01(\v2-.silo.plugin.v1.WatchSyncAuthenticatedContextR\acontext\"\xd7\x01\n" + "\x1bWatchSyncCredentialResponse\x12F\n" + @@ -2777,7 +2820,7 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aD\n" + "\x16SeriesExternalIdsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x04\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xff\x04\n" + "\x0eWatchSyncEvent\x12\x19\n" + "\bevent_id\x18\x01 \x01(\tR\aeventId\x12@\n" + "\toperation\x18\x02 \x01(\x0e2\".silo.plugin.v1.WatchSyncOperationR\toperation\x127\n" + @@ -2792,9 +2835,10 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x12completion_percent\x18\n" + " \x01(\x01R\x11completionPercent\x12\x1d\n" + "\n" + - "play_count\x18\v \x01(\x05R\tplayCount\x12#\n" + - "\rlist_position\x18\f \x01(\x05R\flistPosition\x12*\n" + - "\x11provider_item_key\x18\r \x01(\tR\x0fproviderItemKey\"\x9e\x01\n" + + "play_count\x18\v \x01(\x05R\tplayCount\x12(\n" + + "\rlist_position\x18\f \x01(\x05H\x00R\flistPosition\x88\x01\x01\x12*\n" + + "\x11provider_item_key\x18\r \x01(\tR\x0fproviderItemKeyB\x10\n" + + "\x0e_list_position\"\x9e\x01\n" + "\x1bWatchSyncApplyEventsRequest\x12G\n" + "\acontext\x18\x01 \x01(\v2-.silo.plugin.v1.WatchSyncAuthenticatedContextR\acontext\x126\n" + "\x06events\x18\x02 \x03(\v2\x1e.silo.plugin.v1.WatchSyncEventR\x06events\"\xa5\x01\n" + @@ -2820,9 +2864,10 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "\x0flast_watched_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\rlastWatchedAt\"\x82\x01\n" + "\x1cWatchSyncRemoteProgressState\x12)\n" + "\x10progress_percent\x18\x01 \x01(\x01R\x0fprogressPercent\x127\n" + - "\tpaused_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bpausedAt\"S\n" + + "\tpaused_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bpausedAt\"m\n" + "\x18WatchSyncRemoteListState\x127\n" + - "\tlisted_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\blistedAt\"\x97\x03\n" + + "\tlisted_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\blistedAt\x12\x18\n" + + "\aremoved\x18\x02 \x01(\bR\aremoved\"\x97\x03\n" + "\x14WatchSyncRemoteState\x12*\n" + "\x11provider_item_key\x18\x01 \x01(\tR\x0fproviderItemKey\x124\n" + "\x05media\x18\x02 \x01(\v2\x1e.silo.plugin.v1.WatchSyncMediaR\x05media\x12E\n" + @@ -2888,18 +2933,19 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + "(WATCH_SYNC_FAULT_CODE_INVALID_CREDENTIAL\x10\x03\x12+\n" + "'WATCH_SYNC_FAULT_CODE_PERMISSION_DENIED\x10\x04\x12)\n" + "%WATCH_SYNC_FAULT_CODE_INVALID_REQUEST\x10\x05\x12#\n" + - "\x1fWATCH_SYNC_FAULT_CODE_PERMANENT\x10\x062\xbc\b\n" + + "\x1fWATCH_SYNC_FAULT_CODE_PERMANENT\x10\x062\x9b\x06\n" + "\x11WatchSyncProvider\x12n\n" + "\rInitAuthorize\x12-.silo.plugin.v1.WatchSyncInitAuthorizeRequest\x1a..silo.plugin.v1.WatchSyncInitAuthorizeResponse\x12i\n" + "\fExchangeCode\x12,.silo.plugin.v1.WatchSyncExchangeCodeRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12m\n" + - "\x0eExchangeAPIKey\x12..silo.plugin.v1.WatchSyncExchangeAPIKeyRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12\x8f\x01\n" + - "\x18StartDeviceAuthorization\x128.silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest\x1a9.silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse\x12\x8c\x01\n" + - "\x17PollDeviceAuthorization\x127.silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest\x1a8.silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse\x12u\n" + + "\x0eExchangeAPIKey\x12..silo.plugin.v1.WatchSyncExchangeAPIKeyRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12u\n" + "\x12RefreshCredentials\x122.silo.plugin.v1.WatchSyncRefreshCredentialsRequest\x1a+.silo.plugin.v1.WatchSyncCredentialResponse\x12e\n" + "\n" + "GetAccount\x12*.silo.plugin.v1.WatchSyncGetAccountRequest\x1a+.silo.plugin.v1.WatchSyncGetAccountResponse\x12h\n" + "\vApplyEvents\x12+.silo.plugin.v1.WatchSyncApplyEventsRequest\x1a,.silo.plugin.v1.WatchSyncApplyEventsResponse\x12t\n" + - "\x0fListRemoteState\x12/.silo.plugin.v1.WatchSyncListRemoteStateRequest\x1a0.silo.plugin.v1.WatchSyncListRemoteStateResponseBPZNgithub.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1b\x06proto3" + "\x0fListRemoteState\x12/.silo.plugin.v1.WatchSyncListRemoteStateRequest\x1a0.silo.plugin.v1.WatchSyncListRemoteStateResponse2\xbc\x02\n" + + "#WatchSyncDeviceAuthorizationService\x12\x8a\x01\n" + + "\x05Start\x12?.silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartRequest\x1a@.silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartResponse\x12\x87\x01\n" + + "\x04Poll\x12>.silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollRequest\x1a?.silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponseBPZNgithub.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1b\x06proto3" var ( file_silo_plugin_v1_watch_sync_provider_proto_rawDescOnce sync.Once @@ -2916,51 +2962,51 @@ func file_silo_plugin_v1_watch_sync_provider_proto_rawDescGZIP() []byte { var file_silo_plugin_v1_watch_sync_provider_proto_enumTypes = make([]protoimpl.EnumInfo, 8) var file_silo_plugin_v1_watch_sync_provider_proto_msgTypes = make([]protoimpl.MessageInfo, 34) var file_silo_plugin_v1_watch_sync_provider_proto_goTypes = []any{ - (WatchSyncAuthMethod)(0), // 0: silo.plugin.v1.WatchSyncAuthMethod - (WatchSyncMediaType)(0), // 1: silo.plugin.v1.WatchSyncMediaType - (WatchSyncOperation)(0), // 2: silo.plugin.v1.WatchSyncOperation - (WatchSyncRemoteStateKind)(0), // 3: silo.plugin.v1.WatchSyncRemoteStateKind - (WatchSyncDeviceAuthorizationStatus)(0), // 4: silo.plugin.v1.WatchSyncDeviceAuthorizationStatus - (WatchSyncOrigin)(0), // 5: silo.plugin.v1.WatchSyncOrigin - (WatchSyncApplyStatus)(0), // 6: silo.plugin.v1.WatchSyncApplyStatus - (WatchSyncFaultCode)(0), // 7: silo.plugin.v1.WatchSyncFaultCode - (*WatchSyncProviderDescriptor)(nil), // 8: silo.plugin.v1.WatchSyncProviderDescriptor - (*WatchSyncProviderConfig)(nil), // 9: silo.plugin.v1.WatchSyncProviderConfig - (*WatchSyncCredentials)(nil), // 10: silo.plugin.v1.WatchSyncCredentials - (*WatchSyncAuthenticatedContext)(nil), // 11: silo.plugin.v1.WatchSyncAuthenticatedContext - (*WatchSyncAccount)(nil), // 12: silo.plugin.v1.WatchSyncAccount - (*WatchSyncFault)(nil), // 13: silo.plugin.v1.WatchSyncFault - (*WatchSyncInitAuthorizeRequest)(nil), // 14: silo.plugin.v1.WatchSyncInitAuthorizeRequest - (*WatchSyncInitAuthorizeResponse)(nil), // 15: silo.plugin.v1.WatchSyncInitAuthorizeResponse - (*WatchSyncExchangeCodeRequest)(nil), // 16: silo.plugin.v1.WatchSyncExchangeCodeRequest - (*WatchSyncExchangeAPIKeyRequest)(nil), // 17: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest - (*WatchSyncStartDeviceAuthorizationRequest)(nil), // 18: silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest - (*WatchSyncStartDeviceAuthorizationResponse)(nil), // 19: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse - (*WatchSyncPollDeviceAuthorizationRequest)(nil), // 20: silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest - (*WatchSyncPollDeviceAuthorizationResponse)(nil), // 21: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse - (*WatchSyncRefreshCredentialsRequest)(nil), // 22: silo.plugin.v1.WatchSyncRefreshCredentialsRequest - (*WatchSyncCredentialResponse)(nil), // 23: silo.plugin.v1.WatchSyncCredentialResponse - (*WatchSyncGetAccountRequest)(nil), // 24: silo.plugin.v1.WatchSyncGetAccountRequest - (*WatchSyncGetAccountResponse)(nil), // 25: silo.plugin.v1.WatchSyncGetAccountResponse - (*WatchSyncMedia)(nil), // 26: silo.plugin.v1.WatchSyncMedia - (*WatchSyncEvent)(nil), // 27: silo.plugin.v1.WatchSyncEvent - (*WatchSyncApplyEventsRequest)(nil), // 28: silo.plugin.v1.WatchSyncApplyEventsRequest - (*WatchSyncApplyResult)(nil), // 29: silo.plugin.v1.WatchSyncApplyResult - (*WatchSyncApplyEventsResponse)(nil), // 30: silo.plugin.v1.WatchSyncApplyEventsResponse - (*WatchSyncListRemoteStateRequest)(nil), // 31: silo.plugin.v1.WatchSyncListRemoteStateRequest - (*WatchSyncRemoteWatchedState)(nil), // 32: silo.plugin.v1.WatchSyncRemoteWatchedState - (*WatchSyncRemoteProgressState)(nil), // 33: silo.plugin.v1.WatchSyncRemoteProgressState - (*WatchSyncRemoteListState)(nil), // 34: silo.plugin.v1.WatchSyncRemoteListState - (*WatchSyncRemoteState)(nil), // 35: silo.plugin.v1.WatchSyncRemoteState - (*WatchSyncListRemoteStateResponse)(nil), // 36: silo.plugin.v1.WatchSyncListRemoteStateResponse - nil, // 37: silo.plugin.v1.WatchSyncProviderConfig.ValuesEntry - nil, // 38: silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntry - nil, // 39: silo.plugin.v1.WatchSyncCredentials.SecretAttributesEntry - nil, // 40: silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry - nil, // 41: silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry - (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 43: google.protobuf.Duration - (*structpb.Struct)(nil), // 44: google.protobuf.Struct + (WatchSyncAuthMethod)(0), // 0: silo.plugin.v1.WatchSyncAuthMethod + (WatchSyncMediaType)(0), // 1: silo.plugin.v1.WatchSyncMediaType + (WatchSyncOperation)(0), // 2: silo.plugin.v1.WatchSyncOperation + (WatchSyncRemoteStateKind)(0), // 3: silo.plugin.v1.WatchSyncRemoteStateKind + (WatchSyncDeviceAuthorizationStatus)(0), // 4: silo.plugin.v1.WatchSyncDeviceAuthorizationStatus + (WatchSyncOrigin)(0), // 5: silo.plugin.v1.WatchSyncOrigin + (WatchSyncApplyStatus)(0), // 6: silo.plugin.v1.WatchSyncApplyStatus + (WatchSyncFaultCode)(0), // 7: silo.plugin.v1.WatchSyncFaultCode + (*WatchSyncProviderDescriptor)(nil), // 8: silo.plugin.v1.WatchSyncProviderDescriptor + (*WatchSyncProviderConfig)(nil), // 9: silo.plugin.v1.WatchSyncProviderConfig + (*WatchSyncCredentials)(nil), // 10: silo.plugin.v1.WatchSyncCredentials + (*WatchSyncAuthenticatedContext)(nil), // 11: silo.plugin.v1.WatchSyncAuthenticatedContext + (*WatchSyncAccount)(nil), // 12: silo.plugin.v1.WatchSyncAccount + (*WatchSyncFault)(nil), // 13: silo.plugin.v1.WatchSyncFault + (*WatchSyncInitAuthorizeRequest)(nil), // 14: silo.plugin.v1.WatchSyncInitAuthorizeRequest + (*WatchSyncInitAuthorizeResponse)(nil), // 15: silo.plugin.v1.WatchSyncInitAuthorizeResponse + (*WatchSyncExchangeCodeRequest)(nil), // 16: silo.plugin.v1.WatchSyncExchangeCodeRequest + (*WatchSyncExchangeAPIKeyRequest)(nil), // 17: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest + (*WatchSyncDeviceAuthorizationServiceStartRequest)(nil), // 18: silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartRequest + (*WatchSyncDeviceAuthorizationServiceStartResponse)(nil), // 19: silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartResponse + (*WatchSyncDeviceAuthorizationServicePollRequest)(nil), // 20: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollRequest + (*WatchSyncDeviceAuthorizationServicePollResponse)(nil), // 21: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse + (*WatchSyncRefreshCredentialsRequest)(nil), // 22: silo.plugin.v1.WatchSyncRefreshCredentialsRequest + (*WatchSyncCredentialResponse)(nil), // 23: silo.plugin.v1.WatchSyncCredentialResponse + (*WatchSyncGetAccountRequest)(nil), // 24: silo.plugin.v1.WatchSyncGetAccountRequest + (*WatchSyncGetAccountResponse)(nil), // 25: silo.plugin.v1.WatchSyncGetAccountResponse + (*WatchSyncMedia)(nil), // 26: silo.plugin.v1.WatchSyncMedia + (*WatchSyncEvent)(nil), // 27: silo.plugin.v1.WatchSyncEvent + (*WatchSyncApplyEventsRequest)(nil), // 28: silo.plugin.v1.WatchSyncApplyEventsRequest + (*WatchSyncApplyResult)(nil), // 29: silo.plugin.v1.WatchSyncApplyResult + (*WatchSyncApplyEventsResponse)(nil), // 30: silo.plugin.v1.WatchSyncApplyEventsResponse + (*WatchSyncListRemoteStateRequest)(nil), // 31: silo.plugin.v1.WatchSyncListRemoteStateRequest + (*WatchSyncRemoteWatchedState)(nil), // 32: silo.plugin.v1.WatchSyncRemoteWatchedState + (*WatchSyncRemoteProgressState)(nil), // 33: silo.plugin.v1.WatchSyncRemoteProgressState + (*WatchSyncRemoteListState)(nil), // 34: silo.plugin.v1.WatchSyncRemoteListState + (*WatchSyncRemoteState)(nil), // 35: silo.plugin.v1.WatchSyncRemoteState + (*WatchSyncListRemoteStateResponse)(nil), // 36: silo.plugin.v1.WatchSyncListRemoteStateResponse + nil, // 37: silo.plugin.v1.WatchSyncProviderConfig.ValuesEntry + nil, // 38: silo.plugin.v1.WatchSyncProviderConfig.SecretValuesEntry + nil, // 39: silo.plugin.v1.WatchSyncCredentials.SecretAttributesEntry + nil, // 40: silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry + nil, // 41: silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 43: google.protobuf.Duration + (*structpb.Struct)(nil), // 44: google.protobuf.Struct } var file_silo_plugin_v1_watch_sync_provider_proto_depIdxs = []int32{ 0, // 0: silo.plugin.v1.WatchSyncProviderDescriptor.auth_methods:type_name -> silo.plugin.v1.WatchSyncAuthMethod @@ -2977,73 +3023,75 @@ var file_silo_plugin_v1_watch_sync_provider_proto_depIdxs = []int32{ 13, // 11: silo.plugin.v1.WatchSyncInitAuthorizeResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault 9, // 12: silo.plugin.v1.WatchSyncExchangeCodeRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig 9, // 13: silo.plugin.v1.WatchSyncExchangeAPIKeyRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 9, // 14: silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 43, // 15: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse.polling_interval:type_name -> google.protobuf.Duration - 42, // 16: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse.expires_at:type_name -> google.protobuf.Timestamp - 13, // 17: silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 9, // 18: silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig - 4, // 19: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.status:type_name -> silo.plugin.v1.WatchSyncDeviceAuthorizationStatus - 10, // 20: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 12, // 21: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount - 13, // 22: silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 11, // 23: silo.plugin.v1.WatchSyncRefreshCredentialsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 10, // 24: silo.plugin.v1.WatchSyncCredentialResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 12, // 25: silo.plugin.v1.WatchSyncCredentialResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount - 13, // 26: silo.plugin.v1.WatchSyncCredentialResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 11, // 27: silo.plugin.v1.WatchSyncGetAccountRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 12, // 28: silo.plugin.v1.WatchSyncGetAccountResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount - 13, // 29: silo.plugin.v1.WatchSyncGetAccountResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 1, // 30: silo.plugin.v1.WatchSyncMedia.media_type:type_name -> silo.plugin.v1.WatchSyncMediaType - 40, // 31: silo.plugin.v1.WatchSyncMedia.external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry - 41, // 32: silo.plugin.v1.WatchSyncMedia.series_external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry - 44, // 33: silo.plugin.v1.WatchSyncMedia.metadata:type_name -> google.protobuf.Struct - 2, // 34: silo.plugin.v1.WatchSyncEvent.operation:type_name -> silo.plugin.v1.WatchSyncOperation - 5, // 35: silo.plugin.v1.WatchSyncEvent.origin:type_name -> silo.plugin.v1.WatchSyncOrigin - 42, // 36: silo.plugin.v1.WatchSyncEvent.occurred_at:type_name -> google.protobuf.Timestamp - 26, // 37: silo.plugin.v1.WatchSyncEvent.media:type_name -> silo.plugin.v1.WatchSyncMedia - 11, // 38: silo.plugin.v1.WatchSyncApplyEventsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 27, // 39: silo.plugin.v1.WatchSyncApplyEventsRequest.events:type_name -> silo.plugin.v1.WatchSyncEvent - 6, // 40: silo.plugin.v1.WatchSyncApplyResult.status:type_name -> silo.plugin.v1.WatchSyncApplyStatus - 13, // 41: silo.plugin.v1.WatchSyncApplyResult.fault:type_name -> silo.plugin.v1.WatchSyncFault - 29, // 42: silo.plugin.v1.WatchSyncApplyEventsResponse.results:type_name -> silo.plugin.v1.WatchSyncApplyResult - 10, // 43: silo.plugin.v1.WatchSyncApplyEventsResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 13, // 44: silo.plugin.v1.WatchSyncApplyEventsResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 11, // 45: silo.plugin.v1.WatchSyncListRemoteStateRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext - 3, // 46: silo.plugin.v1.WatchSyncListRemoteStateRequest.state_kinds:type_name -> silo.plugin.v1.WatchSyncRemoteStateKind - 42, // 47: silo.plugin.v1.WatchSyncRemoteWatchedState.last_watched_at:type_name -> google.protobuf.Timestamp - 42, // 48: silo.plugin.v1.WatchSyncRemoteProgressState.paused_at:type_name -> google.protobuf.Timestamp - 42, // 49: silo.plugin.v1.WatchSyncRemoteListState.listed_at:type_name -> google.protobuf.Timestamp - 26, // 50: silo.plugin.v1.WatchSyncRemoteState.media:type_name -> silo.plugin.v1.WatchSyncMedia - 32, // 51: silo.plugin.v1.WatchSyncRemoteState.watched:type_name -> silo.plugin.v1.WatchSyncRemoteWatchedState - 33, // 52: silo.plugin.v1.WatchSyncRemoteState.progress:type_name -> silo.plugin.v1.WatchSyncRemoteProgressState - 34, // 53: silo.plugin.v1.WatchSyncRemoteState.favorite:type_name -> silo.plugin.v1.WatchSyncRemoteListState - 34, // 54: silo.plugin.v1.WatchSyncRemoteState.watchlist:type_name -> silo.plugin.v1.WatchSyncRemoteListState - 35, // 55: silo.plugin.v1.WatchSyncListRemoteStateResponse.items:type_name -> silo.plugin.v1.WatchSyncRemoteState - 10, // 56: silo.plugin.v1.WatchSyncListRemoteStateResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials - 13, // 57: silo.plugin.v1.WatchSyncListRemoteStateResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault - 14, // 58: silo.plugin.v1.WatchSyncProvider.InitAuthorize:input_type -> silo.plugin.v1.WatchSyncInitAuthorizeRequest - 16, // 59: silo.plugin.v1.WatchSyncProvider.ExchangeCode:input_type -> silo.plugin.v1.WatchSyncExchangeCodeRequest - 17, // 60: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:input_type -> silo.plugin.v1.WatchSyncExchangeAPIKeyRequest - 18, // 61: silo.plugin.v1.WatchSyncProvider.StartDeviceAuthorization:input_type -> silo.plugin.v1.WatchSyncStartDeviceAuthorizationRequest - 20, // 62: silo.plugin.v1.WatchSyncProvider.PollDeviceAuthorization:input_type -> silo.plugin.v1.WatchSyncPollDeviceAuthorizationRequest + 9, // 14: silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 43, // 15: silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartResponse.polling_interval:type_name -> google.protobuf.Duration + 42, // 16: silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartResponse.expires_at:type_name -> google.protobuf.Timestamp + 13, // 17: silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 9, // 18: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollRequest.provider_config:type_name -> silo.plugin.v1.WatchSyncProviderConfig + 4, // 19: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse.status:type_name -> silo.plugin.v1.WatchSyncDeviceAuthorizationStatus + 10, // 20: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 12, // 21: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount + 13, // 22: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 43, // 23: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse.polling_interval:type_name -> google.protobuf.Duration + 42, // 24: silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse.expires_at:type_name -> google.protobuf.Timestamp + 11, // 25: silo.plugin.v1.WatchSyncRefreshCredentialsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 10, // 26: silo.plugin.v1.WatchSyncCredentialResponse.credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 12, // 27: silo.plugin.v1.WatchSyncCredentialResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount + 13, // 28: silo.plugin.v1.WatchSyncCredentialResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 11, // 29: silo.plugin.v1.WatchSyncGetAccountRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 12, // 30: silo.plugin.v1.WatchSyncGetAccountResponse.account:type_name -> silo.plugin.v1.WatchSyncAccount + 13, // 31: silo.plugin.v1.WatchSyncGetAccountResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 1, // 32: silo.plugin.v1.WatchSyncMedia.media_type:type_name -> silo.plugin.v1.WatchSyncMediaType + 40, // 33: silo.plugin.v1.WatchSyncMedia.external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.ExternalIdsEntry + 41, // 34: silo.plugin.v1.WatchSyncMedia.series_external_ids:type_name -> silo.plugin.v1.WatchSyncMedia.SeriesExternalIdsEntry + 44, // 35: silo.plugin.v1.WatchSyncMedia.metadata:type_name -> google.protobuf.Struct + 2, // 36: silo.plugin.v1.WatchSyncEvent.operation:type_name -> silo.plugin.v1.WatchSyncOperation + 5, // 37: silo.plugin.v1.WatchSyncEvent.origin:type_name -> silo.plugin.v1.WatchSyncOrigin + 42, // 38: silo.plugin.v1.WatchSyncEvent.occurred_at:type_name -> google.protobuf.Timestamp + 26, // 39: silo.plugin.v1.WatchSyncEvent.media:type_name -> silo.plugin.v1.WatchSyncMedia + 11, // 40: silo.plugin.v1.WatchSyncApplyEventsRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 27, // 41: silo.plugin.v1.WatchSyncApplyEventsRequest.events:type_name -> silo.plugin.v1.WatchSyncEvent + 6, // 42: silo.plugin.v1.WatchSyncApplyResult.status:type_name -> silo.plugin.v1.WatchSyncApplyStatus + 13, // 43: silo.plugin.v1.WatchSyncApplyResult.fault:type_name -> silo.plugin.v1.WatchSyncFault + 29, // 44: silo.plugin.v1.WatchSyncApplyEventsResponse.results:type_name -> silo.plugin.v1.WatchSyncApplyResult + 10, // 45: silo.plugin.v1.WatchSyncApplyEventsResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 13, // 46: silo.plugin.v1.WatchSyncApplyEventsResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 11, // 47: silo.plugin.v1.WatchSyncListRemoteStateRequest.context:type_name -> silo.plugin.v1.WatchSyncAuthenticatedContext + 3, // 48: silo.plugin.v1.WatchSyncListRemoteStateRequest.state_kinds:type_name -> silo.plugin.v1.WatchSyncRemoteStateKind + 42, // 49: silo.plugin.v1.WatchSyncRemoteWatchedState.last_watched_at:type_name -> google.protobuf.Timestamp + 42, // 50: silo.plugin.v1.WatchSyncRemoteProgressState.paused_at:type_name -> google.protobuf.Timestamp + 42, // 51: silo.plugin.v1.WatchSyncRemoteListState.listed_at:type_name -> google.protobuf.Timestamp + 26, // 52: silo.plugin.v1.WatchSyncRemoteState.media:type_name -> silo.plugin.v1.WatchSyncMedia + 32, // 53: silo.plugin.v1.WatchSyncRemoteState.watched:type_name -> silo.plugin.v1.WatchSyncRemoteWatchedState + 33, // 54: silo.plugin.v1.WatchSyncRemoteState.progress:type_name -> silo.plugin.v1.WatchSyncRemoteProgressState + 34, // 55: silo.plugin.v1.WatchSyncRemoteState.favorite:type_name -> silo.plugin.v1.WatchSyncRemoteListState + 34, // 56: silo.plugin.v1.WatchSyncRemoteState.watchlist:type_name -> silo.plugin.v1.WatchSyncRemoteListState + 35, // 57: silo.plugin.v1.WatchSyncListRemoteStateResponse.items:type_name -> silo.plugin.v1.WatchSyncRemoteState + 10, // 58: silo.plugin.v1.WatchSyncListRemoteStateResponse.updated_credentials:type_name -> silo.plugin.v1.WatchSyncCredentials + 13, // 59: silo.plugin.v1.WatchSyncListRemoteStateResponse.fault:type_name -> silo.plugin.v1.WatchSyncFault + 14, // 60: silo.plugin.v1.WatchSyncProvider.InitAuthorize:input_type -> silo.plugin.v1.WatchSyncInitAuthorizeRequest + 16, // 61: silo.plugin.v1.WatchSyncProvider.ExchangeCode:input_type -> silo.plugin.v1.WatchSyncExchangeCodeRequest + 17, // 62: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:input_type -> silo.plugin.v1.WatchSyncExchangeAPIKeyRequest 22, // 63: silo.plugin.v1.WatchSyncProvider.RefreshCredentials:input_type -> silo.plugin.v1.WatchSyncRefreshCredentialsRequest 24, // 64: silo.plugin.v1.WatchSyncProvider.GetAccount:input_type -> silo.plugin.v1.WatchSyncGetAccountRequest 28, // 65: silo.plugin.v1.WatchSyncProvider.ApplyEvents:input_type -> silo.plugin.v1.WatchSyncApplyEventsRequest 31, // 66: silo.plugin.v1.WatchSyncProvider.ListRemoteState:input_type -> silo.plugin.v1.WatchSyncListRemoteStateRequest - 15, // 67: silo.plugin.v1.WatchSyncProvider.InitAuthorize:output_type -> silo.plugin.v1.WatchSyncInitAuthorizeResponse - 23, // 68: silo.plugin.v1.WatchSyncProvider.ExchangeCode:output_type -> silo.plugin.v1.WatchSyncCredentialResponse - 23, // 69: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:output_type -> silo.plugin.v1.WatchSyncCredentialResponse - 19, // 70: silo.plugin.v1.WatchSyncProvider.StartDeviceAuthorization:output_type -> silo.plugin.v1.WatchSyncStartDeviceAuthorizationResponse - 21, // 71: silo.plugin.v1.WatchSyncProvider.PollDeviceAuthorization:output_type -> silo.plugin.v1.WatchSyncPollDeviceAuthorizationResponse + 18, // 67: silo.plugin.v1.WatchSyncDeviceAuthorizationService.Start:input_type -> silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartRequest + 20, // 68: silo.plugin.v1.WatchSyncDeviceAuthorizationService.Poll:input_type -> silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollRequest + 15, // 69: silo.plugin.v1.WatchSyncProvider.InitAuthorize:output_type -> silo.plugin.v1.WatchSyncInitAuthorizeResponse + 23, // 70: silo.plugin.v1.WatchSyncProvider.ExchangeCode:output_type -> silo.plugin.v1.WatchSyncCredentialResponse + 23, // 71: silo.plugin.v1.WatchSyncProvider.ExchangeAPIKey:output_type -> silo.plugin.v1.WatchSyncCredentialResponse 23, // 72: silo.plugin.v1.WatchSyncProvider.RefreshCredentials:output_type -> silo.plugin.v1.WatchSyncCredentialResponse 25, // 73: silo.plugin.v1.WatchSyncProvider.GetAccount:output_type -> silo.plugin.v1.WatchSyncGetAccountResponse 30, // 74: silo.plugin.v1.WatchSyncProvider.ApplyEvents:output_type -> silo.plugin.v1.WatchSyncApplyEventsResponse 36, // 75: silo.plugin.v1.WatchSyncProvider.ListRemoteState:output_type -> silo.plugin.v1.WatchSyncListRemoteStateResponse - 67, // [67:76] is the sub-list for method output_type - 58, // [58:67] is the sub-list for method input_type - 58, // [58:58] is the sub-list for extension type_name - 58, // [58:58] is the sub-list for extension extendee - 0, // [0:58] is the sub-list for field type_name + 19, // 76: silo.plugin.v1.WatchSyncDeviceAuthorizationService.Start:output_type -> silo.plugin.v1.WatchSyncDeviceAuthorizationServiceStartResponse + 21, // 77: silo.plugin.v1.WatchSyncDeviceAuthorizationService.Poll:output_type -> silo.plugin.v1.WatchSyncDeviceAuthorizationServicePollResponse + 69, // [69:78] is the sub-list for method output_type + 60, // [60:69] is the sub-list for method input_type + 60, // [60:60] is the sub-list for extension type_name + 60, // [60:60] is the sub-list for extension extendee + 0, // [0:60] is the sub-list for field type_name } func init() { file_silo_plugin_v1_watch_sync_provider_proto_init() } @@ -3051,6 +3099,7 @@ func file_silo_plugin_v1_watch_sync_provider_proto_init() { if File_silo_plugin_v1_watch_sync_provider_proto != nil { return } + file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[19].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ @@ -3059,7 +3108,7 @@ func file_silo_plugin_v1_watch_sync_provider_proto_init() { NumEnums: 8, NumMessages: 34, NumExtensions: 0, - NumServices: 1, + NumServices: 2, }, GoTypes: file_silo_plugin_v1_watch_sync_provider_proto_goTypes, DependencyIndexes: file_silo_plugin_v1_watch_sync_provider_proto_depIdxs, diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_compat_test.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_compat_test.go new file mode 100644 index 0000000..e415a1e --- /dev/null +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_compat_test.go @@ -0,0 +1,39 @@ +package pluginv1 + +import "context" + +// legacyDirectWatchSyncProvider is deliberately implemented without embedding +// UnimplementedWatchSyncProviderServer. Keep this compile-time assertion so a +// future additive RPC cannot silently break source compatibility for v0.12 +// plugins generated with require_unimplemented_servers=false. +type legacyDirectWatchSyncProvider struct{} + +func (legacyDirectWatchSyncProvider) InitAuthorize(context.Context, *WatchSyncInitAuthorizeRequest) (*WatchSyncInitAuthorizeResponse, error) { + return nil, nil +} + +func (legacyDirectWatchSyncProvider) ExchangeCode(context.Context, *WatchSyncExchangeCodeRequest) (*WatchSyncCredentialResponse, error) { + return nil, nil +} + +func (legacyDirectWatchSyncProvider) ExchangeAPIKey(context.Context, *WatchSyncExchangeAPIKeyRequest) (*WatchSyncCredentialResponse, error) { + return nil, nil +} + +func (legacyDirectWatchSyncProvider) RefreshCredentials(context.Context, *WatchSyncRefreshCredentialsRequest) (*WatchSyncCredentialResponse, error) { + return nil, nil +} + +func (legacyDirectWatchSyncProvider) GetAccount(context.Context, *WatchSyncGetAccountRequest) (*WatchSyncGetAccountResponse, error) { + return nil, nil +} + +func (legacyDirectWatchSyncProvider) ApplyEvents(context.Context, *WatchSyncApplyEventsRequest) (*WatchSyncApplyEventsResponse, error) { + return nil, nil +} + +func (legacyDirectWatchSyncProvider) ListRemoteState(context.Context, *WatchSyncListRemoteStateRequest) (*WatchSyncListRemoteStateResponse, error) { + return nil, nil +} + +var _ WatchSyncProviderServer = legacyDirectWatchSyncProvider{} diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go index 3e8b6a9..1219820 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go @@ -19,15 +19,13 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - WatchSyncProvider_InitAuthorize_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/InitAuthorize" - WatchSyncProvider_ExchangeCode_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeCode" - WatchSyncProvider_ExchangeAPIKey_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeAPIKey" - WatchSyncProvider_StartDeviceAuthorization_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/StartDeviceAuthorization" - WatchSyncProvider_PollDeviceAuthorization_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/PollDeviceAuthorization" - WatchSyncProvider_RefreshCredentials_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/RefreshCredentials" - WatchSyncProvider_GetAccount_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/GetAccount" - WatchSyncProvider_ApplyEvents_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ApplyEvents" - WatchSyncProvider_ListRemoteState_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ListRemoteState" + WatchSyncProvider_InitAuthorize_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/InitAuthorize" + WatchSyncProvider_ExchangeCode_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeCode" + WatchSyncProvider_ExchangeAPIKey_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ExchangeAPIKey" + WatchSyncProvider_RefreshCredentials_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/RefreshCredentials" + WatchSyncProvider_GetAccount_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/GetAccount" + WatchSyncProvider_ApplyEvents_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ApplyEvents" + WatchSyncProvider_ListRemoteState_FullMethodName = "/silo.plugin.v1.WatchSyncProvider/ListRemoteState" ) // WatchSyncProviderClient is the client API for WatchSyncProvider service. @@ -42,8 +40,6 @@ type WatchSyncProviderClient interface { InitAuthorize(ctx context.Context, in *WatchSyncInitAuthorizeRequest, opts ...grpc.CallOption) (*WatchSyncInitAuthorizeResponse, error) ExchangeCode(ctx context.Context, in *WatchSyncExchangeCodeRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) ExchangeAPIKey(ctx context.Context, in *WatchSyncExchangeAPIKeyRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) - StartDeviceAuthorization(ctx context.Context, in *WatchSyncStartDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncStartDeviceAuthorizationResponse, error) - PollDeviceAuthorization(ctx context.Context, in *WatchSyncPollDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncPollDeviceAuthorizationResponse, error) RefreshCredentials(ctx context.Context, in *WatchSyncRefreshCredentialsRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) GetAccount(ctx context.Context, in *WatchSyncGetAccountRequest, opts ...grpc.CallOption) (*WatchSyncGetAccountResponse, error) ApplyEvents(ctx context.Context, in *WatchSyncApplyEventsRequest, opts ...grpc.CallOption) (*WatchSyncApplyEventsResponse, error) @@ -88,26 +84,6 @@ func (c *watchSyncProviderClient) ExchangeAPIKey(ctx context.Context, in *WatchS return out, nil } -func (c *watchSyncProviderClient) StartDeviceAuthorization(ctx context.Context, in *WatchSyncStartDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncStartDeviceAuthorizationResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(WatchSyncStartDeviceAuthorizationResponse) - err := c.cc.Invoke(ctx, WatchSyncProvider_StartDeviceAuthorization_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *watchSyncProviderClient) PollDeviceAuthorization(ctx context.Context, in *WatchSyncPollDeviceAuthorizationRequest, opts ...grpc.CallOption) (*WatchSyncPollDeviceAuthorizationResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(WatchSyncPollDeviceAuthorizationResponse) - err := c.cc.Invoke(ctx, WatchSyncProvider_PollDeviceAuthorization_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *watchSyncProviderClient) RefreshCredentials(ctx context.Context, in *WatchSyncRefreshCredentialsRequest, opts ...grpc.CallOption) (*WatchSyncCredentialResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(WatchSyncCredentialResponse) @@ -160,8 +136,6 @@ type WatchSyncProviderServer interface { InitAuthorize(context.Context, *WatchSyncInitAuthorizeRequest) (*WatchSyncInitAuthorizeResponse, error) ExchangeCode(context.Context, *WatchSyncExchangeCodeRequest) (*WatchSyncCredentialResponse, error) ExchangeAPIKey(context.Context, *WatchSyncExchangeAPIKeyRequest) (*WatchSyncCredentialResponse, error) - StartDeviceAuthorization(context.Context, *WatchSyncStartDeviceAuthorizationRequest) (*WatchSyncStartDeviceAuthorizationResponse, error) - PollDeviceAuthorization(context.Context, *WatchSyncPollDeviceAuthorizationRequest) (*WatchSyncPollDeviceAuthorizationResponse, error) RefreshCredentials(context.Context, *WatchSyncRefreshCredentialsRequest) (*WatchSyncCredentialResponse, error) GetAccount(context.Context, *WatchSyncGetAccountRequest) (*WatchSyncGetAccountResponse, error) ApplyEvents(context.Context, *WatchSyncApplyEventsRequest) (*WatchSyncApplyEventsResponse, error) @@ -184,12 +158,6 @@ func (UnimplementedWatchSyncProviderServer) ExchangeCode(context.Context, *Watch func (UnimplementedWatchSyncProviderServer) ExchangeAPIKey(context.Context, *WatchSyncExchangeAPIKeyRequest) (*WatchSyncCredentialResponse, error) { return nil, status.Error(codes.Unimplemented, "method ExchangeAPIKey not implemented") } -func (UnimplementedWatchSyncProviderServer) StartDeviceAuthorization(context.Context, *WatchSyncStartDeviceAuthorizationRequest) (*WatchSyncStartDeviceAuthorizationResponse, error) { - return nil, status.Error(codes.Unimplemented, "method StartDeviceAuthorization not implemented") -} -func (UnimplementedWatchSyncProviderServer) PollDeviceAuthorization(context.Context, *WatchSyncPollDeviceAuthorizationRequest) (*WatchSyncPollDeviceAuthorizationResponse, error) { - return nil, status.Error(codes.Unimplemented, "method PollDeviceAuthorization not implemented") -} func (UnimplementedWatchSyncProviderServer) RefreshCredentials(context.Context, *WatchSyncRefreshCredentialsRequest) (*WatchSyncCredentialResponse, error) { return nil, status.Error(codes.Unimplemented, "method RefreshCredentials not implemented") } @@ -276,42 +244,6 @@ func _WatchSyncProvider_ExchangeAPIKey_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } -func _WatchSyncProvider_StartDeviceAuthorization_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(WatchSyncStartDeviceAuthorizationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WatchSyncProviderServer).StartDeviceAuthorization(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: WatchSyncProvider_StartDeviceAuthorization_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WatchSyncProviderServer).StartDeviceAuthorization(ctx, req.(*WatchSyncStartDeviceAuthorizationRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WatchSyncProvider_PollDeviceAuthorization_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(WatchSyncPollDeviceAuthorizationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WatchSyncProviderServer).PollDeviceAuthorization(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: WatchSyncProvider_PollDeviceAuthorization_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WatchSyncProviderServer).PollDeviceAuthorization(ctx, req.(*WatchSyncPollDeviceAuthorizationRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _WatchSyncProvider_RefreshCredentials_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(WatchSyncRefreshCredentialsRequest) if err := dec(in); err != nil { @@ -403,14 +335,6 @@ var WatchSyncProvider_ServiceDesc = grpc.ServiceDesc{ MethodName: "ExchangeAPIKey", Handler: _WatchSyncProvider_ExchangeAPIKey_Handler, }, - { - MethodName: "StartDeviceAuthorization", - Handler: _WatchSyncProvider_StartDeviceAuthorization_Handler, - }, - { - MethodName: "PollDeviceAuthorization", - Handler: _WatchSyncProvider_PollDeviceAuthorization_Handler, - }, { MethodName: "RefreshCredentials", Handler: _WatchSyncProvider_RefreshCredentials_Handler, @@ -431,3 +355,149 @@ var WatchSyncProvider_ServiceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, Metadata: "silo/plugin/v1/watch_sync_provider.proto", } + +const ( + WatchSyncDeviceAuthorizationService_Start_FullMethodName = "/silo.plugin.v1.WatchSyncDeviceAuthorizationService/Start" + WatchSyncDeviceAuthorizationService_Poll_FullMethodName = "/silo.plugin.v1.WatchSyncDeviceAuthorizationService/Poll" +) + +// WatchSyncDeviceAuthorizationServiceClient is the client API for WatchSyncDeviceAuthorizationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// WatchSyncDeviceAuthorization is separate from WatchSyncProvider so adding +// device authorization does not add methods to the released v0.12 Go server +// interface. A plugin advertising DEVICE_CODE registers both services. +type WatchSyncDeviceAuthorizationServiceClient interface { + Start(ctx context.Context, in *WatchSyncDeviceAuthorizationServiceStartRequest, opts ...grpc.CallOption) (*WatchSyncDeviceAuthorizationServiceStartResponse, error) + Poll(ctx context.Context, in *WatchSyncDeviceAuthorizationServicePollRequest, opts ...grpc.CallOption) (*WatchSyncDeviceAuthorizationServicePollResponse, error) +} + +type watchSyncDeviceAuthorizationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewWatchSyncDeviceAuthorizationServiceClient(cc grpc.ClientConnInterface) WatchSyncDeviceAuthorizationServiceClient { + return &watchSyncDeviceAuthorizationServiceClient{cc} +} + +func (c *watchSyncDeviceAuthorizationServiceClient) Start(ctx context.Context, in *WatchSyncDeviceAuthorizationServiceStartRequest, opts ...grpc.CallOption) (*WatchSyncDeviceAuthorizationServiceStartResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WatchSyncDeviceAuthorizationServiceStartResponse) + err := c.cc.Invoke(ctx, WatchSyncDeviceAuthorizationService_Start_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *watchSyncDeviceAuthorizationServiceClient) Poll(ctx context.Context, in *WatchSyncDeviceAuthorizationServicePollRequest, opts ...grpc.CallOption) (*WatchSyncDeviceAuthorizationServicePollResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WatchSyncDeviceAuthorizationServicePollResponse) + err := c.cc.Invoke(ctx, WatchSyncDeviceAuthorizationService_Poll_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// WatchSyncDeviceAuthorizationServiceServer is the server API for WatchSyncDeviceAuthorizationService service. +// All implementations should embed UnimplementedWatchSyncDeviceAuthorizationServiceServer +// for forward compatibility. +// +// WatchSyncDeviceAuthorization is separate from WatchSyncProvider so adding +// device authorization does not add methods to the released v0.12 Go server +// interface. A plugin advertising DEVICE_CODE registers both services. +type WatchSyncDeviceAuthorizationServiceServer interface { + Start(context.Context, *WatchSyncDeviceAuthorizationServiceStartRequest) (*WatchSyncDeviceAuthorizationServiceStartResponse, error) + Poll(context.Context, *WatchSyncDeviceAuthorizationServicePollRequest) (*WatchSyncDeviceAuthorizationServicePollResponse, error) +} + +// UnimplementedWatchSyncDeviceAuthorizationServiceServer should be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedWatchSyncDeviceAuthorizationServiceServer struct{} + +func (UnimplementedWatchSyncDeviceAuthorizationServiceServer) Start(context.Context, *WatchSyncDeviceAuthorizationServiceStartRequest) (*WatchSyncDeviceAuthorizationServiceStartResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Start not implemented") +} +func (UnimplementedWatchSyncDeviceAuthorizationServiceServer) Poll(context.Context, *WatchSyncDeviceAuthorizationServicePollRequest) (*WatchSyncDeviceAuthorizationServicePollResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Poll not implemented") +} +func (UnimplementedWatchSyncDeviceAuthorizationServiceServer) testEmbeddedByValue() {} + +// UnsafeWatchSyncDeviceAuthorizationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to WatchSyncDeviceAuthorizationServiceServer will +// result in compilation errors. +type UnsafeWatchSyncDeviceAuthorizationServiceServer interface { + mustEmbedUnimplementedWatchSyncDeviceAuthorizationServiceServer() +} + +func RegisterWatchSyncDeviceAuthorizationServiceServer(s grpc.ServiceRegistrar, srv WatchSyncDeviceAuthorizationServiceServer) { + // If the following call panics, it indicates UnimplementedWatchSyncDeviceAuthorizationServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&WatchSyncDeviceAuthorizationService_ServiceDesc, srv) +} + +func _WatchSyncDeviceAuthorizationService_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WatchSyncDeviceAuthorizationServiceStartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WatchSyncDeviceAuthorizationServiceServer).Start(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WatchSyncDeviceAuthorizationService_Start_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WatchSyncDeviceAuthorizationServiceServer).Start(ctx, req.(*WatchSyncDeviceAuthorizationServiceStartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WatchSyncDeviceAuthorizationService_Poll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WatchSyncDeviceAuthorizationServicePollRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WatchSyncDeviceAuthorizationServiceServer).Poll(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WatchSyncDeviceAuthorizationService_Poll_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WatchSyncDeviceAuthorizationServiceServer).Poll(ctx, req.(*WatchSyncDeviceAuthorizationServicePollRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// WatchSyncDeviceAuthorizationService_ServiceDesc is the grpc.ServiceDesc for WatchSyncDeviceAuthorizationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var WatchSyncDeviceAuthorizationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "silo.plugin.v1.WatchSyncDeviceAuthorizationService", + HandlerType: (*WatchSyncDeviceAuthorizationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Start", + Handler: _WatchSyncDeviceAuthorizationService_Start_Handler, + }, + { + MethodName: "Poll", + Handler: _WatchSyncDeviceAuthorizationService_Poll_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "silo/plugin/v1/watch_sync_provider.proto", +} diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go index 663cd7c..7c6b675 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go @@ -26,7 +26,7 @@ func TestWatchSyncTypedRemoteStateRoundTrip(t *testing.T) { ProgressPercent: 42.5, PausedAt: timestamppb.New(pausedAt), }, - Watchlist: &WatchSyncRemoteListState{ListedAt: timestamppb.New(pausedAt.Add(-2 * time.Hour))}, + Watchlist: &WatchSyncRemoteListState{ListedAt: timestamppb.New(pausedAt.Add(-2 * time.Hour)), Removed: true}, }}, NextCursor: "checkpoint-2", CompleteSnapshot: true, @@ -46,14 +46,15 @@ func TestWatchSyncTypedRemoteStateRoundTrip(t *testing.T) { item.GetWatched().GetPlayCount() != 2 || item.GetProgress().GetProgressPercent() != 42.5 || !item.GetProgress().GetPausedAt().AsTime().Equal(pausedAt) || - !item.GetWatchlist().GetListedAt().AsTime().Equal(pausedAt.Add(-2*time.Hour)) { + !item.GetWatchlist().GetListedAt().AsTime().Equal(pausedAt.Add(-2*time.Hour)) || + !item.GetWatchlist().GetRemoved() { t.Fatalf("remote state = %#v", item) } } func TestWatchSyncDeviceAuthorizationRoundTrip(t *testing.T) { expiresAt := time.Unix(1_800_000_000, 0).UTC() - input := &WatchSyncStartDeviceAuthorizationResponse{ + input := &WatchSyncDeviceAuthorizationServiceStartResponse{ UserCode: "ABCD-1234", VerificationUrl: "https://provider.example/activate", VerificationUrlComplete: "https://provider.example/activate?code=ABCD-1234", @@ -65,17 +66,61 @@ func TestWatchSyncDeviceAuthorizationRoundTrip(t *testing.T) { if err != nil { t.Fatal(err) } - var output WatchSyncStartDeviceAuthorizationResponse + var output WatchSyncDeviceAuthorizationServiceStartResponse if err := proto.Unmarshal(data, &output); err != nil { t.Fatal(err) } if output.GetUserCode() != input.GetUserCode() || + output.GetVerificationUrl() != input.GetVerificationUrl() || + output.GetVerificationUrlComplete() != input.GetVerificationUrlComplete() || + !proto.Equal(&output, input) || + string(output.GetProviderState()) != string(input.GetProviderState()) || output.GetPollingInterval().AsDuration() != 5*time.Second || !output.GetExpiresAt().AsTime().Equal(expiresAt) { t.Fatalf("device authorization = %#v", &output) } } +func TestWatchSyncDeviceAuthorizationPendingStateRoundTrip(t *testing.T) { + expiresAt := time.Unix(1_800_000_100, 0).UTC() + input := &WatchSyncDeviceAuthorizationServicePollResponse{ + Status: WatchSyncDeviceAuthorizationStatus_WATCH_SYNC_DEVICE_AUTHORIZATION_STATUS_PENDING, + ProviderState: []byte("rotated-state"), + PollingInterval: durationpb.New(10 * time.Second), + ExpiresAt: timestamppb.New(expiresAt), + } + data, err := proto.Marshal(input) + if err != nil { + t.Fatal(err) + } + var output WatchSyncDeviceAuthorizationServicePollResponse + if err := proto.Unmarshal(data, &output); err != nil { + t.Fatal(err) + } + if !proto.Equal(input, &output) { + t.Fatalf("pending device authorization = %#v", &output) + } +} + +func TestWatchSyncListPositionPreservesPresence(t *testing.T) { + zero := int32(0) + withZero := &WatchSyncEvent{ListPosition: &zero} + data, err := proto.Marshal(withZero) + if err != nil { + t.Fatal(err) + } + var output WatchSyncEvent + if err := proto.Unmarshal(data, &output); err != nil { + t.Fatal(err) + } + if output.ListPosition == nil || output.GetListPosition() != 0 { + t.Fatalf("explicit zero list position = %#v", output.ListPosition) + } + if (&WatchSyncEvent{}).ListPosition != nil { + t.Fatal("omitted list position unexpectedly has presence") + } +} + func TestWatchSyncApplyResultCarriesTypedRateLimit(t *testing.T) { retryAfter := 45 * time.Second request := &WatchSyncApplyEventsRequest{ diff --git a/pkg/pluginsdk/convert/watch_sync_provider_test.go b/pkg/pluginsdk/convert/watch_sync_provider_test.go index ca2b54e..9c607b9 100644 --- a/pkg/pluginsdk/convert/watch_sync_provider_test.go +++ b/pkg/pluginsdk/convert/watch_sync_provider_test.go @@ -33,7 +33,8 @@ func TestWatchSyncProviderDescriptorRoundTrip(t *testing.T) { } got := decoded.GetWatchSyncProvider() if got == nil || !got.GetExportWatched() || !got.GetImportWatchlist() || !got.GetScrobblePlayback() || got.GetMaxBatchSize() != 25 || - len(got.GetAuthMethods()) != 1 || len(got.GetSupportedMediaTypes()) != 2 || + len(got.GetAuthMethods()) != 1 || got.GetAuthMethods()[0] != pluginv1.WatchSyncAuthMethod_WATCH_SYNC_AUTH_METHOD_DEVICE_CODE || + len(got.GetSupportedMediaTypes()) != 2 || got.GetSupportedMediaTypes()[1] != pluginv1.WatchSyncMediaType_WATCH_SYNC_MEDIA_TYPE_EPISODE { t.Fatalf("decoded descriptor = %#v", got) } diff --git a/pkg/pluginsdk/runtime/runtime.go b/pkg/pluginsdk/runtime/runtime.go index 4bd1285..d80954f 100644 --- a/pkg/pluginsdk/runtime/runtime.go +++ b/pkg/pluginsdk/runtime/runtime.go @@ -35,6 +35,9 @@ type CapabilityServers struct { AuthProvider pluginv1.AuthProviderServer HttpRoutes pluginv1.HttpRoutesServer WatchSyncProvider pluginv1.WatchSyncProviderServer + // WatchSyncDeviceAuthorization is registered alongside WatchSyncProvider + // when the capability advertises device-code authentication. + WatchSyncDeviceAuthorization pluginv1.WatchSyncDeviceAuthorizationServiceServer } // Client wraps the gRPC connection to a plugin and provides typed accessors @@ -133,6 +136,10 @@ func (c *Client) WatchSyncProvider() pluginv1.WatchSyncProviderClient { return pluginv1.NewWatchSyncProviderClient(c.conn) } +func (c *Client) WatchSyncDeviceAuthorization() pluginv1.WatchSyncDeviceAuthorizationServiceClient { + return pluginv1.NewWatchSyncDeviceAuthorizationServiceClient(c.conn) +} + type GRPCPlugin struct { plugin.Plugin Servers CapabilityServers @@ -178,6 +185,9 @@ func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc.Server) if p.Servers.WatchSyncProvider != nil { pluginv1.RegisterWatchSyncProviderServer(server, p.Servers.WatchSyncProvider) } + if p.Servers.WatchSyncDeviceAuthorization != nil { + pluginv1.RegisterWatchSyncDeviceAuthorizationServiceServer(server, p.Servers.WatchSyncDeviceAuthorization) + } return nil } diff --git a/pkg/pluginsdk/runtime/watch_sync_provider_test.go b/pkg/pluginsdk/runtime/watch_sync_provider_test.go index 4b5d408..95a8f83 100644 --- a/pkg/pluginsdk/runtime/watch_sync_provider_test.go +++ b/pkg/pluginsdk/runtime/watch_sync_provider_test.go @@ -12,16 +12,24 @@ type stubWatchSyncProvider struct { pluginv1.UnimplementedWatchSyncProviderServer } -func TestGRPCServerRegistersWatchSyncProvider(t *testing.T) { +type stubWatchSyncDeviceAuthorization struct { + pluginv1.UnimplementedWatchSyncDeviceAuthorizationServiceServer +} + +func TestGRPCServerRegistersWatchSyncServices(t *testing.T) { p := &runtime.GRPCPlugin{Servers: runtime.CapabilityServers{ - Runtime: stubRuntime{}, - WatchSyncProvider: stubWatchSyncProvider{}, + Runtime: stubRuntime{}, + WatchSyncProvider: stubWatchSyncProvider{}, + WatchSyncDeviceAuthorization: stubWatchSyncDeviceAuthorization{}, }} srv := grpc.NewServer() if err := p.GRPCServer(nil, srv); err != nil { - t.Fatalf("GRPCServer with WatchSyncProvider = %v, want nil", err) + t.Fatalf("GRPCServer with watch-sync services = %v, want nil", err) } if _, ok := srv.GetServiceInfo()["silo.plugin.v1.WatchSyncProvider"]; !ok { t.Fatalf("WatchSyncProvider service not registered; got %v", srv.GetServiceInfo()) } + if _, ok := srv.GetServiceInfo()["silo.plugin.v1.WatchSyncDeviceAuthorizationService"]; !ok { + t.Fatalf("WatchSyncDeviceAuthorization service not registered; got %v", srv.GetServiceInfo()) + } } diff --git a/proto/silo/plugin/v1/watch_sync_provider.proto b/proto/silo/plugin/v1/watch_sync_provider.proto index ead759c..7fd1414 100644 --- a/proto/silo/plugin/v1/watch_sync_provider.proto +++ b/proto/silo/plugin/v1/watch_sync_provider.proto @@ -16,14 +16,20 @@ service WatchSyncProvider { rpc InitAuthorize(WatchSyncInitAuthorizeRequest) returns (WatchSyncInitAuthorizeResponse); rpc ExchangeCode(WatchSyncExchangeCodeRequest) returns (WatchSyncCredentialResponse); rpc ExchangeAPIKey(WatchSyncExchangeAPIKeyRequest) returns (WatchSyncCredentialResponse); - rpc StartDeviceAuthorization(WatchSyncStartDeviceAuthorizationRequest) returns (WatchSyncStartDeviceAuthorizationResponse); - rpc PollDeviceAuthorization(WatchSyncPollDeviceAuthorizationRequest) returns (WatchSyncPollDeviceAuthorizationResponse); rpc RefreshCredentials(WatchSyncRefreshCredentialsRequest) returns (WatchSyncCredentialResponse); rpc GetAccount(WatchSyncGetAccountRequest) returns (WatchSyncGetAccountResponse); rpc ApplyEvents(WatchSyncApplyEventsRequest) returns (WatchSyncApplyEventsResponse); rpc ListRemoteState(WatchSyncListRemoteStateRequest) returns (WatchSyncListRemoteStateResponse); } +// WatchSyncDeviceAuthorizationService is separate from WatchSyncProvider so adding +// device authorization does not add methods to the released v0.12 Go server +// interface. A plugin advertising DEVICE_CODE registers both services. +service WatchSyncDeviceAuthorizationService { + rpc Start(WatchSyncDeviceAuthorizationServiceStartRequest) returns (WatchSyncDeviceAuthorizationServiceStartResponse); + rpc Poll(WatchSyncDeviceAuthorizationServicePollRequest) returns (WatchSyncDeviceAuthorizationServicePollResponse); +} + enum WatchSyncAuthMethod { WATCH_SYNC_AUTH_METHOD_UNSPECIFIED = 0; WATCH_SYNC_AUTH_METHOD_AUTHORIZATION_CODE = 1; @@ -192,12 +198,12 @@ message WatchSyncExchangeAPIKeyRequest { string api_key = 3; } -message WatchSyncStartDeviceAuthorizationRequest { +message WatchSyncDeviceAuthorizationServiceStartRequest { string capability_id = 1; WatchSyncProviderConfig provider_config = 2; } -message WatchSyncStartDeviceAuthorizationResponse { +message WatchSyncDeviceAuthorizationServiceStartResponse { string user_code = 1; string verification_url = 2; string verification_url_complete = 3; @@ -209,13 +215,13 @@ message WatchSyncStartDeviceAuthorizationResponse { WatchSyncFault fault = 7; } -message WatchSyncPollDeviceAuthorizationRequest { +message WatchSyncDeviceAuthorizationServicePollRequest { string capability_id = 1; WatchSyncProviderConfig provider_config = 2; bytes provider_state = 3; } -message WatchSyncPollDeviceAuthorizationResponse { +message WatchSyncDeviceAuthorizationServicePollResponse { WatchSyncDeviceAuthorizationStatus status = 1; // Complete authoritative replacement when status is AUTHORIZED. The host // persists it before consuming account or fault. @@ -224,6 +230,14 @@ message WatchSyncPollDeviceAuthorizationResponse { // PENDING normally omits fault. DENIED and EXPIRED may use a safe, // non-retryable fault. Connection-wide and transport failures use fault. WatchSyncFault fault = 4; + // Optional authoritative replacement for provider_state while PENDING. The + // host encrypts and uses it for the next poll. + bytes provider_state = 5; + // Optional replacement polling interval while PENDING, for example after an + // OAuth device-flow slow_down response. + google.protobuf.Duration polling_interval = 6; + // Optional replacement expiry for a rotated provider challenge. + google.protobuf.Timestamp expires_at = 7; } message WatchSyncRefreshCredentialsRequest { @@ -282,7 +296,7 @@ message WatchSyncEvent { int32 play_count = 11; // Optional zero-based position for list providers that accept explicit // ordering. Providers may ignore it unless their API supports order writes. - int32 list_position = 12; + optional int32 list_position = 12; // Provider-native key previously returned by ListRemoteState. It lets a // removal address the exact upstream record when external IDs are absent or // no longer resolve. @@ -351,6 +365,9 @@ message WatchSyncRemoteProgressState { message WatchSyncRemoteListState { // Provider timestamp for when the item was added, when available. google.protobuf.Timestamp listed_at = 1; + // True is an explicit tombstone. It is meaningful for incremental + // traversals, where omitting an item is not a deletion. + bool removed = 2; } message WatchSyncRemoteState { From c33f66caa5a900f7e076cd32d20651dbdae1924d Mon Sep 17 00:00:00 2001 From: Quick104 <31828688+Quick104@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:56:41 -0400 Subject: [PATCH 3/5] docs(watchsync): define list tombstones --- README.md | 5 +++-- .../silo/plugin/v1/watch_sync_provider.pb.go | 4 +++- .../plugin/v1/watch_sync_provider_grpc.pb.go | 4 ++-- .../silo/plugin/v1/watch_sync_provider_test.go | 18 ++++++++++++++++++ proto/silo/plugin/v1/watch_sync_provider.proto | 2 ++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 37d16ba..e98cff6 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,9 @@ sync phase needs, keeps that phase's `cursor` fixed while following ephemeral page tokens, commits each successful page, and only then persists the final `next_cursor`. `complete_snapshot=true` means the traversal is authoritative; when false, missing items are not deletions. An incremental favorite or -watchlist removal is an item whose corresponding list state has `removed=true`. -When +watchlist removal is an item whose corresponding list state has `removed=true`; +it may omit `media` when `provider_item_key` identifies a record previously +returned to the host. When `provides_watchlist_order=true`, the order of returned watchlist states is the remote list order. Event `list_position` is presence-aware: an explicit zero means the first position, while omission means no requested ordering. diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go index b18efdf..e6d741a 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go @@ -2501,7 +2501,9 @@ func (x *WatchSyncRemoteListState) GetRemoved() bool { type WatchSyncRemoteState struct { state protoimpl.MessageState `protogen:"open.v1"` ProviderItemKey string `protobuf:"bytes,1,opt,name=provider_item_key,json=providerItemKey,proto3" json:"provider_item_key,omitempty"` - Media *WatchSyncMedia `protobuf:"bytes,2,opt,name=media,proto3" json:"media,omitempty"` + // Required except on an explicit favorite/watchlist tombstone whose + // provider_item_key identifies a record returned previously. + Media *WatchSyncMedia `protobuf:"bytes,2,opt,name=media,proto3" json:"media,omitempty"` // At least one typed state must be present. Both may be present when the // provider reports a completed play and a separate resume point. Watched *WatchSyncRemoteWatchedState `protobuf:"bytes,3,opt,name=watched,proto3" json:"watched,omitempty"` diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go index 1219820..c5fbd31 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_grpc.pb.go @@ -365,7 +365,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // -// WatchSyncDeviceAuthorization is separate from WatchSyncProvider so adding +// WatchSyncDeviceAuthorizationService is separate from WatchSyncProvider so adding // device authorization does not add methods to the released v0.12 Go server // interface. A plugin advertising DEVICE_CODE registers both services. type WatchSyncDeviceAuthorizationServiceClient interface { @@ -405,7 +405,7 @@ func (c *watchSyncDeviceAuthorizationServiceClient) Poll(ctx context.Context, in // All implementations should embed UnimplementedWatchSyncDeviceAuthorizationServiceServer // for forward compatibility. // -// WatchSyncDeviceAuthorization is separate from WatchSyncProvider so adding +// WatchSyncDeviceAuthorizationService is separate from WatchSyncProvider so adding // device authorization does not add methods to the released v0.12 Go server // interface. A plugin advertising DEVICE_CODE registers both services. type WatchSyncDeviceAuthorizationServiceServer interface { diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go index 7c6b675..6e053fd 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go @@ -121,6 +121,24 @@ func TestWatchSyncListPositionPreservesPresence(t *testing.T) { } } +func TestWatchSyncListTombstoneDoesNotRequireMedia(t *testing.T) { + input := &WatchSyncRemoteState{ + ProviderItemKey: "remote-1", + Favorite: &WatchSyncRemoteListState{Removed: true}, + } + data, err := proto.Marshal(input) + if err != nil { + t.Fatal(err) + } + var output WatchSyncRemoteState + if err := proto.Unmarshal(data, &output); err != nil { + t.Fatal(err) + } + if !proto.Equal(input, &output) || output.GetMedia() != nil || !output.GetFavorite().GetRemoved() { + t.Fatalf("list tombstone = %#v", &output) + } +} + func TestWatchSyncApplyResultCarriesTypedRateLimit(t *testing.T) { retryAfter := 45 * time.Second request := &WatchSyncApplyEventsRequest{ diff --git a/proto/silo/plugin/v1/watch_sync_provider.proto b/proto/silo/plugin/v1/watch_sync_provider.proto index 7fd1414..aed5918 100644 --- a/proto/silo/plugin/v1/watch_sync_provider.proto +++ b/proto/silo/plugin/v1/watch_sync_provider.proto @@ -372,6 +372,8 @@ message WatchSyncRemoteListState { message WatchSyncRemoteState { string provider_item_key = 1; + // Required except on an explicit favorite/watchlist tombstone whose + // provider_item_key identifies a record returned previously. WatchSyncMedia media = 2; // At least one typed state must be present. Both may be present when the // provider reports a completed play and a separate resume point. From d4b0770d73428f0079c567daea362b3b893779aa Mon Sep 17 00:00:00 2001 From: Quick104 <31828688+Quick104@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:08:51 -0400 Subject: [PATCH 4/5] docs(watchsync): require snapshots for ordered lists --- README.md | 7 ++++--- .../silo/plugin/v1/watch_sync_provider.pb.go | 6 +++++- .../silo/plugin/v1/watch_sync_provider_test.go | 10 +++++++++- proto/silo/plugin/v1/watch_sync_provider.proto | 6 +++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e98cff6..2314048 100644 --- a/README.md +++ b/README.md @@ -176,9 +176,10 @@ when false, missing items are not deletions. An incremental favorite or watchlist removal is an item whose corresponding list state has `removed=true`; it may omit `media` when `provider_item_key` identifies a record previously returned to the host. When -`provides_watchlist_order=true`, the order of returned watchlist states is the -remote list order. Event `list_position` is presence-aware: an explicit zero -means the first position, while omission means no requested ordering. +`provides_watchlist_order=true`, watchlist traversals must be complete snapshots +and the order of returned watchlist states is the remote list order. Event +`list_position` is presence-aware: an explicit zero means the first position, +while omission means no requested ordering. ## Scan sources diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go index e6d741a..f18b0b8 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go @@ -490,6 +490,8 @@ type WatchSyncProviderDescriptor struct { ExportWatchlist bool `protobuf:"varint,13,opt,name=export_watchlist,json=exportWatchlist,proto3" json:"export_watchlist,omitempty"` RemoveWatchlist bool `protobuf:"varint,14,opt,name=remove_watchlist,json=removeWatchlist,proto3" json:"remove_watchlist,omitempty"` // The order of WATCHLIST states returned by ListRemoteState is meaningful. + // WATCHLIST traversals must set complete_snapshot=true when this is enabled; + // an incremental subset cannot define positions relative to omitted items. ProvidesWatchlistOrder bool `protobuf:"varint,15,opt,name=provides_watchlist_order,json=providesWatchlistOrder,proto3" json:"provides_watchlist_order,omitempty"` ScrobblePlayback bool `protobuf:"varint,16,opt,name=scrobble_playback,json=scrobblePlayback,proto3" json:"scrobble_playback,omitempty"` unknownFields protoimpl.UnknownFields @@ -2597,7 +2599,9 @@ type WatchSyncListRemoteStateResponse struct { NextCursor string `protobuf:"bytes,3,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` // True when this traversal is an authoritative full snapshot. False means an // incremental delta, so an absent item must not be interpreted as deletion. - // The value must remain stable across every page in one traversal. + // The value must remain stable across every page in one traversal. A provider + // advertising provides_watchlist_order must set this true for WATCHLIST + // traversals so the returned order is unambiguous. CompleteSnapshot bool `protobuf:"varint,4,opt,name=complete_snapshot,json=completeSnapshot,proto3" json:"complete_snapshot,omitempty"` // Complete authoritative replacement. The host persists it before consuming // this page or fault; a persistence failure discards the page and cursor. diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go index 6e053fd..a939440 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go @@ -116,7 +116,15 @@ func TestWatchSyncListPositionPreservesPresence(t *testing.T) { if output.ListPosition == nil || output.GetListPosition() != 0 { t.Fatalf("explicit zero list position = %#v", output.ListPosition) } - if (&WatchSyncEvent{}).ListPosition != nil { + omittedData, err := proto.Marshal(&WatchSyncEvent{}) + if err != nil { + t.Fatal(err) + } + var omittedOutput WatchSyncEvent + if err := proto.Unmarshal(omittedData, &omittedOutput); err != nil { + t.Fatal(err) + } + if omittedOutput.ListPosition != nil { t.Fatal("omitted list position unexpectedly has presence") } } diff --git a/proto/silo/plugin/v1/watch_sync_provider.proto b/proto/silo/plugin/v1/watch_sync_provider.proto index aed5918..b0a4bc5 100644 --- a/proto/silo/plugin/v1/watch_sync_provider.proto +++ b/proto/silo/plugin/v1/watch_sync_provider.proto @@ -113,6 +113,8 @@ message WatchSyncProviderDescriptor { bool export_watchlist = 13; bool remove_watchlist = 14; // The order of WATCHLIST states returned by ListRemoteState is meaningful. + // WATCHLIST traversals must set complete_snapshot=true when this is enabled; + // an incremental subset cannot define positions relative to omitted items. bool provides_watchlist_order = 15; bool scrobble_playback = 16; } @@ -393,7 +395,9 @@ message WatchSyncListRemoteStateResponse { string next_cursor = 3; // True when this traversal is an authoritative full snapshot. False means an // incremental delta, so an absent item must not be interpreted as deletion. - // The value must remain stable across every page in one traversal. + // The value must remain stable across every page in one traversal. A provider + // advertising provides_watchlist_order must set this true for WATCHLIST + // traversals so the returned order is unambiguous. bool complete_snapshot = 4; // Complete authoritative replacement. The host persists it before consuming // this page or fault; a persistence failure discards the page and cursor. From 206f05f5bc88ee0d4a37061dd974a2771c46dc6f Mon Sep 17 00:00:00 2001 From: Quick104 <31828688+Quick104@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:07:53 -0400 Subject: [PATCH 5/5] fix(watchsync): preserve device auth compatibility --- README.md | 16 +++++-- .../silo/plugin/v1/watch_sync_provider.pb.go | 22 +++++++--- .../plugin/v1/watch_sync_provider_test.go | 27 ++++++++++++ .../runtime/capability_servers_compat_test.go | 18 ++++++++ pkg/pluginsdk/runtime/runtime.go | 40 ++++++++++++++--- pkg/pluginsdk/runtime/serve_manifest.go | 43 ++++++++++++++++++- .../runtime/watch_sync_provider_test.go | 14 +++--- .../silo/plugin/v1/watch_sync_provider.proto | 12 ++++-- 8 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 pkg/pluginsdk/runtime/capability_servers_compat_test.go diff --git a/README.md b/README.md index 2314048..71d8db5 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,19 @@ fails, the host commits no other response data. Device-code plugins register both `WatchSyncProvider` and the separate `WatchSyncDeviceAuthorizationService`. Keeping device authorization in a second service preserves source compatibility for v0.12 Go providers that -implemented `WatchSyncProviderServer` directly. A pending poll may replace its -opaque provider state, polling interval, and expiry; the host encrypts and -persists those values before the next poll. +implemented `WatchSyncProviderServer` directly. Register it without changing +the released `CapabilityServers` shape: + +```go +runtime.ServeManifestWithOptions(manifestJSON, version, servers, + runtime.WithWatchSyncDeviceAuthorization(deviceAuthServer)) +``` + +A pending poll may replace its opaque provider state, polling interval, and +expiry; the host encrypts and persists those values before the next poll. +Those updates remain part of the same user challenge, so the original user code +and verification URL must stay valid until expiry. An explicitly empty +`provider_state` clears the prior state; omitting it retains the prior state. `WatchSyncProviderConfig` is keyed by manifest config key and field, for example `provider.client_id`. Scalar values are sent as strings and structured values diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go index f18b0b8..91bfbd2 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider.pb.go @@ -1472,13 +1472,19 @@ type WatchSyncDeviceAuthorizationServicePollResponse struct { // PENDING normally omits fault. DENIED and EXPIRED may use a safe, // non-retryable fault. Connection-wide and transport failures use fault. Fault *WatchSyncFault `protobuf:"bytes,4,opt,name=fault,proto3" json:"fault,omitempty"` + // A PENDING response may update host-private state and timing only for the + // same user challenge. The user_code and verification URLs returned by Start + // must remain valid through expires_at; if those instructions change, expire + // this flow and require a new Start call. + // // Optional authoritative replacement for provider_state while PENDING. The - // host encrypts and uses it for the next poll. - ProviderState []byte `protobuf:"bytes,5,opt,name=provider_state,json=providerState,proto3" json:"provider_state,omitempty"` + // host encrypts and uses it for the next poll. Presence is significant: an + // explicitly empty value clears the prior state, while omission retains it. + ProviderState []byte `protobuf:"bytes,5,opt,name=provider_state,json=providerState,proto3,oneof" json:"provider_state,omitempty"` // Optional replacement polling interval while PENDING, for example after an // OAuth device-flow slow_down response. PollingInterval *durationpb.Duration `protobuf:"bytes,6,opt,name=polling_interval,json=pollingInterval,proto3" json:"polling_interval,omitempty"` - // Optional replacement expiry for a rotated provider challenge. + // Optional replacement expiry for the same user challenge. ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -2781,16 +2787,17 @@ const file_silo_plugin_v1_watch_sync_provider_proto_rawDesc = "" + ".WatchSyncDeviceAuthorizationServicePollRequest\x12#\n" + "\rcapability_id\x18\x01 \x01(\tR\fcapabilityId\x12P\n" + "\x0fprovider_config\x18\x02 \x01(\v2'.silo.plugin.v1.WatchSyncProviderConfigR\x0eproviderConfig\x12%\n" + - "\x0eprovider_state\x18\x03 \x01(\fR\rproviderState\"\xdf\x03\n" + + "\x0eprovider_state\x18\x03 \x01(\fR\rproviderState\"\xf7\x03\n" + "/WatchSyncDeviceAuthorizationServicePollResponse\x12J\n" + "\x06status\x18\x01 \x01(\x0e22.silo.plugin.v1.WatchSyncDeviceAuthorizationStatusR\x06status\x12F\n" + "\vcredentials\x18\x02 \x01(\v2$.silo.plugin.v1.WatchSyncCredentialsR\vcredentials\x12:\n" + "\aaccount\x18\x03 \x01(\v2 .silo.plugin.v1.WatchSyncAccountR\aaccount\x124\n" + - "\x05fault\x18\x04 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\x12%\n" + - "\x0eprovider_state\x18\x05 \x01(\fR\rproviderState\x12D\n" + + "\x05fault\x18\x04 \x01(\v2\x1e.silo.plugin.v1.WatchSyncFaultR\x05fault\x12*\n" + + "\x0eprovider_state\x18\x05 \x01(\fH\x00R\rproviderState\x88\x01\x01\x12D\n" + "\x10polling_interval\x18\x06 \x01(\v2\x19.google.protobuf.DurationR\x0fpollingInterval\x129\n" + "\n" + - "expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"m\n" + + "expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAtB\x11\n" + + "\x0f_provider_state\"m\n" + "\"WatchSyncRefreshCredentialsRequest\x12G\n" + "\acontext\x18\x01 \x01(\v2-.silo.plugin.v1.WatchSyncAuthenticatedContextR\acontext\"\xd7\x01\n" + "\x1bWatchSyncCredentialResponse\x12F\n" + @@ -3105,6 +3112,7 @@ func file_silo_plugin_v1_watch_sync_provider_proto_init() { if File_silo_plugin_v1_watch_sync_provider_proto != nil { return } + file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[13].OneofWrappers = []any{} file_silo_plugin_v1_watch_sync_provider_proto_msgTypes[19].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go index a939440..51e4fba 100644 --- a/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go +++ b/pkg/pluginproto/silo/plugin/v1/watch_sync_provider_test.go @@ -102,6 +102,33 @@ func TestWatchSyncDeviceAuthorizationPendingStateRoundTrip(t *testing.T) { } } +func TestWatchSyncDeviceAuthorizationPendingStatePreservesPresence(t *testing.T) { + explicitEmpty := &WatchSyncDeviceAuthorizationServicePollResponse{ProviderState: []byte{}} + data, err := proto.Marshal(explicitEmpty) + if err != nil { + t.Fatal(err) + } + var output WatchSyncDeviceAuthorizationServicePollResponse + if err := proto.Unmarshal(data, &output); err != nil { + t.Fatal(err) + } + if output.ProviderState == nil || len(output.GetProviderState()) != 0 { + t.Fatalf("explicit empty provider state = %#v", output.ProviderState) + } + + data, err = proto.Marshal(&WatchSyncDeviceAuthorizationServicePollResponse{}) + if err != nil { + t.Fatal(err) + } + output.Reset() + if err := proto.Unmarshal(data, &output); err != nil { + t.Fatal(err) + } + if output.ProviderState != nil { + t.Fatalf("omitted provider state = %#v, want nil", output.ProviderState) + } +} + func TestWatchSyncListPositionPreservesPresence(t *testing.T) { zero := int32(0) withZero := &WatchSyncEvent{ListPosition: &zero} diff --git a/pkg/pluginsdk/runtime/capability_servers_compat_test.go b/pkg/pluginsdk/runtime/capability_servers_compat_test.go new file mode 100644 index 0000000..57d193f --- /dev/null +++ b/pkg/pluginsdk/runtime/capability_servers_compat_test.go @@ -0,0 +1,18 @@ +package runtime + +// Compile-time guard for the released v0.12 unkeyed composite literal shape. +// New optional servers belong behind additive registration APIs, not here. +var _ = CapabilityServers{ + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, +} diff --git a/pkg/pluginsdk/runtime/runtime.go b/pkg/pluginsdk/runtime/runtime.go index d80954f..09d18b8 100644 --- a/pkg/pluginsdk/runtime/runtime.go +++ b/pkg/pluginsdk/runtime/runtime.go @@ -35,9 +35,6 @@ type CapabilityServers struct { AuthProvider pluginv1.AuthProviderServer HttpRoutes pluginv1.HttpRoutesServer WatchSyncProvider pluginv1.WatchSyncProviderServer - // WatchSyncDeviceAuthorization is registered alongside WatchSyncProvider - // when the capability advertises device-code authentication. - WatchSyncDeviceAuthorization pluginv1.WatchSyncDeviceAuthorizationServiceServer } // Client wraps the gRPC connection to a plugin and provides typed accessors @@ -73,6 +70,24 @@ func DefaultPluginSet(servers CapabilityServers) plugin.PluginSet { } } +// DefaultPluginSetWithWatchSyncDeviceAuthorization returns the default plugin +// set plus the separate device-authorization service. Keeping that server out +// of CapabilityServers preserves the released v0.12 unkeyed struct shape. +func DefaultPluginSetWithWatchSyncDeviceAuthorization( + servers CapabilityServers, + deviceAuthorization pluginv1.WatchSyncDeviceAuthorizationServiceServer, +) plugin.PluginSet { + if deviceAuthorization == nil { + return DefaultPluginSet(servers) + } + return plugin.PluginSet{ + PluginSetName: &grpcPluginWithWatchSyncDeviceAuthorization{ + GRPCPlugin: &GRPCPlugin{Servers: servers}, + deviceAuthorization: deviceAuthorization, + }, + } +} + func NewClient(conn *grpc.ClientConn) *Client { return &Client{conn: conn} } @@ -145,6 +160,22 @@ type GRPCPlugin struct { Servers CapabilityServers } +type grpcPluginWithWatchSyncDeviceAuthorization struct { + *GRPCPlugin + deviceAuthorization pluginv1.WatchSyncDeviceAuthorizationServiceServer +} + +func (p *grpcPluginWithWatchSyncDeviceAuthorization) GRPCServer( + broker *plugin.GRPCBroker, + server *grpc.Server, +) error { + if err := p.GRPCPlugin.GRPCServer(broker, server); err != nil { + return err + } + pluginv1.RegisterWatchSyncDeviceAuthorizationServiceServer(server, p.deviceAuthorization) + return nil +} + func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc.Server) error { pluginHost.setBroker(broker) if p.Servers.Runtime == nil { @@ -185,9 +216,6 @@ func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc.Server) if p.Servers.WatchSyncProvider != nil { pluginv1.RegisterWatchSyncProviderServer(server, p.Servers.WatchSyncProvider) } - if p.Servers.WatchSyncDeviceAuthorization != nil { - pluginv1.RegisterWatchSyncDeviceAuthorizationServiceServer(server, p.Servers.WatchSyncDeviceAuthorization) - } return nil } diff --git a/pkg/pluginsdk/runtime/serve_manifest.go b/pkg/pluginsdk/runtime/serve_manifest.go index 4d4b6bb..8b7dbdc 100644 --- a/pkg/pluginsdk/runtime/serve_manifest.go +++ b/pkg/pluginsdk/runtime/serve_manifest.go @@ -19,6 +19,24 @@ type manifestRuntime struct { manifest *pluginv1.PluginManifest } +type serveManifestOptions struct { + watchSyncDeviceAuthorization pluginv1.WatchSyncDeviceAuthorizationServiceServer +} + +// ServeManifestOption adds a service without changing the released +// CapabilityServers or ServeConfig struct layouts. +type ServeManifestOption func(*serveManifestOptions) + +// WithWatchSyncDeviceAuthorization registers the separate device-code service +// for a watch-sync provider that advertises DEVICE_CODE authentication. +func WithWatchSyncDeviceAuthorization( + server pluginv1.WatchSyncDeviceAuthorizationServiceServer, +) ServeManifestOption { + return func(options *serveManifestOptions) { + options.watchSyncDeviceAuthorization = server + } +} + func (s *manifestRuntime) GetManifest(context.Context, *pluginv1.GetManifestRequest) (*pluginv1.GetManifestResponse, error) { return &pluginv1.GetManifestResponse{Manifest: s.manifest}, nil } @@ -37,10 +55,33 @@ func (s *manifestRuntime) BindHostBroker(_ context.Context, req *pluginv1.BindHo // servers (the caller supplies only the non-Runtime servers). It never returns; // a fatal manifest error panics, matching a misbuilt plugin's old main(). func ServeManifest(manifestBytes []byte, version string, servers CapabilityServers) { + ServeManifestWithOptions(manifestBytes, version, servers) +} + +// ServeManifestWithOptions is the additive form of ServeManifest for services +// introduced after the released CapabilityServers shape. +func ServeManifestWithOptions( + manifestBytes []byte, + version string, + servers CapabilityServers, + options ...ServeManifestOption, +) { m, err := manifest.LoadWithChecksum(manifestBytes, version) if err != nil { panic(err) } servers.Runtime = &manifestRuntime{manifest: m} - Serve(ServeConfig{Servers: servers}) + var resolved serveManifestOptions + for _, option := range options { + if option != nil { + option(&resolved) + } + } + Serve(ServeConfig{ + Servers: servers, + Plugins: DefaultPluginSetWithWatchSyncDeviceAuthorization( + servers, + resolved.watchSyncDeviceAuthorization, + ), + }) } diff --git a/pkg/pluginsdk/runtime/watch_sync_provider_test.go b/pkg/pluginsdk/runtime/watch_sync_provider_test.go index 95a8f83..d96ba34 100644 --- a/pkg/pluginsdk/runtime/watch_sync_provider_test.go +++ b/pkg/pluginsdk/runtime/watch_sync_provider_test.go @@ -5,6 +5,7 @@ import ( pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" runtime "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/runtime" + "github.com/hashicorp/go-plugin" "google.golang.org/grpc" ) @@ -17,11 +18,14 @@ type stubWatchSyncDeviceAuthorization struct { } func TestGRPCServerRegistersWatchSyncServices(t *testing.T) { - p := &runtime.GRPCPlugin{Servers: runtime.CapabilityServers{ - Runtime: stubRuntime{}, - WatchSyncProvider: stubWatchSyncProvider{}, - WatchSyncDeviceAuthorization: stubWatchSyncDeviceAuthorization{}, - }} + plugins := runtime.DefaultPluginSetWithWatchSyncDeviceAuthorization(runtime.CapabilityServers{ + Runtime: stubRuntime{}, + WatchSyncProvider: stubWatchSyncProvider{}, + }, stubWatchSyncDeviceAuthorization{}) + p, ok := plugins[runtime.PluginSetName].(plugin.GRPCPlugin) + if !ok { + t.Fatalf("watch-sync plugin type = %T, want plugin.GRPCPlugin", plugins[runtime.PluginSetName]) + } srv := grpc.NewServer() if err := p.GRPCServer(nil, srv); err != nil { t.Fatalf("GRPCServer with watch-sync services = %v, want nil", err) diff --git a/proto/silo/plugin/v1/watch_sync_provider.proto b/proto/silo/plugin/v1/watch_sync_provider.proto index b0a4bc5..c76b717 100644 --- a/proto/silo/plugin/v1/watch_sync_provider.proto +++ b/proto/silo/plugin/v1/watch_sync_provider.proto @@ -232,13 +232,19 @@ message WatchSyncDeviceAuthorizationServicePollResponse { // PENDING normally omits fault. DENIED and EXPIRED may use a safe, // non-retryable fault. Connection-wide and transport failures use fault. WatchSyncFault fault = 4; + // A PENDING response may update host-private state and timing only for the + // same user challenge. The user_code and verification URLs returned by Start + // must remain valid through expires_at; if those instructions change, expire + // this flow and require a new Start call. + // // Optional authoritative replacement for provider_state while PENDING. The - // host encrypts and uses it for the next poll. - bytes provider_state = 5; + // host encrypts and uses it for the next poll. Presence is significant: an + // explicitly empty value clears the prior state, while omission retains it. + optional bytes provider_state = 5; // Optional replacement polling interval while PENDING, for example after an // OAuth device-flow slow_down response. google.protobuf.Duration polling_interval = 6; - // Optional replacement expiry for a rotated provider challenge. + // Optional replacement expiry for the same user challenge. google.protobuf.Timestamp expires_at = 7; }