direct_notifications.go

v1.0.0
Doc Versions Source
1
package bitwarden
2
3
import (
4
	"context"
5
	"log"
6
7
	"sourcecraft.dev/bigbes/go-bitwarden/internal/api"
8
)
9
10
// NotificationHandler is called when a server notification is received.
11
// It runs in a background goroutine — implementations must be safe for
12
// concurrent use.
13
type NotificationHandler func(notifType NotificationType, payload NotificationPayload)
14
15
// NotificationType represents the type of server notification.
16
type NotificationType = api.NotificationType
17
18
// Notification type constants.
19
const (
20
	NotifSyncCipherUpdate    = api.NotifSyncCipherUpdate
21
	NotifSyncCipherCreate    = api.NotifSyncCipherCreate
22
	NotifSyncLoginDelete     = api.NotifSyncLoginDelete
23
	NotifSyncFolderDelete    = api.NotifSyncFolderDelete
24
	NotifSyncCiphers         = api.NotifSyncCiphers
25
	NotifSyncVault           = api.NotifSyncVault
26
	NotifSyncOrgKeys         = api.NotifSyncOrgKeys
27
	NotifSyncFolderCreate    = api.NotifSyncFolderCreate
28
	NotifSyncFolderUpdate    = api.NotifSyncFolderUpdate
29
	NotifSyncCipherDelete    = api.NotifSyncCipherDelete
30
	NotifSyncSettings        = api.NotifSyncSettings
31
	NotifLogOut              = api.NotifLogOut
32
	NotifSyncSendCreate      = api.NotifSyncSendCreate
33
	NotifSyncSendUpdate      = api.NotifSyncSendUpdate
34
	NotifSyncSendDelete      = api.NotifSyncSendDelete
35
	NotifAuthRequest         = api.NotifAuthRequest
36
	NotifAuthRequestResponse = api.NotifAuthRequestResponse
37
)
38
39
// NotificationPayload contains details of a server notification.
40
type NotificationPayload struct {
41
	ID             string
42
	UserID         string
43
	OrganizationID string
44
	CollectionIDs  []string
45
	RevisionDate   string
46
}
47
48
// WithNotificationHandler sets a callback invoked on each notification.
49
// If not set, notifications only trigger cache invalidation.
50
func WithNotificationHandler(h NotificationHandler) DirectClientOption {
51
	return func(dc *DirectClient) {
52
		dc.notifyHandler = h
53
	}
54
}
55
56
// StartNotifications connects to the server's WebSocket notification hub.
57
// Notifications trigger automatic cache invalidation. If a NotificationHandler
58
// was configured via WithNotificationHandler, it is also called.
59
// The provided context controls the connection lifetime.
60
func (dc *DirectClient) StartNotifications(ctx context.Context) error {
61
	dc.mu.Lock()
62
	defer dc.mu.Unlock()
63
64
	if dc.notifications != nil {
65
		return nil // already running
66
	}
67
68
	nc := api.NewNotificationsClient(dc.transport, dc.handleNotification)
69
	if err := nc.Connect(ctx); err != nil {
70
		return err
71
	}
72
73
	dc.notifications = nc
74
	return nil
75
}
76
77
// StopNotifications disconnects from the notification hub.
78
func (dc *DirectClient) StopNotifications() error {
79
	dc.mu.Lock()
80
	nc := dc.notifications
81
	dc.notifications = nil
82
	dc.mu.Unlock()
83
84
	if nc != nil {
85
		return nc.Close()
86
	}
87
	return nil
88
}
89
90
// SetNotificationsLogger sets a logger for notification debug output.
91
func (dc *DirectClient) SetNotificationsLogger(l *log.Logger) {
92
	dc.mu.Lock()
93
	defer dc.mu.Unlock()
94
	if dc.notifications != nil {
95
		dc.notifications.SetLogger(l)
96
	}
97
}
98
99
func (dc *DirectClient) handleNotification(msg api.NotificationMessage) {
100
	payload := NotificationPayload{
101
		ID:             msg.Payload.ID,
102
		UserID:         msg.Payload.UserID,
103
		OrganizationID: msg.Payload.OrganizationID,
104
		CollectionIDs:  msg.Payload.CollectionIDs,
105
		RevisionDate:   msg.Payload.RevisionDate,
106
	}
107
108
	// Perform cache action based on notification type
109
	switch msg.Type {
110
	case api.NotifSyncCipherCreate, api.NotifSyncCipherUpdate:
111
		// Can't decrypt individual items here, so invalidate the cache.
112
		// The next read will fetch from the server.
113
		dc.cache.invalidate()
114
115
	case api.NotifSyncCipherDelete, api.NotifSyncLoginDelete:
116
		dc.cache.removeCipher(msg.Payload.ID)
117
118
	case api.NotifSyncFolderCreate, api.NotifSyncFolderUpdate:
119
		dc.cache.invalidate()
120
121
	case api.NotifSyncFolderDelete:
122
		dc.cache.removeFolder(msg.Payload.ID)
123
124
	case api.NotifSyncCiphers, api.NotifSyncVault, api.NotifSyncOrgKeys, api.NotifSyncSettings:
125
		dc.cache.invalidate()
126
127
	case api.NotifSyncSendCreate, api.NotifSyncSendUpdate:
128
		dc.cache.invalidate()
129
130
	case api.NotifSyncSendDelete:
131
		dc.cache.removeSend(msg.Payload.ID)
132
133
	case api.NotifLogOut:
134
		dc.cache.invalidate()
135
		dc.mu.Lock()
136
		if dc.keyChain != nil {
137
			dc.keyChain.Clear()
138
			dc.keyChain = nil
139
		}
140
		dc.mu.Unlock()
141
	}
142
143
	// Forward to user handler if set
144
	if dc.notifyHandler != nil {
145
		dc.notifyHandler(msg.Type, payload)
146
	}
147
}
148

Source Files