@sanity/sdk 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1705 -2163
- package/dist/index.js +497 -187
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/_exports/index.ts +22 -1
- package/src/auth/authStore.ts +1 -0
- package/src/auth/refreshStampedToken.test.ts +225 -6
- package/src/auth/refreshStampedToken.ts +95 -30
- package/src/presence/bifurTransport.test.ts +257 -0
- package/src/presence/bifurTransport.ts +108 -0
- package/src/presence/presenceStore.test.ts +247 -0
- package/src/presence/presenceStore.ts +163 -0
- package/src/presence/types.ts +70 -0
- package/src/query/queryStore.test.ts +4 -1
- package/src/releases/releasesStore.test.ts +5 -2
- package/src/users/reducers.ts +9 -3
- package/src/users/types.ts +17 -0
- package/src/users/usersConstants.ts +1 -0
- package/src/users/usersStore.test.ts +129 -9
- package/src/users/usersStore.ts +132 -4
- package/src/utils/defineIntent.test.ts +477 -0
- package/src/utils/defineIntent.ts +244 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {fromUrl} from '@sanity/bifur-client'
|
|
2
|
+
import {type SanityClient} from '@sanity/client'
|
|
3
|
+
import {of, Subject} from 'rxjs'
|
|
4
|
+
import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
|
|
5
|
+
|
|
6
|
+
import {createBifurTransport} from './bifurTransport'
|
|
7
|
+
import {type PresenceLocation, type TransportEvent} from './types'
|
|
8
|
+
|
|
9
|
+
vi.mock('@sanity/bifur-client', () => ({
|
|
10
|
+
fromUrl: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
const fromUrlMock = fromUrl as Mock
|
|
14
|
+
|
|
15
|
+
type BifurStateMessage = {
|
|
16
|
+
type: 'state'
|
|
17
|
+
i: string
|
|
18
|
+
m: {
|
|
19
|
+
sessionId: string
|
|
20
|
+
locations: PresenceLocation[]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type BifurDisconnectMessage = {
|
|
25
|
+
type: 'disconnect'
|
|
26
|
+
i: string
|
|
27
|
+
m: {session: string}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type BifurRollCallEvent = {
|
|
31
|
+
type: 'rollCall'
|
|
32
|
+
i: string
|
|
33
|
+
session: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type IncomingBifurEvent = BifurRollCallEvent | BifurStateMessage | BifurDisconnectMessage
|
|
37
|
+
|
|
38
|
+
describe('createBifurTransport', () => {
|
|
39
|
+
let mockBifurClient: {
|
|
40
|
+
listen: Mock
|
|
41
|
+
request: Mock
|
|
42
|
+
}
|
|
43
|
+
let mockSanityClient: SanityClient
|
|
44
|
+
let token$: Subject<string | null>
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.useFakeTimers()
|
|
48
|
+
mockBifurClient = {
|
|
49
|
+
listen: vi.fn(() => new Subject<never>()),
|
|
50
|
+
request: vi.fn(() => of(undefined)),
|
|
51
|
+
}
|
|
52
|
+
fromUrlMock.mockReturnValue(mockBifurClient)
|
|
53
|
+
|
|
54
|
+
mockSanityClient = {
|
|
55
|
+
config: () => ({
|
|
56
|
+
dataset: 'test-dataset',
|
|
57
|
+
url: 'http://localhost:3333',
|
|
58
|
+
requestTagPrefix: 'test-tag',
|
|
59
|
+
}),
|
|
60
|
+
withConfig: vi.fn().mockReturnThis(),
|
|
61
|
+
} as unknown as SanityClient
|
|
62
|
+
|
|
63
|
+
token$ = new Subject<string | null>()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('constructs the bifur client with the correct URL', () => {
|
|
67
|
+
createBifurTransport({
|
|
68
|
+
client: mockSanityClient,
|
|
69
|
+
token$,
|
|
70
|
+
sessionId: 'session-id-123',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(fromUrlMock).toHaveBeenCalledWith(
|
|
74
|
+
'ws://localhost:3333/socket/test-dataset?tag=test-tag',
|
|
75
|
+
{
|
|
76
|
+
token$,
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('handles incoming rollCall events', () => {
|
|
82
|
+
const incomingBifurEvents$ = new Subject<IncomingBifurEvent>()
|
|
83
|
+
mockBifurClient.listen.mockReturnValue(incomingBifurEvents$)
|
|
84
|
+
|
|
85
|
+
const [incomingEvents$] = createBifurTransport({
|
|
86
|
+
client: mockSanityClient,
|
|
87
|
+
token$,
|
|
88
|
+
sessionId: 'session-id-123',
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const receivedEvents: TransportEvent[] = []
|
|
92
|
+
incomingEvents$.subscribe((event) => receivedEvents.push(event))
|
|
93
|
+
|
|
94
|
+
incomingBifurEvents$.next({
|
|
95
|
+
type: 'rollCall',
|
|
96
|
+
i: 'user-1',
|
|
97
|
+
session: 'session-id-456',
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expect(receivedEvents).toEqual([
|
|
101
|
+
{
|
|
102
|
+
type: 'rollCall',
|
|
103
|
+
userId: 'user-1',
|
|
104
|
+
sessionId: 'session-id-456',
|
|
105
|
+
},
|
|
106
|
+
])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('handles incoming state events', () => {
|
|
110
|
+
const date = new Date('2024-01-01T12:00:00.000Z')
|
|
111
|
+
vi.setSystemTime(date)
|
|
112
|
+
|
|
113
|
+
const incomingBifurEvents$ = new Subject<IncomingBifurEvent>()
|
|
114
|
+
mockBifurClient.listen.mockReturnValue(incomingBifurEvents$)
|
|
115
|
+
|
|
116
|
+
const [incomingEvents$] = createBifurTransport({
|
|
117
|
+
client: mockSanityClient,
|
|
118
|
+
token$,
|
|
119
|
+
sessionId: 'session-id-123',
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const receivedEvents: TransportEvent[] = []
|
|
123
|
+
incomingEvents$.subscribe((event) => receivedEvents.push(event))
|
|
124
|
+
|
|
125
|
+
const locations: PresenceLocation[] = [
|
|
126
|
+
{type: 'document', documentId: 'doc1', path: ['a'], lastActiveAt: new Date().toISOString()},
|
|
127
|
+
]
|
|
128
|
+
incomingBifurEvents$.next({
|
|
129
|
+
type: 'state',
|
|
130
|
+
i: 'user-1',
|
|
131
|
+
m: {
|
|
132
|
+
sessionId: 'session-id-456',
|
|
133
|
+
locations,
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
expect(receivedEvents).toEqual([
|
|
138
|
+
{
|
|
139
|
+
type: 'state',
|
|
140
|
+
userId: 'user-1',
|
|
141
|
+
sessionId: 'session-id-456',
|
|
142
|
+
timestamp: date.toISOString(),
|
|
143
|
+
locations,
|
|
144
|
+
},
|
|
145
|
+
])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('handles incoming disconnect events', () => {
|
|
149
|
+
const date = new Date('2024-01-01T12:00:00.000Z')
|
|
150
|
+
vi.setSystemTime(date)
|
|
151
|
+
|
|
152
|
+
const incomingBifurEvents$ = new Subject<IncomingBifurEvent>()
|
|
153
|
+
mockBifurClient.listen.mockReturnValue(incomingBifurEvents$)
|
|
154
|
+
|
|
155
|
+
const [incomingEvents$] = createBifurTransport({
|
|
156
|
+
client: mockSanityClient,
|
|
157
|
+
token$,
|
|
158
|
+
sessionId: 'session-id-123',
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const receivedEvents: TransportEvent[] = []
|
|
162
|
+
incomingEvents$.subscribe((event) => receivedEvents.push(event))
|
|
163
|
+
|
|
164
|
+
incomingBifurEvents$.next({
|
|
165
|
+
type: 'disconnect',
|
|
166
|
+
i: 'user-1',
|
|
167
|
+
m: {
|
|
168
|
+
session: 'session-id-456',
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
expect(receivedEvents).toEqual([
|
|
173
|
+
{
|
|
174
|
+
type: 'disconnect',
|
|
175
|
+
userId: 'user-1',
|
|
176
|
+
sessionId: 'session-id-456',
|
|
177
|
+
timestamp: date.toISOString(),
|
|
178
|
+
},
|
|
179
|
+
])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('throws an error for unknown incoming events', () => {
|
|
183
|
+
const incomingBifurEvents$ = new Subject<IncomingBifurEvent>()
|
|
184
|
+
mockBifurClient.listen.mockReturnValue(incomingBifurEvents$)
|
|
185
|
+
|
|
186
|
+
const [incomingEvents$] = createBifurTransport({
|
|
187
|
+
client: mockSanityClient,
|
|
188
|
+
token$,
|
|
189
|
+
sessionId: 'session-id-123',
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const errors: Error[] = []
|
|
193
|
+
incomingEvents$.subscribe({
|
|
194
|
+
error: (err) => errors.push(err),
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
+
incomingBifurEvents$.next({type: 'unknown'} as any)
|
|
199
|
+
|
|
200
|
+
expect(errors.length).toBe(1)
|
|
201
|
+
expect(errors[0]).toBeInstanceOf(Error)
|
|
202
|
+
expect(errors[0].message).toContain('Got unknown presence event')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
describe('dispatchMessage', () => {
|
|
206
|
+
it('sends a "rollCall" message', () => {
|
|
207
|
+
const [, dispatchMessage] = createBifurTransport({
|
|
208
|
+
client: mockSanityClient,
|
|
209
|
+
token$,
|
|
210
|
+
sessionId: 'my-session',
|
|
211
|
+
})
|
|
212
|
+
dispatchMessage({type: 'rollCall'})
|
|
213
|
+
expect(mockBifurClient.request).toHaveBeenCalledWith('presence_rollcall', {
|
|
214
|
+
session: 'my-session',
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('sends a "state" message', () => {
|
|
219
|
+
const [, dispatchMessage] = createBifurTransport({
|
|
220
|
+
client: mockSanityClient,
|
|
221
|
+
token$,
|
|
222
|
+
sessionId: 'my-session',
|
|
223
|
+
})
|
|
224
|
+
const locations: PresenceLocation[] = [
|
|
225
|
+
{type: 'document', documentId: 'doc1', path: ['a'], lastActiveAt: new Date().toISOString()},
|
|
226
|
+
]
|
|
227
|
+
dispatchMessage({type: 'state', locations})
|
|
228
|
+
expect(mockBifurClient.request).toHaveBeenCalledWith('presence_announce', {
|
|
229
|
+
data: {locations, sessionId: 'my-session'},
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('sends a "disconnect" message', () => {
|
|
234
|
+
const [, dispatchMessage] = createBifurTransport({
|
|
235
|
+
client: mockSanityClient,
|
|
236
|
+
token$,
|
|
237
|
+
sessionId: 'my-session',
|
|
238
|
+
})
|
|
239
|
+
dispatchMessage({type: 'disconnect'})
|
|
240
|
+
expect(mockBifurClient.request).toHaveBeenCalledWith('presence_disconnect', {
|
|
241
|
+
session: 'my-session',
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('does nothing for unknown message types', () => {
|
|
246
|
+
const [, dispatchMessage] = createBifurTransport({
|
|
247
|
+
client: mockSanityClient,
|
|
248
|
+
token$,
|
|
249
|
+
sessionId: 'my-session',
|
|
250
|
+
})
|
|
251
|
+
// The type assertion is needed to test this case
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
+
dispatchMessage({type: 'unknown'} as any)
|
|
254
|
+
expect(mockBifurClient.request).not.toHaveBeenCalled()
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {type BifurClient, fromUrl} from '@sanity/bifur-client'
|
|
2
|
+
import {type SanityClient} from '@sanity/client'
|
|
3
|
+
import {EMPTY, fromEvent, type Observable} from 'rxjs'
|
|
4
|
+
import {map, share, switchMap} from 'rxjs/operators'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type BifurTransportOptions,
|
|
8
|
+
type PresenceLocation,
|
|
9
|
+
type PresenceTransport,
|
|
10
|
+
type TransportEvent,
|
|
11
|
+
type TransportMessage,
|
|
12
|
+
} from './types'
|
|
13
|
+
|
|
14
|
+
type BifurStateMessage = {
|
|
15
|
+
type: 'state'
|
|
16
|
+
i: string
|
|
17
|
+
m: {
|
|
18
|
+
sessionId: string
|
|
19
|
+
locations: PresenceLocation[]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type BifurDisconnectMessage = {
|
|
24
|
+
type: 'disconnect'
|
|
25
|
+
i: string
|
|
26
|
+
m: {session: string}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type RollCallEvent = {
|
|
30
|
+
type: 'rollCall'
|
|
31
|
+
i: string
|
|
32
|
+
session: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type IncomingBifurEvent = RollCallEvent | BifurStateMessage | BifurDisconnectMessage
|
|
36
|
+
|
|
37
|
+
function getBifurClient(client: SanityClient, token$: Observable<string | null>): BifurClient {
|
|
38
|
+
const bifurVersionedClient = client.withConfig({apiVersion: '2022-06-30'})
|
|
39
|
+
const {dataset, url: baseUrl, requestTagPrefix = 'sanity.studio'} = bifurVersionedClient.config()
|
|
40
|
+
const url = `${baseUrl.replace(/\/+$/, '')}/socket/${dataset}`.replace(/^http/, 'ws')
|
|
41
|
+
const urlWithTag = `${url}?tag=${requestTagPrefix}`
|
|
42
|
+
|
|
43
|
+
return fromUrl(urlWithTag, {token$})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handleIncomingMessage = (event: IncomingBifurEvent): TransportEvent => {
|
|
47
|
+
switch (event.type) {
|
|
48
|
+
case 'rollCall':
|
|
49
|
+
return {
|
|
50
|
+
type: 'rollCall',
|
|
51
|
+
userId: event.i,
|
|
52
|
+
sessionId: event.session,
|
|
53
|
+
}
|
|
54
|
+
case 'state': {
|
|
55
|
+
const {sessionId, locations} = event.m
|
|
56
|
+
return {
|
|
57
|
+
type: 'state',
|
|
58
|
+
userId: event.i,
|
|
59
|
+
sessionId,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
locations,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
case 'disconnect':
|
|
65
|
+
return {
|
|
66
|
+
type: 'disconnect',
|
|
67
|
+
userId: event.i,
|
|
68
|
+
sessionId: event.m.session,
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
}
|
|
71
|
+
default: {
|
|
72
|
+
throw new Error(`Got unknown presence event: ${JSON.stringify(event)}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const createBifurTransport = (options: BifurTransportOptions): PresenceTransport => {
|
|
78
|
+
const {client, token$, sessionId} = options
|
|
79
|
+
const bifur = getBifurClient(client, token$)
|
|
80
|
+
|
|
81
|
+
const incomingEvents$: Observable<TransportEvent> = bifur
|
|
82
|
+
.listen<IncomingBifurEvent>('presence')
|
|
83
|
+
.pipe(map(handleIncomingMessage))
|
|
84
|
+
|
|
85
|
+
const dispatchMessage = (message: TransportMessage): Observable<void> => {
|
|
86
|
+
switch (message.type) {
|
|
87
|
+
case 'rollCall':
|
|
88
|
+
return bifur.request('presence_rollcall', {session: sessionId})
|
|
89
|
+
case 'state':
|
|
90
|
+
return bifur.request('presence_announce', {
|
|
91
|
+
data: {locations: message.locations, sessionId},
|
|
92
|
+
})
|
|
93
|
+
case 'disconnect':
|
|
94
|
+
return bifur.request('presence_disconnect', {session: sessionId})
|
|
95
|
+
default: {
|
|
96
|
+
return EMPTY
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof window !== 'undefined') {
|
|
102
|
+
fromEvent(window, 'beforeunload')
|
|
103
|
+
.pipe(switchMap(() => dispatchMessage({type: 'disconnect'})))
|
|
104
|
+
.subscribe()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return [incomingEvents$.pipe(share()), dispatchMessage]
|
|
108
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
import {delay, firstValueFrom, of, Subject} from 'rxjs'
|
|
3
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {getTokenState} from '../auth/authStore'
|
|
6
|
+
import {getClient} from '../client/clientStore'
|
|
7
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
8
|
+
import {type SanityUser} from '../users/types'
|
|
9
|
+
import {getUserState} from '../users/usersStore'
|
|
10
|
+
import {createBifurTransport} from './bifurTransport'
|
|
11
|
+
import {getPresence} from './presenceStore'
|
|
12
|
+
import {type PresenceLocation, type TransportEvent} from './types'
|
|
13
|
+
|
|
14
|
+
vi.mock('../auth/authStore')
|
|
15
|
+
vi.mock('../client/clientStore')
|
|
16
|
+
vi.mock('../users/usersStore')
|
|
17
|
+
vi.mock('./bifurTransport')
|
|
18
|
+
|
|
19
|
+
describe('presenceStore', () => {
|
|
20
|
+
let instance: SanityInstance
|
|
21
|
+
let mockClient: SanityClient
|
|
22
|
+
let mockTokenState: Subject<string | null>
|
|
23
|
+
let mockIncomingEvents: Subject<TransportEvent>
|
|
24
|
+
let mockDispatchMessage: ReturnType<typeof vi.fn>
|
|
25
|
+
let mockGetUserState: ReturnType<typeof vi.fn>
|
|
26
|
+
|
|
27
|
+
const mockUser: SanityUser = {
|
|
28
|
+
sanityUserId: 'u123',
|
|
29
|
+
profile: {
|
|
30
|
+
id: 'user-1',
|
|
31
|
+
displayName: 'Test User',
|
|
32
|
+
email: 'test@example.com',
|
|
33
|
+
provider: 'google',
|
|
34
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
35
|
+
},
|
|
36
|
+
memberships: [],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks()
|
|
41
|
+
|
|
42
|
+
// Mock crypto.randomUUID
|
|
43
|
+
Object.defineProperty(global, 'crypto', {
|
|
44
|
+
value: {
|
|
45
|
+
randomUUID: vi.fn(() => 'test-session-id'),
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
mockClient = {
|
|
50
|
+
withConfig: vi.fn().mockReturnThis(),
|
|
51
|
+
} as unknown as SanityClient
|
|
52
|
+
|
|
53
|
+
mockTokenState = new Subject<string | null>()
|
|
54
|
+
mockIncomingEvents = new Subject<TransportEvent>()
|
|
55
|
+
mockDispatchMessage = vi.fn(() => of(undefined))
|
|
56
|
+
|
|
57
|
+
vi.mocked(getClient).mockReturnValue(mockClient)
|
|
58
|
+
vi.mocked(getTokenState).mockReturnValue({
|
|
59
|
+
observable: mockTokenState.asObservable(),
|
|
60
|
+
getCurrent: vi.fn(),
|
|
61
|
+
subscribe: vi.fn(),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
vi.mocked(createBifurTransport).mockReturnValue([
|
|
65
|
+
mockIncomingEvents.asObservable(),
|
|
66
|
+
mockDispatchMessage,
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
mockGetUserState = vi.fn(() => of(mockUser))
|
|
70
|
+
vi.mocked(getUserState).mockImplementation(mockGetUserState)
|
|
71
|
+
|
|
72
|
+
instance = createSanityInstance({projectId: 'test-project', dataset: 'test-dataset'})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
instance.dispose()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('getPresence', () => {
|
|
80
|
+
it('creates bifur transport with correct parameters', () => {
|
|
81
|
+
getPresence(instance)
|
|
82
|
+
|
|
83
|
+
expect(createBifurTransport).toHaveBeenCalledWith({
|
|
84
|
+
client: mockClient,
|
|
85
|
+
token$: expect.any(Object),
|
|
86
|
+
sessionId: 'test-session-id',
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('sends rollCall message on initialization', () => {
|
|
91
|
+
getPresence(instance)
|
|
92
|
+
|
|
93
|
+
expect(mockDispatchMessage).toHaveBeenCalledWith({type: 'rollCall'})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('returns empty array when no users present', () => {
|
|
97
|
+
const source = getPresence(instance)
|
|
98
|
+
expect(source.getCurrent()).toEqual([])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('handles state events from other users', async () => {
|
|
102
|
+
const source = getPresence(instance)
|
|
103
|
+
|
|
104
|
+
// Subscribe to initialize the store
|
|
105
|
+
const unsubscribe = source.subscribe(() => {})
|
|
106
|
+
|
|
107
|
+
// Wait a bit for initialization
|
|
108
|
+
await firstValueFrom(of(null).pipe(delay(10)))
|
|
109
|
+
|
|
110
|
+
const locations: PresenceLocation[] = [
|
|
111
|
+
{
|
|
112
|
+
type: 'document',
|
|
113
|
+
documentId: 'doc-1',
|
|
114
|
+
path: ['title'],
|
|
115
|
+
lastActiveAt: '2023-01-01T12:00:00Z',
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
mockIncomingEvents.next({
|
|
120
|
+
type: 'state',
|
|
121
|
+
userId: 'user-1',
|
|
122
|
+
sessionId: 'other-session',
|
|
123
|
+
timestamp: '2023-01-01T12:00:00Z',
|
|
124
|
+
locations,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Wait for processing
|
|
128
|
+
await firstValueFrom(of(null).pipe(delay(20)))
|
|
129
|
+
|
|
130
|
+
const presence = source.getCurrent()
|
|
131
|
+
expect(presence).toHaveLength(1)
|
|
132
|
+
expect(presence[0].sessionId).toBe('other-session')
|
|
133
|
+
expect(presence[0].locations).toEqual(locations)
|
|
134
|
+
|
|
135
|
+
unsubscribe()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('ignores events from own session', async () => {
|
|
139
|
+
const source = getPresence(instance)
|
|
140
|
+
const unsubscribe = source.subscribe(() => {})
|
|
141
|
+
|
|
142
|
+
await firstValueFrom(of(null).pipe(delay(10)))
|
|
143
|
+
|
|
144
|
+
mockIncomingEvents.next({
|
|
145
|
+
type: 'state',
|
|
146
|
+
userId: 'user-1',
|
|
147
|
+
sessionId: 'test-session-id', // Same as our session
|
|
148
|
+
timestamp: '2023-01-01T12:00:00Z',
|
|
149
|
+
locations: [],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
await firstValueFrom(of(null).pipe(delay(20)))
|
|
153
|
+
|
|
154
|
+
const presence = source.getCurrent()
|
|
155
|
+
expect(presence).toHaveLength(0)
|
|
156
|
+
|
|
157
|
+
unsubscribe()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('handles disconnect events', async () => {
|
|
161
|
+
const source = getPresence(instance)
|
|
162
|
+
const unsubscribe = source.subscribe(() => {})
|
|
163
|
+
|
|
164
|
+
await firstValueFrom(of(null).pipe(delay(10)))
|
|
165
|
+
|
|
166
|
+
// First add a user
|
|
167
|
+
mockIncomingEvents.next({
|
|
168
|
+
type: 'state',
|
|
169
|
+
userId: 'user-1',
|
|
170
|
+
sessionId: 'other-session',
|
|
171
|
+
timestamp: '2023-01-01T12:00:00Z',
|
|
172
|
+
locations: [],
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
await firstValueFrom(of(null).pipe(delay(20)))
|
|
176
|
+
expect(source.getCurrent()).toHaveLength(1)
|
|
177
|
+
|
|
178
|
+
// Then disconnect them
|
|
179
|
+
mockIncomingEvents.next({
|
|
180
|
+
type: 'disconnect',
|
|
181
|
+
userId: 'user-1',
|
|
182
|
+
sessionId: 'other-session',
|
|
183
|
+
timestamp: '2023-01-01T12:01:00Z',
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
await firstValueFrom(of(null).pipe(delay(20)))
|
|
187
|
+
expect(source.getCurrent()).toHaveLength(0)
|
|
188
|
+
|
|
189
|
+
unsubscribe()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('fetches user data for present users', async () => {
|
|
193
|
+
const source = getPresence(instance)
|
|
194
|
+
const unsubscribe = source.subscribe(() => {})
|
|
195
|
+
|
|
196
|
+
await firstValueFrom(of(null).pipe(delay(10)))
|
|
197
|
+
|
|
198
|
+
mockIncomingEvents.next({
|
|
199
|
+
type: 'state',
|
|
200
|
+
userId: 'user-1',
|
|
201
|
+
sessionId: 'other-session',
|
|
202
|
+
timestamp: '2023-01-01T12:00:00Z',
|
|
203
|
+
locations: [
|
|
204
|
+
{
|
|
205
|
+
type: 'document',
|
|
206
|
+
documentId: 'doc-1',
|
|
207
|
+
path: ['title'],
|
|
208
|
+
lastActiveAt: '2023-01-01T12:00:00Z',
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
await firstValueFrom(of(null).pipe(delay(50)))
|
|
214
|
+
|
|
215
|
+
expect(getUserState).toHaveBeenCalledWith(instance, {
|
|
216
|
+
userId: 'user-1',
|
|
217
|
+
resourceType: 'project',
|
|
218
|
+
projectId: 'test-project',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
unsubscribe()
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('handles presence events correctly', async () => {
|
|
225
|
+
const source = getPresence(instance)
|
|
226
|
+
const unsubscribe = source.subscribe(() => {})
|
|
227
|
+
|
|
228
|
+
await firstValueFrom(of(null).pipe(delay(10)))
|
|
229
|
+
|
|
230
|
+
mockIncomingEvents.next({
|
|
231
|
+
type: 'state',
|
|
232
|
+
userId: 'test-user',
|
|
233
|
+
sessionId: 'other-session',
|
|
234
|
+
timestamp: '2023-01-01T12:00:00Z',
|
|
235
|
+
locations: [],
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
await firstValueFrom(of(null).pipe(delay(50)))
|
|
239
|
+
|
|
240
|
+
const presence = source.getCurrent()
|
|
241
|
+
expect(presence).toHaveLength(1)
|
|
242
|
+
expect(presence[0].sessionId).toBe('other-session')
|
|
243
|
+
|
|
244
|
+
unsubscribe()
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
})
|