Skip to content

Commit ae17f17

Browse files
yuichi0301uchidabdbch
authored
fix(collaboration-caret): guard against null awareness state values (#7740)
* fix(collaboration-caret): guard against null awareness state values * chore: use single quotes in changeset frontmatter for consistency * chore: remove unrelated changeset from this branch * Update packages/extension-collaboration-caret/src/collaboration-caret.ts --------- Co-authored-by: uchida <uchida@nulab.com> Co-authored-by: bdbch <6538827+bdbch@users.noreply.github.com>
1 parent 2dc3df6 commit ae17f17

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tiptap/extension-collaboration-caret': patch
3+
---
4+
5+
Fix crash when awareness state value is null or undefined (e.g. after a client disconnects)

packages/extension-collaboration-caret/__tests__/collaboration-caret.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,66 @@ describe('extension-collaboration-caret', () => {
233233

234234
getEditorEl()?.remove()
235235
})
236+
237+
it('should handle null or undefined awareness state values without crashing', () => {
238+
const ydoc = new Y.Doc()
239+
240+
const states = new Map<number, Record<string, any> | null>()
241+
242+
states.set(1, { user: { name: 'Alice', color: '#ff0000' } })
243+
states.set(2, null as any)
244+
states.set(3, undefined as any)
245+
states.set(4, { user: { name: 'Bob', color: '#00ff00' } })
246+
247+
const mockProvider = {
248+
awareness: {
249+
states,
250+
setLocalStateField: () => {},
251+
on: () => {},
252+
off: () => {},
253+
getStates: () => states,
254+
},
255+
}
256+
257+
const editorEl = createEditorEl()
258+
259+
const editor = new Editor({
260+
element: editorEl,
261+
extensions: [
262+
Document,
263+
Paragraph,
264+
Text,
265+
Collaboration.configure({
266+
document: ydoc,
267+
}),
268+
CollaborationCaret.configure({
269+
provider: mockProvider,
270+
user: {
271+
name: 'Test User',
272+
color: '#0000ff',
273+
},
274+
}),
275+
],
276+
})
277+
278+
const users = editor.storage.collaborationCaret.users
279+
280+
expect(users).toEqual(
281+
expect.arrayContaining([
282+
expect.objectContaining({ clientId: 1, name: 'Alice', color: '#ff0000' }),
283+
expect.objectContaining({ clientId: 4, name: 'Bob', color: '#00ff00' }),
284+
]),
285+
)
286+
287+
const nullUser = users.find((u: any) => u.clientId === 2)
288+
289+
expect(nullUser).toEqual({ clientId: 2 })
290+
291+
const undefinedUser = users.find((u: any) => u.clientId === 3)
292+
293+
expect(undefinedUser).toEqual({ clientId: 3 })
294+
295+
editor.destroy()
296+
getEditorEl()?.remove()
297+
})
236298
})

packages/extension-collaboration-caret/src/collaboration-caret.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,16 @@ declare module '@tiptap/core' {
8383
}
8484
}
8585

86-
const awarenessStatesToArray = (states: Map<number, Record<string, any>>) => {
86+
const awarenessStatesToArray = (states: Map<number, Record<string, any> | null | undefined>) => {
8787
return Array.from(states.entries()).map(([key, value]) => {
88+
if (value && value.user) {
89+
return {
90+
clientId: key,
91+
...value.user,
92+
}
93+
}
8894
return {
8995
clientId: key,
90-
...value.user,
9196
}
9297
})
9398
}

0 commit comments

Comments
 (0)