Skip to content

Commit f317171

Browse files
committed
refactor(realtime): extract user event "permission" to SocketClient
Signed-off-by: BoHong Li <[email protected]>
1 parent 968c12f commit f317171

File tree

4 files changed

+339
-109
lines changed

4 files changed

+339
-109
lines changed

lib/realtime.js

Lines changed: 117 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -805,15 +805,29 @@ class SocketClient {
805805
// reveiced when user logout or changed
806806
this.socket.on('user changed', this.userChangedEventHandler.bind(this))
807807
// delete a note
808-
this.socket.on('delete', this.deleteNote.bind(this))
808+
this.socket.on('delete', this.deleteNoteEventHandler.bind(this))
809+
// received note permission change request
810+
this.socket.on('permission', this.permissionChangeEventHandler.bind(this))
809811
}
810812

811813
isUserLoggedIn () {
812814
return this.socket.request.user && this.socket.request.user.logged_in
813815
}
814816

815-
getCurrentLoggedInUserId () {
816-
return get(this.socket, 'request.user.id')
817+
isNoteAndUserExists () {
818+
const note = getNoteFromNotePool(this.socket.noteId)
819+
const user = getUserFromUserPool(this.socket.id)
820+
return note && user
821+
}
822+
823+
isNoteOwner () {
824+
const note = this.getCurrentNote()
825+
return get(note, 'owner') === this.getCurrentLoggedInUserId()
826+
}
827+
828+
isAnonymousEnable () {
829+
//TODO: move this method to config module
830+
return config.allowAnonymous || config.allowAnonymousEdits
817831
}
818832

819833
disconnectSocketOnNote (note) {
@@ -827,28 +841,83 @@ class SocketClient {
827841
})
828842
}
829843

844+
getCurrentUser () {
845+
if (!this.socket.id) return
846+
return getUserFromUserPool(this.socket.id)
847+
}
848+
849+
getCurrentLoggedInUserId () {
850+
return get(this.socket, 'request.user.id')
851+
}
852+
853+
getCurrentNote () {
854+
if (!this.socket.noteId) return
855+
return getNoteFromNotePool(this.socket.noteId)
856+
}
857+
858+
getNoteChannel () {
859+
return this.socket.broadcast.to(this.socket.noteId)
860+
}
861+
830862
async destroyNote (id) {
831863
return models.Note.destroy({
832864
where: { id: id }
833865
})
834866
}
835867

