Skip to content

Commit 6f4d055

Browse files
committed
Awareness emits different events for update and for change
1 parent fa8ebc1 commit 6f4d055

File tree

8 files changed

+152
-38
lines changed

8 files changed

+152
-38
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ offline.
4545
</dd>
4646
<b><code>setLocalStateField(string, any)</code></b>
4747
<dd>
48-
Only update a single field on the local awareness object. Does not do
48+
Only update a single field on the local awareness object. Does not do
4949
anything if the local state is not set.
5050
</dd>
5151
<b><code>getStates():Map&lt;number,Object&lt;string,any&gt;&gt;</code></b>
@@ -58,7 +58,15 @@ on('change', ({ added: Array&lt;number&gt;, updated: Array&lt;number&gt;
5858
removed: Array&lt;number&gt; }, [transactionOrigin:any]) => ..)
5959
</code></b>
6060
<dd>
61-
Listen to remote and local changes on the awareness instance.
61+
Listen to remote and local state changes on the awareness instance.
62+
</dd>
63+
<b><code>
64+
on('update', ({ added: Array&lt;number&gt;, updated: Array&lt;number&gt;
65+
removed: Array&lt;number&gt; }, [transactionOrigin:any]) => ..)
66+
</code></b>
67+
<dd>
68+
Listen to remote and local awareness changes on the awareness instance.
69+
This event is called even when the awarenes state does not change.
6270
</dd>
6371
</dl>
6472

awareness.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as decoding from 'lib0/decoding.js'
77
import * as time from 'lib0/time.js'
88
import * as math from 'lib0/math.js'
99
import { Observable } from 'lib0/observable.js'
10+
import * as f from 'lib0/function.js'
1011
import * as Y from 'yjs' // eslint-disable-line
1112

1213
export const outdatedTimeout = 30000
@@ -91,6 +92,7 @@ export class Awareness extends Observable {
9192
const clientID = this.doc.clientID
9293
const currLocalMeta = this.meta.get(clientID)
9394
const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1
95+
const prevState = this.states.get(clientID)
9496
if (state === null) {
9597
this.states.delete(clientID)
9698
} else {
@@ -102,15 +104,24 @@ export class Awareness extends Observable {
102104
})
103105
const added = []
104106
const updated = []
107+
const filteredUpdated = []
105108
const removed = []
106109
if (state === null) {
107110
removed.push(clientID)
108-
} else if (currLocalMeta === undefined) {
109-
added.push(clientID)
111+
} else if (prevState == null) {
112+
if (state != null) {
113+
added.push(clientID)
114+
}
110115
} else {
111116
updated.push(clientID)
117+
if (!f.equalityDeep(prevState, state)) {
118+
filteredUpdated.push(clientID)
119+
}
112120
}
113-
this.emit('change', [{ added, updated, removed }, 'local'])
121+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
122+
this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local'])
123+
}
124+
this.emit('update', [{ added, updated, removed }, 'local'])
114125
}
115126
/**
116127
* @param {string} field
@@ -157,6 +168,7 @@ export const removeAwarenessStates = (awareness, clients, origin) => {
157168
}
158169
if (removed.length > 0) {
159170
awareness.emit('change', [{ added: [], updated: [], removed }, origin])
171+
awareness.emit('update', [{ added: [], updated: [], removed }, origin])
160172
}
161173
}
162174

@@ -190,13 +202,15 @@ export const applyAwarenessUpdate = (awareness, update, origin) => {
190202
const timestamp = time.getUnixTime()
191203
const added = []
192204
const updated = []
205+
const filteredUpdated = []
193206
const removed = []
194207
const len = decoding.readVarUint(decoder)
195208
for (let i = 0; i < len; i++) {
196209
const clientID = decoding.readVarUint(decoder)
197210
let clock = decoding.readVarUint(decoder)
198211
const state = JSON.parse(decoding.readVarString(decoder))
199212
const clientMeta = awareness.meta.get(clientID)
213+
const prevState = awareness.states.get(clientID)
200214
const currClock = clientMeta === undefined ? 0 : clientMeta.clock
201215
if (currClock < clock || (currClock === clock && state === null && awareness.states.has(clientID))) {
202216
if (state === null) {
@@ -220,12 +234,20 @@ export const applyAwarenessUpdate = (awareness, update, origin) => {
220234
} else if (clientMeta !== undefined && state === null) {
221235
removed.push(clientID)
222236
} else if (state !== null) {
237+
if (!f.equalityDeep(state, prevState)) {
238+
filteredUpdated.push(clientID)
239+
}
223240
updated.push(clientID)
224241
}
225242
}
226243
}
227-
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
244+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
228245
awareness.emit('change', [{
246+
added, updated: filteredUpdated, removed
247+
}, origin])
248+
}
249+
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
250+
awareness.emit('update', [{
229251
added, updated, removed
230252
}, origin])
231253
}

awareness.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
import * as Y from 'yjs'
3+
import * as t from 'lib0/testing.js'
4+
import * as awareness from './awareness.js'
5+
6+
/**
7+
* @param {t.TestCase} tc
8+
*/
9+
export const testAwareness = tc => {
10+
const doc1 = new Y.Doc()
11+
doc1.clientID = 0
12+
const doc2 = new Y.Doc()
13+
doc2.clientID = 1
14+
const aw1 = new awareness.Awareness(doc1)
15+
const aw2 = new awareness.Awareness(doc2)
16+
aw1.on('update', /** @param {any} p */ ({ added, updated, removed }) => {
17+
const enc = awareness.encodeAwarenessUpdate(aw1, added.concat(updated).concat(removed))
18+
awareness.applyAwarenessUpdate(aw2, enc, 'custom')
19+
})
20+
let lastChange = /** @type {any} */ (null)
21+
aw2.on('change', /** @param {any} change */ change => {
22+
lastChange = change
23+
})
24+
aw1.setLocalState({ x: 4 })
25+
t.compare(aw2.getStates().get(0), { x: 4 })
26+
t.assert(/** @type {any} */ (aw2.meta.get(0)).clock === 1)
27+
t.compare(lastChange.added, [0])
28+
lastChange = null
29+
aw1.setLocalState({ x: 4 })
30+
t.assert(lastChange === null)
31+
t.assert(/** @type {any} */ (aw2.meta.get(0)).clock === 2)
32+
aw1.setLocalState(null)
33+
t.assert(lastChange.removed.length === 1)
34+
t.compare(aw1.getStates().get(0), undefined)
35+
}

