-
-
Notifications
You must be signed in to change notification settings - Fork 121
Expand file tree
/
Copy pathevents.go
More file actions
735 lines (643 loc) · 30.9 KB
/
Copy pathevents.go
File metadata and controls
735 lines (643 loc) · 30.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
package centrifuge
import (
"context"
"time"
"github.com/centrifugal/protocol"
)
// ConnectEvent contains fields related to connecting event (when a server
// received Connect protocol command from client).
type ConnectEvent struct {
// ClientID that was generated by library for client connection.
ClientID string
// Token received from client as part of Connect Command.
Token string
// Data received from client as part of Connect Command.
Data []byte
// Name can contain client name if provided on connect.
Name string
// Version can contain client version if provided on connect.
Version string
// Profile can contain the profile name the client declared on connect.
// This is an unvalidated client assertion, exactly like Name and Version:
// treat it as a request, not a fact. Return ConnectReply.Profile to set the
// profile the server will actually use - whatever is returned there wins,
// and returning an empty one discards the client's claim entirely.
Profile string
// Transport contains information about transport used by client.
Transport TransportInfo
// Channels is a list of channels a client wants to subscribe to
// (server-side). It's just a way for a client to provide this list.
// Server should use ConnectReply.Subscriptions to tell Centrifuge
// the final list of server-side subscriptions for a connection which
// can differ from the Channels list.
Channels []string
// Headers can be set by SDK to pass custom key-value headers from
// client to server. It's up to the application to decide what to do
// with these headers.
Headers map[string]string
}
// ConnectReply contains reaction to ConnectEvent.
type ConnectReply struct {
// Context allows returning a modified context.
Context context.Context
// Credentials should be set if app wants to authenticate connection.
// This field is optional since auth Credentials could be set through
// HTTP middleware.
Credentials *Credentials
// Data allows setting custom data in connect reply.
Data []byte
// Subscriptions map contains channels to subscribe connection to on server-side.
Subscriptions map[string]SubscribeOptions
// ClientSideRefresh tells library to use client-side refresh logic:
// i.e. send refresh commands with new connection token. If not set
// then server-side refresh mechanism will be used.
ClientSideRefresh bool
// Storage can be used to fill initial connection storage during connecting.
// This data may be then accessed/modified/replaced later during Client's lifetime
// over Client.AcquireStorage() call. This API is EXPERIMENTAL.
Storage map[string]any
// Profile is the application context this connection belongs to - which view,
// screen or client kind it is. Connections sharing a profile see traffic of a
// similar shape.
//
// Setting it here overrides whatever the client declared in its connect
// command, and is the recommended way to classify connections: a claim in the
// token this handler already reads is one change to the service issuing
// tokens, rather than a release of every client.
//
// It is carried on the Client and can be read with Client.Profile(). Nothing
// in this package interprets it; it exists so features layered on top - the
// obvious one being compression dictionaries - can group connections that
// look alike.
Profile string
// MaxMessagesInFrame is the maximum number of messages (replies and pushes) which
// Centrifuge Client message writer will collect from the client's queue before sending
// to the connection. By default, it's 16. Use -1 to disable the limit.
MaxMessagesInFrame int
// WriteDelay is a time Centrifuge will try to collect messages inside message writer loop
// before sending them towards this connection. Enabling WriteDelay may reduce CPU usage of
// both server and client in case of high message rate inside individual connections. The
// reduction happens due to the lesser number of system calls to execute. Enabling WriteDelay
// limits the maximum throughput of messages towards the connection which may be achieved.
// For example, if WriteDelay is 100ms then the max throughput per second will be
// (1000 / 100) * MaxMessagesInFrame (16 by default), i.e. 160 messages per second. This
// should be more than enough for target Centrifuge use cases (frontend apps) though.
WriteDelay time.Duration
// WriteWithTimer enables using write timer for this client connection. This mode is only
// active when WriteDelay is set to a non-zero value. In this mode Centrifuge uses a timer
// to schedule batched writes to the connection instead of checking for messages in a
// dedicated goroutine loop.
WriteWithTimer bool
// ReplyWithoutQueue when enabled will force Centrifuge to avoid using Client write
// queue for sending replies to commands for this connection. Replies sent directly to
// the Client's transport thus avoiding possible delays caused by writer loop, but replies
// lose a chance to be batched.
ReplyWithoutQueue bool
// QueueInitialCap set an initial capacity for client's message queue, the size of queue
// can grow further, but won't be reduced below QueueInitialCap. By default, it's 2.
QueueInitialCap int
// QueueShrinkDelay is a time Centrifuge will wait, after the client's message
// queue has been drained, before shrinking it back down. This option only
// applies when WriteDelay is set to a non-zero value.
//
// It matters because shrinking is evaluated immediately after a drain, when
// the queue is empty by construction. Acting on that would discard the ring
// the writer had just filled, and the next frame would rebuild it one
// doubling at a time - allocating on every write cycle for the lifetime of
// the connection. Waiting instead lets the queue settle: under sustained load
// the timer keeps resetting and capacity is simply retained, and once the
// connection goes quiet the timer fires and the memory is released in full.
//
// Zero value (the default) means Centrifuge picks a sensible delay itself
// (currently one second). Pass a negative value to shrink immediately after
// every drain - available for completeness, but it reintroduces the per-cycle
// allocations described above and is not recommended.
//
// Broadcasting to 200 subscribers with WriteDelay set, per publication:
//
// shrink immediately 19.1us 84 allocs 34 KB
// default delay 12.7us 2 allocs 376 B
//
// A longer delay holds capacity through longer quiet gaps, which suits
// connections that burst repeatedly; a shorter one returns memory sooner.
QueueShrinkDelay time.Duration
// PingPongConfig if set, will override Transport's PingPongConfig to enable setting ping/pong interval
// for individual client.
PingPongConfig *PingPongConfig
// Labels is a map of custom label key-value pairs that will be attached to the client connection.
// Labels can be used for any application purpose. Additionally, labels defined in MetricsConfig.ClientLabels
// will be exported to Prometheus metrics for all client metrics.
// For exported metric labels: if a defined label is not found in this map, an empty string will be used.
Labels map[string]string
}
// ConnectingHandler called when new client authenticates on server.
type ConnectingHandler func(context.Context, ConnectEvent) (ConnectReply, error)
// ConnectHandler called when client connected to server and ready to communicate.
type ConnectHandler func(*Client)
// RefreshEvent contains fields related to refresh event.
type RefreshEvent struct {
// ClientSideRefresh is true for refresh initiated by client-side refresh workflow.
ClientSideRefresh bool
// Token will only be set in case of using client-side refresh mechanism.
Token string
}
// RefreshReply contains fields determining the reaction on refresh event.
type RefreshReply struct {
// Expired tells Centrifuge that connection expired. In this case connection will be
// closed with DisconnectExpired.
Expired bool
// ExpireAt defines time in future when connection should expire,
// zero value means no expiration.
ExpireAt int64
// Info allows modifying connection information,
// zero value means no modification of current connection Info.
Info []byte
}
// RefreshCallback should be called as soon as handler decides what to do
// with connection refresh event.
type RefreshCallback func(RefreshReply, error)
// RefreshHandler called when it's time to validate client connection and
// update its expiration time if it's still actual.
//
// Centrifuge library supports two ways of refreshing connection: client-side
// and server-side.
//
// The default mechanism is server-side, this means that as soon refresh handler
// set and connection expiration time happens (by timer) – refresh handler will
// be called.
//
// If ClientSideRefresh in ConnectReply inside ConnectingHandler set to true then
// library uses client-side refresh mechanism. In this case library relies on
// Refresh commands sent from client periodically to refresh connection. Refresh
// command contains updated connection token.
type RefreshHandler func(RefreshEvent, RefreshCallback)
// AliveHandler called periodically while connection alive. This is a helper
// to do periodic things which can tolerate some approximation in time. This
// callback will run every ClientPresenceUpdateInterval and can save you a timer.
type AliveHandler func()
// UnsubscribeEvent contains fields related to unsubscribe event.
type UnsubscribeEvent struct {
// Channel client unsubscribed from.
Channel string
// ServerSide set to true for server-side subscription unsubscribe events.
ServerSide bool
// Unsubscribe identifies the source of unsubscribe (i.e. why unsubscribed event happened).
Unsubscribe
// Disconnect can be additionally set to identify the reason of disconnect when Unsubscribe.Code
// is UnsubscribeCodeDisconnect - i.e. when unsubscribe caused by a client disconnection process.
// Otherwise, it's nil.
Disconnect *Disconnect
}
// UnsubscribeHandler called when client unsubscribed from channel.
type UnsubscribeHandler func(UnsubscribeEvent)
// DisconnectEvent contains fields related to disconnect event.
type DisconnectEvent struct {
// Disconnect contains a Disconnect object which identifies the code and reason
// of disconnect process. When disconnect was not initiated by a server this
// is always DisconnectConnectionClosed.
Disconnect
}
// DisconnectHandler called when client disconnects from server. The important
// thing to remember is that you should not rely entirely on this handler to
// clean up non-expiring resources (in your database for example). Why? Because
// in case of any non-graceful node shutdown (kill -9, process crash, machine lost)
// disconnect handler will never be called (obviously) so you can have stale data.
type DisconnectHandler func(DisconnectEvent)
// SubscribeEvent contains fields related to subscribe event.
type SubscribeEvent struct {
// Channel client wants to subscribe to.
Channel string
// Token will only be set for token channels. This is a task of application
// to check that subscription to a channel has valid token.
Token string
// Data received from client as part of Subscribe Command.
Data []byte
// Positioned is true when Client wants to create subscription with positioned property.
Positioned bool
// Recoverable is true when Client wants to create subscription with recoverable property.
Recoverable bool
// JoinLeave is true when Client wants to receive join/leave messages.
JoinLeave bool
// Type is the subscription type requested by the client (map, presence, etc.).
// For regular subscriptions this is SubscriptionTypeStream (zero value).
Type SubscriptionType
}
// SubscribeCallback should be called as soon as handler decides what to do
// with connection subscribe event.
type SubscribeCallback func(SubscribeReply, error)
// SubscribeReply contains fields determining the reaction on subscribe event.
type SubscribeReply struct {
// Options to control subscription.
Options SubscribeOptions
// ClientSideRefresh tells library to use client-side refresh logic: i.e. send
// SubRefresh commands with new Subscription Token. If not set then server-side
// SubRefresh handler will be used.
ClientSideRefresh bool
// Publications to include in the subscribe result. These are delivered to the
// client as publication events before live publications begin — same mechanism
// as recovery publications. Useful for delivering initial state on subscribe.
Publications []*Publication
// SubscriptionReady channel if provided will be closed as soon as Centrifuge
// written subscribe reply to the connection, so it's possible to start writing
// publications into a channel using experimental Client.WritePublication method.
// In usual flow you don't need to provide this channel at all.
// This is EXPERIMENTAL and may be removed in the future.
SubscriptionReady chan struct{}
}
// SubscribeHandler called when client wants to subscribe on channel.
type SubscribeHandler func(SubscribeEvent, SubscribeCallback)
// PublishEvent contains fields related to publish event. Note that this event
// called before actual publish to Broker so handler has an option to reject this
// publication returning an error.
type PublishEvent struct {
// Channel client wants to publish data to.
Channel string
// Data client wants to publish.
Data []byte
// ClientInfo about client connection.
ClientInfo *ClientInfo
}
// PublishReply contains fields determining the result on publish.
type PublishReply struct {
// Options to control publication.
Options PublishOptions
// Result if set will tell Centrifuge that message already published to
// channel by handler code. In this case Centrifuge won't try to publish
// into channel again after handler returned PublishReply. This can be
// useful if you need to know new Publication offset in your code, or you
// want to make sure message successfully published to Broker on server
// side (otherwise only client will get an error).
Result *PublishResult
}
// PublishCallback should be called with PublishReply or error.
type PublishCallback func(PublishReply, error)
// PublishHandler called when client publishes into channel.
type PublishHandler func(PublishEvent, PublishCallback)
// MapPublishEvent contains fields related to map publish event.
type MapPublishEvent struct {
// Channel client wants to publish data to.
Channel string
// Key sent by the client (may be empty if server assigns keys).
Key string
// Data client wants to publish.
Data []byte
// ClientInfo about client connection.
ClientInfo *ClientInfo
}
// MapPublishReply contains fields determining the result on map publish.
type MapPublishReply struct {
// Key overrides the key from the client request. If set, this key is used
// instead of the one sent by the client. This allows the server to control
// key assignment (e.g., set key to client ID for cursor channels).
Key string
// Options to control map publication.
Options MapPublishOptions
// Result if set will tell Centrifuge that message already published to
// channel by handler code. In this case Centrifuge won't try to publish
// into channel again after handler returned MapPublishReply.
Result *MapUpdateResult
}
// MapPublishCallback should be called with MapPublishReply or error.
type MapPublishCallback func(MapPublishReply, error)
// MapPublishHandler called when client publishes into map channel.
type MapPublishHandler func(MapPublishEvent, MapPublishCallback)
// MapRemoveEvent contains fields related to map remove event.
type MapRemoveEvent struct {
// Channel client wants to remove a key from.
Channel string
// Key sent by the client (may be empty if server assigns keys).
Key string
// ClientInfo about client connection.
ClientInfo *ClientInfo
}
// MapRemoveReply contains fields determining the result on map remove.
type MapRemoveReply struct {
// Key overrides the key from the client request.
Key string
// Options to control map removal.
Options MapRemoveOptions
// Result if set will tell Centrifuge that removal already performed
// by handler code. In this case Centrifuge won't try to remove again.
Result *MapUpdateResult
}
// MapRemoveCallback should be called with MapRemoveReply or error.
type MapRemoveCallback func(MapRemoveReply, error)
// MapRemoveHandler called when client wants to remove a key from map channel.
type MapRemoveHandler func(MapRemoveEvent, MapRemoveCallback)
// SubRefreshEvent contains fields related to subscription refresh event.
type SubRefreshEvent struct {
// ClientSideRefresh is true for refresh initiated by client-side subscription
// refresh workflow.
ClientSideRefresh bool
// Channel to which SubRefreshEvent belongs to.
Channel string
// Token will only be set in case of using client-side subscription refresh mechanism.
Token string
}
// SubRefreshReply contains fields determining the reaction on
// subscription refresh event.
type SubRefreshReply struct {
// Expired tells Centrifuge that subscription expired. In this case connection will be
// closed with DisconnectExpired.
Expired bool
// ExpireAt is a new Unix time of expiration. Zero value means no expiration.
ExpireAt int64
// Info is a new channel-scope info. Zero value means do not change previous one.
Info []byte
// ServerTagsFilter is an optional updated server-side tags filter. When nil,
// the existing filter is left unchanged. When set, Centrifuge compares it
// with the current filter — if different, the filter is updated in the hub
// and for map subscriptions the client is unsubscribed with
// UnsubscribeCodeStateInvalidated to force a full state re-sync.
ServerTagsFilter *FilterNode
}
// SubRefreshCallback should be called as soon as handler decides what to do
// with connection SubRefreshEvent.
type SubRefreshCallback func(SubRefreshReply, error)
// SubRefreshHandler called when it's time to validate client subscription to channel and
// update it's state if needed.
//
// If ClientSideRefresh in SubscribeReply inside SubscribeHandler set to true then
// library uses client-side subscription refresh mechanism. In this case library relies on
// SubRefresh commands sent from client periodically to refresh subscription. SubRefresh
// command contains updated subscription token.
type SubRefreshHandler func(SubRefreshEvent, SubRefreshCallback)
// RPCEvent contains fields related to rpc request.
type RPCEvent struct {
// Method is an optional string that contains RPC method name client wants to call.
// This is an optional field, by default clients send RPC without any method set.
Method string
// Data contains RPC untouched payload.
Data []byte
}
// RPCReply contains fields determining the reaction on rpc request.
type RPCReply struct {
// Data to return in RPC reply to client.
Data []byte
}
// RPCCallback should be called as soon as handler decides what to do
// with connection RPCEvent.
type RPCCallback func(RPCReply, error)
// RPCHandler must handle incoming command from client.
type RPCHandler func(RPCEvent, RPCCallback)
// MessageEvent contains fields related to message request.
type MessageEvent struct {
// Data contains message untouched payload.
Data []byte
}
// MessageHandler must handle incoming async message from client. So Centrifuge
// feels similar to pure WebSocket API. Though in general, we recommend using RPC
// where possible to send data to a server from a client as it provides a better
// flow control.
type MessageHandler func(MessageEvent)
// PresenceEvent has channel operation called for.
type PresenceEvent struct {
Channel string
}
// PresenceReply contains fields determining the reaction on presence request.
type PresenceReply struct {
Result *PresenceResult
}
// PresenceCallback should be called with PresenceReply or error.
type PresenceCallback func(PresenceReply, error)
// PresenceHandler called when presence request received from client.
type PresenceHandler func(PresenceEvent, PresenceCallback)
// PresenceStatsEvent has channel operation called for.
type PresenceStatsEvent struct {
Channel string
}
// PresenceStatsReply contains fields determining the reaction on presence request.
type PresenceStatsReply struct {
Result *PresenceStatsResult
}
// PresenceStatsCallback should be called with PresenceStatsReply or error.
type PresenceStatsCallback func(PresenceStatsReply, error)
// PresenceStatsHandler must handle incoming command from client.
type PresenceStatsHandler func(PresenceStatsEvent, PresenceStatsCallback)
// HistoryEvent has channel operation called for.
type HistoryEvent struct {
Channel string
Filter HistoryFilter
}
// HistoryReply contains fields determining the reaction on history request.
type HistoryReply struct {
Result *HistoryResult
}
// HistoryCallback should be called with HistoryReply or error.
type HistoryCallback func(HistoryReply, error)
// HistoryHandler must handle incoming command from client.
type HistoryHandler func(HistoryEvent, HistoryCallback)
// StateSnapshotHandler must return a copy of current client's
// internal state. Returning a copy is important to avoid data races.
type StateSnapshotHandler func() (any, error)
// CacheEmptyEvent is issued when recovery mode is used but Centrifuge can't
// find Publication in history to recover from. This event allows application
// to decide what to do in this case – it's possible to populate the cache by
// sending actual data to a channel.
type CacheEmptyEvent struct {
Channel string
}
// CacheEmptyReply contains fields determining the reaction on cache empty event.
type CacheEmptyReply struct {
// Populated when set to true tells Centrifuge that cache was populated and
// in that case Centrifuge will try to recover missed Publication from history
// one more time.
Populated bool
}
// CacheEmptyHandler allows setting cache empty handler function.
type CacheEmptyHandler func(CacheEmptyEvent) (CacheEmptyReply, error)
// SurveyEvent with Op and Data of survey.
type SurveyEvent struct {
Op string
Data []byte
}
// SurveyReply contains survey reply fields.
type SurveyReply struct {
Code uint32
Data []byte
}
// SurveyCallback should be called with SurveyReply as soon as survey completed.
type SurveyCallback func(SurveyReply)
// SurveyHandler allows setting survey handler function.
type SurveyHandler func(SurveyEvent, SurveyCallback)
// NotificationEvent with Op and Data.
type NotificationEvent struct {
FromNodeID string
Op string
Data []byte
}
// NotificationHandler allows handling notifications.
type NotificationHandler func(NotificationEvent)
// NodeInfoSendReply can modify sending Node control frame in some ways.
type NodeInfoSendReply struct {
// Data allows setting an arbitrary data to the control node frame which is
// published by each Node periodically, so it will be available in the
// result of Node.Info call for the current Node description. Keep this
// data reasonably small.
Data []byte
}
// NodeInfoSendHandler called every time the control node frame is published
// and allows modifying Node control frame sending. Currently, attaching an
// arbitrary data to it. See NodeInfoSendReply.
type NodeInfoSendHandler func() NodeInfoSendReply
// TransportWriteEvent called just before sending data into the client connection. The
// event is triggered from inside each client's message queue consumer – so it should
// not directly affect Hub broadcast latencies.
type TransportWriteEvent struct {
// Data represents single Centrifuge protocol message which is going to be sent
// into the connection. For unidirectional transports this is an encoded protocol.Push
// type, for bidirectional transports this is an encoded protocol.Reply type.
Data []byte
// Channel will be set if TransportWriteEvent relates to some channel.
Channel string
// Key of Publication (optional).
Key string
// FrameType tells what is being sent inside Data.
FrameType protocol.FrameType
}
// TransportWriteHandler called just before writing data to the Transport.
// At this moment application can skip sending data to a client returning
// false from a handler. The main purpose of this handler is not a message
// filtering based on data content but rather tracing stuff.
type TransportWriteHandler func(*Client, TransportWriteEvent) bool
// CommandReadEvent contains protocol.Command processed by Client. Command
// type and its fields in the event MAY BE POOLED by Centrifuge, so code
// which wants to use Command AFTER CommandReadHandler handler returns MUST
// MAKE A COPY.
type CommandReadEvent struct {
// Command which was read from the connection. May be pooled - see comment of CommandReadEvent.
Command *protocol.Command
// CommandSize is a size of command in bytes in its protocol representation.
CommandSize int
}
// CommandReadHandler allows setting a callback which will be called before
// Client processed a protocol.Command read from the connection. Return an error
// if you want to prevent command execution.
// Also, carefully read docs for CommandReadEvent to avoid possible bugs.
type CommandReadHandler func(*Client, CommandReadEvent) error
// CommandProcessedEvent contains protocol.Command processed by Client. Command and
// Reply types and their fields in the event MAY BE POOLED by Centrifuge, so code
// which wants to use them AFTER CommandProcessedHandler handler returns MUST MAKE A
// COPY.
type CommandProcessedEvent struct {
// Command which was processed. May be pooled - see comment of CommandProcessedEvent.
Command *protocol.Command
// Error may be set to non-nil if Command processing resulted into error.
Error error
// Reply to the command. Reply may be pooled - see comment of CommandProcessedEvent.
// This Reply may be nil, for example in the following cases:
// 1. For Send command since send commands do not have replies
// 2. When command processing resulted into disconnection of the client without sending a reply.
// 3. When unidirectional transport connects (Centrifuge creates Connect Command artificially
// with id: 1 and never sends replies to the unidirectional transport, only pushes).
Reply *protocol.Reply
// Started is a time command was passed to Client for processing.
Started time.Time
}
// newCommandProcessedEvent is a helper to create CommandProcessedEvent.
func newCommandProcessedEvent(command *protocol.Command, err error, reply *protocol.Reply, started time.Time) CommandProcessedEvent {
return CommandProcessedEvent{Command: command, Error: err, Reply: reply, Started: started}
}
// CommandProcessedHandler allows setting a callback which will be called after
// Client processed a protocol.Command. This exists mostly for real-time connection
// tracing purposes. CommandProcessedHandler may be called after the corresponding
// Reply written to the connection and TransportWriteHandler called. But for tracing
// purposes this seems tolerable as commands and replies may be matched by id.
// Also, carefully read docs for CommandProcessedEvent to avoid possible bugs.
type CommandProcessedHandler func(*Client, CommandProcessedEvent)
// UntrackEvent contains fields related to an untrack request on a keyed channel.
type UntrackEvent struct {
// Channel client untracked keys from.
Channel string
// Keys are the keys being untracked.
Keys []string
}
// UntrackHandler called when client untracks keys on a keyed channel.
type UntrackHandler func(UntrackEvent)
// TrackEvent contains fields related to a track request on a keyed channel.
// A single request may carry multiple signed batches (e.g. when the SDK
// replays cached signatures after reconnect) — the handler MUST validate
// every batch's signature before allowing the track to proceed.
type TrackEvent struct {
Channel string
Batches []TrackBatch
}
// TrackBatch is one signed group of items in a TrackEvent. Each batch
// carries the FULL set of items its Signature was computed over.
type TrackBatch struct {
Items []TrackItem
// Signature is an opaque string sent by the client to prove authorization for
// the requested channel and keys in this batch. In Centrifugo this is an HMAC
// token generated by the application backend. The handler MUST verify Signature
// before allowing the batch to proceed — returning nil error without validation
// lets any client subscribe to arbitrary keys, bypassing access control.
Signature string
}
// TrackItem represents a single item being tracked (key + version).
type TrackItem struct {
Key string
Version uint64
}
// TrackReply contains the reaction to a keyed track event. Batches is parallel
// to TrackEvent.Batches — len(Batches) MUST equal len(event.Batches).
type TrackReply struct {
Batches []TrackBatchReply
}
// TrackBatchReply contains the per-batch reaction inside a TrackReply.
type TrackBatchReply struct {
// ExpireAt is the Unix timestamp (seconds) after which the keys in this batch
// are considered expired and stop receiving updates. The client must re-track
// with a fresh signature to continue.
//
// Setting a reasonable ExpireAt is important for proper permission control: it
// limits how long a client can receive data after a single authorization check.
// Without it (ExpireAt=0), a successfully tracked client receives updates
// indefinitely — even if the user's access has since been revoked.
// A typical value matches the signature/token TTL issued by the backend
// (e.g. 5–15 minutes).
ExpireAt int64
}
// TrackCallback should be called with TrackReply or error.
type TrackCallback func(TrackReply, error)
// TrackHandler is called when a client sends a track request on a shared
// poll channel. The handler MUST validate every TrackBatch.Signature in
// TrackEvent.Batches and return an error if any is invalid — this is the
// sole authorization gate for which keys a client may receive data for.
// On success, return a TrackReply with one TrackBatchReply per batch
// (parallel to event.Batches), each with ExpireAt set to bound how long
// the client may receive updates for that batch without re-authorization.
type TrackHandler func(TrackEvent, TrackCallback)
// SharedPollEvent contains fields for a shared poll refresh call.
type SharedPollEvent struct {
Channel string
Items []SharedPollItem
}
// SharedPollItem represents an item being refreshed.
type SharedPollItem struct {
Key string
Version uint64 // 0 when Mode is "versionless"
}
// SharedPollResult contains the response from a shared poll refresh.
type SharedPollResult struct {
Items []SharedPollRefreshItem
// Epoch is the publisher's current epoch for this channel. When set, an
// epoch change vs the channel's stored epoch resets all per-key versions
// and unsubscribes current subscribers with insufficient-state code,
// forcing a fresh subscribe handshake. Empty epoch means no epoch
// invalidation logic — pure version comparison.
//
// Only meaningful for versioned shared-poll channels. Versionless
// channels use server-generated channel-level epoch and ignore this.
Epoch string
}
// SharedPollRefreshItem represents a single item in a shared poll refresh response.
type SharedPollRefreshItem struct {
Key string
Data []byte
Version uint64
Removed bool
PrevData []byte
}
// SharedPollHandler is called by the refresh worker to fetch current item
// data from the backend. Registered on Node, not per-client.
type SharedPollHandler func(ctx context.Context, event SharedPollEvent) (SharedPollResult, error)