836-
deleteNote () {
837-
// need login to do more actions
838-
if (this.isUserLoggedIn() && this.isNoteAndUserExists()) {
839-
const note = this.getCurrentNote()
840-
// Only owner can delete note
841-
if (note.owner && note.owner === this.getCurrentLoggedInUserId()) {
842-
this.destroyNote(note.id)
843-
.then((successRows) => {
844-
if (!successRows) return
845-
this.disconnectSocketOnNote(note)
846-
})
847-
.catch(function (err) {
848-
return logger.error('delete note failed: ' + err)
868+
async changeNotePermission (newPermission) {
869+
const changedRows = await models.Note.update({
870+
permission: newPermission
871+
}, {
872+
where: {
873+
id: this.getCurrentNote().id
874+
}
875+
})
876+
if (changedRows !== 1) {
877+
throw new Error(`update database failed, cannot set permission ${newPermission} to note ${this.getCurrentNote().id}`)
878+
}
879+
}
880+
881+
notifyPermissionChanged () {
882+
realtime.io.to(this.getCurrentNote().id).emit('permission', {
883+
permission: this.getCurrentNote().permission
884+
})
885+
this.getCurrentNote().socks.forEach((sock) => {
886+
if (sock) {
887+
if (!exports.checkViewPermission(sock.request, this.getCurrentNote())) {
888+
sock.emit('info', {
889+
code: 403
849890
})
891+
setTimeout(function () {
892+
sock.disconnect(true)
893+
}, 0)
894+
}
850895
}
896+
})
897+
}
898+
899+
refreshEventHandler () {
900+
exports.emitRefresh(this.socket)
901+
}
902+
903+
checkVersionEventHandler () {
904+
this.socket.emit('version', {
905+
version: config.fullversion,
906+
minimumCompatibleVersion: config.minimumCompatibleVersion
907+
})
908+
}
909+
910+
userStatusEventHandler (data) {
911+
if (!this.isNoteAndUserExists()) return
912+
const user = this.getCurrentUser()
913+
if (config.debug) {
914+
logger.info('SERVER received [' + this.socket.noteId + '] user status from [' + this.socket.id + ']: ' + JSON.stringify(data))
851915
}
916+
if (data) {
917+
user.idle = data.idle
918+
user.type = data.type
919+
}
920+
exports.emitUserStatus(this.socket)
852921
}
853922

854923
userChangedEventHandler () {
@@ -863,26 +932,6 @@ class SocketClient {
863932
exports.emitOnlineUsers(this.socket)
864933
}
865934

866-
getCurrentUser () {
867-
if (!this.socket.id) return
868-
return getUserFromUserPool(this.socket.id)
869-
}
870-
871-
getCurrentNote () {
872-
if (!this.socket.noteId) return
873-
return getNoteFromNotePool(this.socket.noteId)
874-
}
875-
876-
getNoteChannel () {
877-
return this.socket.broadcast.to(this.socket.noteId)
878-
}
879-
880-
isNoteAndUserExists () {
881-
const note = getNoteFromNotePool(this.socket.noteId)
882-
const user = getUserFromUserPool(this.socket.id)
883-
return note && user
884-
}
885-
886935
onlineUsersEventHandler () {
887936
if (!this.isNoteAndUserExists()) return
888937

@@ -921,28 +970,40 @@ class SocketClient {
921970
})
922971
}
923972

924-
checkVersionEventHandler () {
925-
this.socket.emit('version', {
926-
version: config.fullversion,
927-
minimumCompatibleVersion: config.minimumCompatibleVersion
928-
})
929-
}
930-
931-
refreshEventHandler () {
932-
exports.emitRefresh(this.socket)
973+
deleteNoteEventHandler () {
974+
// need login to do more actions
975+
if (this.isUserLoggedIn() && this.isNoteAndUserExists()) {
976+
const note = this.getCurrentNote()
977+
// Only owner can delete note
978+
if (note.owner && note.owner === this.getCurrentLoggedInUserId()) {
979+
this.destroyNote(note.id)
980+
.then((successRows) => {
981+
if (!successRows) return
982+
this.disconnectSocketOnNote(note)
983+
})
984+
.catch(function (err) {
985+
return logger.error('delete note failed: ' + err)
986+
})
987+
}
988+
}
933989
}
934990

935-
userStatusEventHandler (data) {
991+
permissionChangeEventHandler (permission) {
992+
if (!this.isUserLoggedIn()) return
936993
if (!this.isNoteAndUserExists()) return
937-
const user = this.getCurrentUser()
938-
if (config.debug) {
939-
logger.info('SERVER received [' + this.socket.noteId + '] user status from [' + this.socket.id + ']: ' + JSON.stringify(data))
940-
}
941-
if (data) {
942-
user.idle = data.idle
943-
user.type = data.type
944-
}
945-
exports.emitUserStatus(this.socket)
994+
995+
const note = this.getCurrentNote()
996+
// Only owner can change permission
997+
if (!this.isNoteOwner()) return
998+
if (!this.isAnonymousEnable() && permission === 'freely') return
999+
1000+
this.changeNotePermission(permission)
1001+
.then(() => {
1002+
console.log('---')
1003+
note.permission = permission
1004+
this.notifyPermissionChanged()
1005+
})
1006+
.catch(err => logger.error('update note permission failed: ' + err))
9461007
}
9471008

9481009
disconnectEventHandler () {
@@ -1009,52 +1070,6 @@ function connection (socket) {
10091070

10101071
const socketClient = new SocketClient(socket)
10111072
socketClient.registerEventHandler()
1012-
1013-
// received note permission change request
1014-
socket.on('permission', function (permission) {
1015-
// need login to do more actions
1016-
if (socket.request.user && socket.request.user.logged_in) {
1017-
var noteId = socket.noteId
1018-
if (!noteId || !notes[noteId]) return
1019-
var note = notes[noteId]
1020-
// Only owner can change permission
1021-
if (note.owner && note.owner === socket.request.user.id) {
1022-
if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return
1023-
note.permission = permission
1024-
models.Note.update({
1025-
permission: permission
1026-
}, {
1027-
where: {
1028-
id: noteId
1029-
}
1030-
}).then(function (count) {
1031-
if (!count) {
1032-
return
1033-
}
1034-
var out = {
1035-
permission: permission
1036-
}
1037-
realtime.io.to(note.id).emit('permission', out)
1038-
for (var i = 0, l = note.socks.length; i < l; i++) {
1039-
var sock = note.socks[i]
1040-
if (typeof sock !== 'undefined' && sock) {
1041-
// check view permission
1042-
if (!checkViewPermission(sock.request, note)) {
1043-
sock.emit('info', {
1044-
code: 403
1045-
})
1046-
setTimeout(function () {
1047-
sock.disconnect(true)
1048-
}, 0)
1049-
}
1050-
}
1051-
}
1052-
}).catch(function (err) {
1053-
return logger.error('update note permission failed: ' + err)
1054-
})
1055-
}
1056-
}
1057-
})
10581073
}
10591074

10601075
exports = module.exports = realtime
@@ -1070,6 +1085,7 @@ exports.emitRefresh = emitRefresh
10701085
exports.emitUserStatus = emitUserStatus
10711086
exports.disconnect = disconnect
10721087
exports.emitOnlineUsers = emitOnlineUsers
1088+
exports.checkViewPermission = checkViewPermission
10731089
exports.notes = notes
10741090
exports.users = users
10751091
exports.disconnectSocketQueue = disconnectSocketQueue

test/connectionQueue.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ describe('ConnectionQueue', function () {
7878
setTimeout(() => {
7979
assert(processSpy.called)
8080
done()
81-
}, 1)
81+
}, 10)
8282
})
8383

8484
it('should run process although error occurred', (done) => {
@@ -100,7 +100,7 @@ describe('ConnectionQueue', function () {
100100
assert(failedTask.called)
101101
assert(normalTask.called)
102102
done()
103-
}, 5)
103+
}, 10)
104104
})
105105

106106
it('should ignore trigger when event not complete', (done) => {
@@ -125,6 +125,6 @@ describe('ConnectionQueue', function () {
125125
setTimeout(() => {
126126
assert(processSpy.calledOnce)
127127
done()
128-
}, 2)
128+
}, 10)
129129
})
130130
})

test/realtime/realtime.test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,68 @@ describe('realtime', function () {
258258
})
259259
})
260260