package-lock.json

Lines changed: 22 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"version": "0.2.3",
44
"description": "Yjs encoding protocols",
55
"type": "module",
6+
"funding": {
7+
"type": "GitHub Sponsors ❤",
8+
"url": "https://github.com/sponsors/dmonad"
9+
},
610
"files": [
711
"dist/*",
812
"auth.*",
@@ -12,9 +16,10 @@
1216
"scripts": {
1317
"clean": "rm -rf dist *.d.ts */*.d.ts *.d.ts.map */*.d.ts.map",
1418
"dist": "rm -rf dist && rollup -c",
15-
"test": "npm run lint",
19+
"test": "npm run lint && npm run dist && node dist/test.cjs",
1620
"lint": "standard && tsc",
1721
"types": "tsc --outDir .",
22+
"debug": "rollup -c && concurrently 'rollup -wc' 'http-server -o test.html'",
1823
"preversion": "npm run dist && npm run test && npm run types",
1924
"postpublish": "npm run clean"
2025
},
@@ -38,11 +43,10 @@
3843
},
3944
"homepage": "https://github.com/yjs/y-protocols#readme",
4045
"dependencies": {
41-
"lib0": "^0.2.20"
46+
"lib0": "^0.2.28"
4247
},
4348
"devDependencies": {
44-
"rollup": "^1.1.2",
45-
"rollup-cli": "^1.0.9",
49+
"rollup": "^1.29.0",
4650
"standard": "^12.0.1",
4751
"typescript": "^3.8.3",
4852
"yjs": "^13.0.4"

rollup.config.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
import fs from 'fs'
2-
import path from 'path'
1+
import resolve from '@rollup/plugin-node-resolve'
2+
import commonjs from '@rollup/plugin-commonjs'
33

4-
const files = fs.readdirSync('./').filter(file => /(?<!\.(test|config))\.js$/.test(file))
4+
const files = ['awareness.js', 'auth.js', 'sync.js', 'test.js']
55

6-
export default files.map(file => ({
7-
input: file,
6+
export default [{
7+
input: './test.js',
88
output: {
9-
file: path.join('./dist', file.slice(0, -3) + '.cjs'),
9+
file: './dist/test.js',
10+
format: 'iife',
11+
sourcemap: true
12+
},
13+
plugins: [
14+
resolve({ mainFields: ['module', 'browser', 'main'] }),
15+
commonjs()
16+
]
17+
}, {
18+
input: files,
19+
output: {
20+
dir: './dist',
1021
format: 'cjs',
22+
sourcemap: true,
23+
entryFileNames: '[name].cjs',
24+
chunkFileNames: '[name]-[hash].cjs',
1125
paths: /** @param {any} path */ path => {
1226
if (/^lib0\//.test(path)) {
1327
return `lib0/dist/${path.slice(5, -3) + '.cjs'}`
1428
}
1529
return path
1630
}
17-
}
18-
}))
31+
},
32+
external: /** @param {any} id */ id => /^lib0\/|yjs/.test(id)
33+
}]

test.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Testing y-protocols</title>
5+
</head>
6+
<body>
7+
<script src="./dist/test.js"></script>
8+
</body>
9+
</html>

test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { runTests } from 'lib0/testing.js'
2+
import * as log from 'lib0/logging.js'
3+
import * as awareness from './awareness.test.js'
4+
5+
import { isBrowser, isNode } from 'lib0/environment.js'
6+
7+
/* istanbul ignore if */
8+
if (isBrowser) {
9+
log.createVConsole(document.body)
10+
}
11+
12+
runTests({
13+
awareness
14+
}).then(success => {
15+
/* istanbul ignore next */
16+
if (isNode) {
17+
process.exit(success ? 0 : 1)
18+
}
19+
})

0 commit comments

Comments
 (0)