diff --git a/packages/text/src/markup/__tests__/utils.test.ts b/packages/text/src/markup/__tests__/utils.test.ts index 39dac27437..d4fec9c7af 100644 --- a/packages/text/src/markup/__tests__/utils.test.ts +++ b/packages/text/src/markup/__tests__/utils.test.ts @@ -116,33 +116,53 @@ describe('areEqualMarkups', () => { expect(areEqualMarkups(markup, markup)).toBeTruthy() }) it('returns true for empty content', async () => { - const markup1 = '{"type":"doc","content":[{"type":"paragraph"}]}' - const markup2 = '{"type":"doc","content":[{"type":"paragraph","content":[]}]}' - expect(areEqualMarkups(markup1, markup2)).toBeTruthy() + expect( + areEqualMarkups('{"type":"doc","content":[]}', '{"type":"doc","content":[{"type":"paragraph"}]}') + ).toBeTruthy() + expect( + areEqualMarkups( + '{"type":"doc","content":[{"type":"paragraph"}]}', + '{"type":"doc","content":[{"type":"paragraph","content":[]}]}' + ) + ).toBeTruthy() }) - it('returns true for similar content', async () => { - const markup1 = '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}' - const markup2 = - '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","content":[],"marks":[],"attrs": {"color": null}}]}]}' - expect(areEqualMarkups(markup1, markup2)).toBeTruthy() + it('returns true for same content but empty marks and attrs', async () => { + expect( + areEqualMarkups( + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}', + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","content":[],"marks":[],"attrs": {"color": null}}]}]}' + ) + ).toBeTruthy() }) - it('returns false for the same content with different spaces', async () => { - const markup1 = '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}' - const markup2 = - '{"type":"doc","content":[{"type":"hardBreak"},{"type":"paragraph","content":[{"type":"text","text":"hello"}]},{"type":"hardBreak"}]}' - expect(areEqualMarkups(markup1, markup2)).toBeFalsy() + it('returns false for same content but trailing hard breaks', async () => { + expect( + areEqualMarkups( + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","marks":[]}]}]}', + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"},{"type":"hardBreak"}]}]}' + ) + ).toBeFalsy() + expect( + areEqualMarkups( + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}', + '{"type":"doc","content":[{"type":"hardBreak"},{"type":"paragraph","content":[{"type":"text","text":"hello"}]},{"type":"hardBreak"}]}' + ) + ).toBeFalsy() }) it('returns false for different content', async () => { - const markup1 = '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}' - const markup2 = '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"world"}]}]}' - expect(areEqualMarkups(markup1, markup2)).toBeFalsy() + expect( + areEqualMarkups( + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}', + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"world"}]}]}' + ) + ).toBeFalsy() }) it('returns false for different marks', async () => { - const markup1 = - '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","marks":[{"type":"bold"}]}]}]}' - const markup2 = - '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","marks":[{"type":"italic"}]}]}]}' - expect(areEqualMarkups(markup1, markup2)).toBeFalsy() + expect( + areEqualMarkups( + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","marks":[{"type":"bold"}]}]}]}', + '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello","marks":[{"type":"italic"}]}]}]}' + ) + ).toBeFalsy() }) }) diff --git a/packages/text/src/markup/utils.ts b/packages/text/src/markup/utils.ts index 4c6a4c0055..2020d37b52 100644 --- a/packages/text/src/markup/utils.ts +++ b/packages/text/src/markup/utils.ts @@ -45,7 +45,14 @@ export function areEqualMarkups (markup1: Markup, markup2: Markup): boolean { return true } - return equalNodes(markupToJSON(markup1), markupToJSON(markup2)) + const node1 = markupToJSON(markup1) + const node2 = markupToJSON(markup2) + + if (isEmptyNode(node1) && isEmptyNode(node2)) { + return true + } + + return equalNodes(node1, node2) } /** @public */ diff --git a/server/collaborator/src/extensions/authentication.ts b/server/collaborator/src/extensions/authentication.ts index 98b877084c..fae26d78f7 100644 --- a/server/collaborator/src/extensions/authentication.ts +++ b/server/collaborator/src/extensions/authentication.ts @@ -16,6 +16,7 @@ import { DocumentId, parseDocumentId } from '@hcengineering/collaborator-client' import { isReadonlyDoc } from '@hcengineering/collaboration' import { MeasureContext } from '@hcengineering/core' +import { decodeToken } from '@hcengineering/server-token' import { Extension, onAuthenticatePayload } from '@hocuspocus/server' import { getWorkspaceInfo } from '../account' @@ -34,14 +35,18 @@ export class AuthenticationExtension implements Extension { async onAuthenticate (data: onAuthenticatePayload): Promise { const ctx = this.configuration.ctx - const { workspaceUrl, collaborativeDoc } = parseDocumentId(data.documentName as DocumentId) + const { workspaceUrl: workspace, collaborativeDoc } = parseDocumentId(data.documentName as DocumentId) + + return await ctx.with('authenticate', { workspace }, async () => { + const token = decodeToken(data.token) + + ctx.info('authenticate', { workspace, mode: token.extra?.mode ?? '' }) - return await ctx.with('authenticate', { workspace: workspaceUrl }, async () => { // verify workspace can be accessed with the token const workspaceInfo = await getWorkspaceInfo(data.token) // verify workspace url in the document matches the token - if (workspaceInfo.workspace !== workspaceUrl) { + if (workspaceInfo.workspace !== workspace) { throw new Error('documentName must include workspace') } diff --git a/server/collaborator/src/extensions/storage.ts b/server/collaborator/src/extensions/storage.ts index 1945799539..b3f845a5d0 100644 --- a/server/collaborator/src/extensions/storage.ts +++ b/server/collaborator/src/extensions/storage.ts @@ -49,18 +49,21 @@ export class StorageExtension implements Extension { } async onLoadDocument ({ context, documentName }: withContext): Promise { - this.configuration.ctx.info('load document', { documentName }) + const { connectionId } = context + + this.configuration.ctx.info('load document', { documentName, connectionId }) return await this.loadDocument(documentName as DocumentId, context) } async onStoreDocument ({ context, documentName, document }: withContext): Promise { const { ctx } = this.configuration + const { connectionId } = context - ctx.info('store document', { documentName }) + ctx.info('store document', { documentName, connectionId }) const collaborators = this.collaborators.get(documentName) if (collaborators === undefined || collaborators.size === 0) { - ctx.info('no changes for document', { documentName }) + ctx.info('no changes for document', { documentName, connectionId }) return } @@ -83,7 +86,7 @@ export class StorageExtension implements Extension { const collaborators = this.collaborators.get(documentName) if (collaborators === undefined || !collaborators.has(connectionId)) { - ctx.info('no changes for document', { documentName }) + ctx.info('no changes for document', { documentName, connectionId }) return } diff --git a/server/collaborator/src/server.ts b/server/collaborator/src/server.ts index 0c1dbeee05..3be5be9337 100644 --- a/server/collaborator/src/server.ts +++ b/server/collaborator/src/server.ts @@ -178,9 +178,6 @@ export async function start ( return } - const token = decodeToken(authHeader.split(' ')[1]) - const context = getContext(token) - const request = req.body as RpcRequest const method = methods[request.method] if (method === undefined) { @@ -189,6 +186,10 @@ export async function start ( } res.status(400).send(response) } else { + const token = decodeToken(authHeader.split(' ')[1]) + const context = getContext(token) + + rpcCtx.info('rpc', { method: request.method, connectionId: context.connectionId, mode: token.extra?.mode ?? '' }) await rpcCtx.with('/rpc', { method: request.method }, async (ctx) => { try { const response: RpcResponse = await rpcCtx.with(request.method, {}, async (ctx) => {