261+
describe('checkViewPermission', function () {
262+
// role -> guest, loggedInUser, loggedInOwner
263+
const viewPermission = {
264+
freely: [true, true, true],
265+
editable: [true, true, true],
266+
limited: [false, true, true],
267+
locked: [true, true, true],
268+
protected: [false, true, true],
269+
private: [false, false, true]
270+
}
271+
const loggedInUserId = 'user1_id'
272+
const ownerUserId = 'user2_id'
273+
const guestReq = {}
274+
const loggedInUserReq = {
275+
user: {
276+
id: loggedInUserId,
277+
logged_in: true
278+
}
279+
}
280+
const loggedInOwnerReq = {
281+
user: {
282+
id: ownerUserId,
283+
logged_in: true
284+
}
285+
}
286+
287+
const note = {
288+
owner: ownerUserId
289+
}
290+
291+
let realtime
292+
293+
beforeEach(() => {
294+
mock('../../lib/logger', {
295+
error: () => {
296+
}
297+
})
298+
mock('../../lib/history', {})
299+
mock('../../lib/models', {
300+
Note: {
301+
parseNoteTitle: (data) => (data)
302+
}
303+
})
304+
mock('../../lib/config', {})
305+
realtime = require('../../lib/realtime')
306+
})
307+
308+
Object.keys(viewPermission).forEach(function (permission) {
309+
describe(permission, function () {
310+
beforeEach(() => {
311+
note.permission = permission
312+
})
313+
it('guest permission test', function () {
314+
assert(realtime.checkViewPermission(guestReq, note) === viewPermission[permission][0])
315+
})
316+
it('loggedIn User permission test', function () {
317+
assert(realtime.checkViewPermission(loggedInUserReq, note) === viewPermission[permission][1])
318+
})
319+
it('loggedIn Owner permission test', function () {
320+
assert(realtime.checkViewPermission(loggedInOwnerReq, note) === viewPermission[permission][2])
321+
})
322+
})
323+
})
324+
})
261325
})

0 commit comments

Comments
 (0)