From 54ff768844d4cd627fb3781373b942f078266401 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 23 Dec 2024 16:35:42 +0700 Subject: [PATCH 1/8] Upgrade Live Kit to latest version (#7524) Signed-off-by: Andrey Sobolev --- common/config/rush/pnpm-lock.yaml | 77 ++++++++++++++++------------- desktop/package.json | 2 +- desktop/src/ui/screenShare.ts | 2 +- dev/prod/src/platform.ts | 1 - plugins/love-resources/package.json | 6 +-- plugins/love-resources/src/utils.ts | 6 ++- server/backup/src/backup.ts | 1 - 7 files changed, 52 insertions(+), 43 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index dc88015b3e..697190fc01 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -42,11 +42,11 @@ dependencies: specifier: ^5.0.0 version: 5.0.0 '@livekit/krisp-noise-filter': - specifier: ~0.2.1 - version: 0.2.2(livekit-client@2.2.0) + specifier: ~0.2.13 + version: 0.2.13(livekit-client@2.7.5) '@livekit/track-processors': - specifier: ~0.3.1 - version: 0.3.2(livekit-client@2.2.0) + specifier: ~0.3.3 + version: 0.3.3(livekit-client@2.7.5) '@octokit/types': specifier: ^12.0.0 version: 12.6.0 @@ -1710,8 +1710,8 @@ dependencies: specifier: ^1.9.46 version: 1.10.56 livekit-client: - specifier: ^2.0.10 - version: 2.2.0 + specifier: ^2.7.5 + version: 2.7.5 livekit-server-sdk: specifier: ^2.0.10 version: 2.6.0 @@ -4782,18 +4782,16 @@ packages: resolution: {integrity: sha512-GlM2AbzrErd/TmLL3E8hAHmb5Q7VhDJp35vIbyPVA5Rz55LZuRr8pwL3qrwwkVNo05gMX1J44gURKb4MHQZo7w==} dev: false - /@livekit/krisp-noise-filter@0.2.2(livekit-client@2.2.0): - resolution: {integrity: sha512-jGAxvw07+6jF3iU7jEeZ6dnr2c2nSIW3J9zt18KM+dwVsLpLNAfFchlwUStpJEmQoDhw9sQgRSOOKpqHcjj7GQ==} + /@livekit/krisp-noise-filter@0.2.13(livekit-client@2.7.5): + resolution: {integrity: sha512-41+NgOIoKsbcIG60f1Hyj4m3zPHIiq7R8HV4pfCUSgr/hnshDIbV0d53Dy/2xqBnXEPCoIbTvFRb8vBllEmYCQ==} peerDependencies: livekit-client: ^2.0.8 dependencies: - livekit-client: 2.2.0 + livekit-client: 2.7.5 dev: false - /@livekit/protocol@1.16.0: - resolution: {integrity: sha512-xZZTZVh2FmWmUgNS3n+oGNbA4GcS4XOwhg8CWy75jenYxbgQ89ds7ixfMQ+F+oxktcXfJ1qsph086oRTlg8e5Q==} - dependencies: - '@bufbuild/protobuf': 1.10.0 + /@livekit/mutex@1.0.0: + resolution: {integrity: sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==} dev: false /@livekit/protocol@1.19.1: @@ -4802,14 +4800,20 @@ packages: '@bufbuild/protobuf': 1.10.0 dev: false - /@livekit/track-processors@0.3.2(livekit-client@2.2.0): - resolution: {integrity: sha512-4JUCzb7yIKoVsTo8J6FTzLZJHcI6DihfX/pGRDg0SOGaxprcDPrt8jaDBBTsnGBSXHeMxl2ugN+xQjdCWzLKEA==} + /@livekit/protocol@1.29.4: + resolution: {integrity: sha512-dsqxvABHilrMA0BU5m1w8cMWSVeDjV2ZUIUDClNQZju3c30DLMfEYDHU5nmXDfaaHjNIgoRbYR7upJMozG8JJg==} + dependencies: + '@bufbuild/protobuf': 1.10.0 + dev: false + + /@livekit/track-processors@0.3.3(livekit-client@2.7.5): + resolution: {integrity: sha512-C5mBWe34ie0pfhCf9zAD65D26ZytzW/xGrj8Zc1BGh5kLISFDVQlefO78LZWw9K5saTamk60O4fzSCKqWMPMLA==} peerDependencies: livekit-client: ^1.12.0 || ^2.1.0 dependencies: '@mediapipe/holistic': 0.5.1675471629 '@mediapipe/tasks-vision': 0.10.9 - livekit-client: 2.2.0 + livekit-client: 2.7.5 dev: false /@malept/cross-spawn-promise@2.0.0: @@ -8537,7 +8541,7 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} dependencies: - tslib: 2.6.2 + tslib: 2.7.0 dev: false /astral-regex@2.0.0: @@ -9265,7 +9269,7 @@ packages: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} dependencies: debug: 4.4.0 - tslib: 2.6.2 + tslib: 2.7.0 transitivePeerDependencies: - supports-color dev: false @@ -15378,17 +15382,18 @@ packages: resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} dev: false - /livekit-client@2.2.0: - resolution: {integrity: sha512-Iuk17l269P7SxvE3Gs3ExwoW3+lrz3BfiXOU7gTf/GqansxPx7Y+rfkLHWSBmvmAXOt+Rld4EFEpk8ARSagfnQ==} + /livekit-client@2.7.5: + resolution: {integrity: sha512-sPhHYwXvG75y1LDC50dDC9k6Z49L2vc/HcMRhzhi7yBca6ofPEebpB0bmPOry4ovrnFA+a8TL1pFR2mko1/clw==} dependencies: - '@livekit/protocol': 1.16.0 + '@livekit/mutex': 1.0.0 + '@livekit/protocol': 1.29.4 events: 3.3.0 loglevel: 1.9.1 sdp-transform: 2.14.2 ts-debounce: 4.0.0 - tslib: 2.6.2 + tslib: 2.7.0 typed-emitter: 2.1.0 - webrtc-adapter: 8.2.3 + webrtc-adapter: 9.0.1 dev: false /livekit-server-sdk@2.6.0: @@ -15642,7 +15647,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.6.2 + tslib: 2.7.0 dev: false /lowercase-keys@2.0.0: @@ -16477,7 +16482,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.7.0 dev: false /node-abi@3.55.0: @@ -18661,7 +18666,7 @@ packages: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} requiresBuild: true dependencies: - tslib: 2.6.2 + tslib: 2.7.0 dev: false optional: true @@ -20609,6 +20614,10 @@ packages: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false + /tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + dev: false + /tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} @@ -21371,8 +21380,8 @@ packages: - uglify-js dev: false - /webrtc-adapter@8.2.3: - resolution: {integrity: sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==} + /webrtc-adapter@9.0.1: + resolution: {integrity: sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==} engines: {node: '>=6.0.0', npm: '>=3.10.0'} dependencies: sdp: 3.2.0 @@ -23937,7 +23946,7 @@ packages: dev: false file:projects/desktop.tgz(bufferutil@4.0.8)(sass@1.71.1)(utf-8-validate@6.0.4): - resolution: {integrity: sha512-A11ToahoSZULhHUbTgjKyyMHDZ68BplLHTrHKKiOkZBooPhGcJZJsOLIMKNjJ5+rT+aQDeiSMFMl+2cfaeuh0A==, tarball: file:projects/desktop.tgz} + resolution: {integrity: sha512-dsC1AXo6G2w4mUGwtojyf7fmfs+/DtOZZ+BtgCQOlkvIJUCe2Ff8JiFgn78jAEz9tArOd553ctBfIxRIR/SiEw==, tarball: file:projects/desktop.tgz} id: file:projects/desktop.tgz name: '@rush-temp/desktop' version: 0.0.0 @@ -23973,7 +23982,7 @@ packages: file-loader: 6.2.0(webpack@5.97.1) fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.3.3)(webpack@5.97.1) html-webpack-plugin: 5.6.0(webpack@5.97.1) - livekit-client: 2.2.0 + livekit-client: 2.7.5 mini-css-extract-plugin: 2.8.0(webpack@5.97.1) node-loader: 2.0.0(webpack@5.97.1) postcss: 8.4.35 @@ -25547,13 +25556,13 @@ packages: dev: false file:projects/love-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2): - resolution: {integrity: sha512-dWrQ7MUtar6HnPE52l/G6rANBTPCKOs7mnI23PYyBiiKi2x7Gvs3rpFYiAF3lpTTxK5B5TZ/DThx0/YMylj5Fg==, tarball: file:projects/love-resources.tgz} + resolution: {integrity: sha512-xbjsBbX7ymXpU9Ua0cPKMevznsVwwejZYcFPmRpW+1WY1Kxy0pQcuGr+nV8qYSmqQa1g3RJMHTZLT/3RmohFBQ==, tarball: file:projects/love-resources.tgz} id: file:projects/love-resources.tgz name: '@rush-temp/love-resources' version: 0.0.0 dependencies: - '@livekit/krisp-noise-filter': 0.2.2(livekit-client@2.2.0) - '@livekit/track-processors': 0.3.2(livekit-client@2.2.0) + '@livekit/krisp-noise-filter': 0.2.13(livekit-client@2.7.5) + '@livekit/track-processors': 0.3.3(livekit-client@2.7.5) '@types/jest': 29.5.12 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) @@ -25564,7 +25573,7 @@ packages: eslint-plugin-promise: 6.1.1(eslint@8.56.0) eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.19)(ts-node@10.9.2) jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2) - livekit-client: 2.2.0 + livekit-client: 2.7.5 prettier: 3.2.5 prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.19) sass: 1.71.1 diff --git a/desktop/package.json b/desktop/package.json index 0093049c99..468cdd5751 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -219,7 +219,7 @@ "electron-store": "^8.2.0", "electron-log": "^5.1.7", "electron-updater": "^6.3.4", - "livekit-client": "^2.0.10", + "livekit-client": "^2.7.5", "@hcengineering/server-backup": "^0.6.0", "ws": "^8.18.0" }, diff --git a/desktop/src/ui/screenShare.ts b/desktop/src/ui/screenShare.ts index c7f121b430..4516519906 100644 --- a/desktop/src/ui/screenShare.ts +++ b/desktop/src/ui/screenShare.ts @@ -21,7 +21,7 @@ export function defineScreenShare (): void { throw new DeviceUnsupportedError('getDisplayMedia not supported') } - return await new Promise((resolve, reject) => { + return await new Promise>((resolve, reject) => { let wasSelected = false showPopup( diff --git a/dev/prod/src/platform.ts b/dev/prod/src/platform.ts index ac3a633e15..f4e20c97fa 100644 --- a/dev/prod/src/platform.ts +++ b/dev/prod/src/platform.ts @@ -256,7 +256,6 @@ export async function configurePlatform() { if (err.message.includes('Loading chunk') && i != 4) { continue } - Analytics.handleError(err) location.reload() } } diff --git a/plugins/love-resources/package.json b/plugins/love-resources/package.json index 8182753be7..9539c6cbca 100644 --- a/plugins/love-resources/package.json +++ b/plugins/love-resources/package.json @@ -59,9 +59,9 @@ "@hcengineering/view-resources": "^0.6.0", "@hcengineering/workbench": "^0.6.16", "@hcengineering/workbench-resources": "^0.6.1", - "@livekit/krisp-noise-filter": "~0.2.1", - "@livekit/track-processors": "~0.3.1", - "livekit-client": "^2.0.10", + "@livekit/krisp-noise-filter": "~0.2.13", + "@livekit/track-processors": "~0.3.3", + "livekit-client": "^2.7.5", "svelte": "^4.2.19" } } diff --git a/plugins/love-resources/src/utils.ts b/plugins/love-resources/src/utils.ts index e7a3164e2d..17bbe4c6c6 100644 --- a/plugins/love-resources/src/utils.ts +++ b/plugins/love-resources/src/utils.ts @@ -284,8 +284,10 @@ async function setKrispProcessor (pub: LocalTrackPublication): Promise { await pub.track.setProcessor(krispProcessor) await krispProcessor.setEnabled($myPreferences?.noiseCancellation ?? true) } catch (err: any) { - console.error(err) - Analytics.handleError(err) + if (err?.message !== 'SDK_ALREADY_INITIALIZED') { + console.error(err) + Analytics.handleError(err) + } } } } diff --git a/server/backup/src/backup.ts b/server/backup/src/backup.ts index 845d6b7469..bdd7badcf2 100644 --- a/server/backup/src/backup.ts +++ b/server/backup/src/backup.ts @@ -964,7 +964,6 @@ export async function backup ( break } } catch (err: any) { - console.error(err) ctx.error('failed to load chunks', { error: err }) if (idx !== undefined) { await ctx.with('closeChunk', {}, async () => { From 6a7d9e3bdab8e4c28ef57022a26a38b80603f2c9 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 23 Dec 2024 17:54:09 +0700 Subject: [PATCH 2/8] UBERF-8993: Fix handling of known errors (#7526) --- packages/platform/src/platform.ts | 1 + packages/presentation/src/pipeline.ts | 19 +++++++++++++++++-- plugins/client-resources/src/connection.ts | 7 ++++--- plugins/login-resources/src/utils.ts | 6 +++++- server/account/src/operations.ts | 6 ++++++ 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/platform/src/platform.ts b/packages/platform/src/platform.ts index 29ff120300..ec85cb5687 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -131,6 +131,7 @@ export default plugin(platformId, { BadError: '' as StatusCode, UnknownError: '' as StatusCode<{ message: string }>, InvalidId: '' as StatusCode<{ id: string }>, + ConnectionClosed: '' as StatusCode, LoadingPlugin: '' as StatusCode<{ plugin: string }>, NoLocationForPlugin: '' as StatusCode<{ plugin: Plugin }>, diff --git a/packages/presentation/src/pipeline.ts b/packages/presentation/src/pipeline.ts index cd75ef2672..4dc23a0470 100644 --- a/packages/presentation/src/pipeline.ts +++ b/packages/presentation/src/pipeline.ts @@ -18,7 +18,7 @@ import { type TxResult, type WithLookup } from '@hcengineering/core' -import { setPlatformStatus, unknownError, type Resource } from '@hcengineering/platform' +import platform, { PlatformError, setPlatformStatus, unknownError, type Resource } from '@hcengineering/platform' /** * @public @@ -112,7 +112,12 @@ export class PresentationPipelineImpl implements PresentationPipeline { return this.head !== undefined ? await this.head.findAll(_class, query, options) : await this.client.findAll(_class, query, options) - } catch (err) { + } catch (err: any) { + if (err instanceof PlatformError) { + if (err.status.code === platform.status.ConnectionClosed) { + return toFindResult([], -1) + } + } Analytics.handleError(err as Error) const status = unknownError(err) await setPlatformStatus(status) @@ -134,6 +139,11 @@ export class PresentationPipelineImpl implements PresentationPipeline { ? await this.head.findOne(_class, query, options) : await this.client.findOne(_class, query, options) } catch (err) { + if (err instanceof PlatformError) { + if (err.status.code === platform.status.ConnectionClosed) { + return + } + } Analytics.handleError(err as Error) const status = unknownError(err) await setPlatformStatus(status) @@ -163,6 +173,11 @@ export class PresentationPipelineImpl implements PresentationPipeline { return await this.head.tx(tx) } } catch (err) { + if (err instanceof PlatformError) { + if (err.status.code === platform.status.ConnectionClosed) { + return {} + } + } Analytics.handleError(err as Error) const status = unknownError(err) await setPlatformStatus(status) diff --git a/plugins/client-resources/src/connection.ts b/plugins/client-resources/src/connection.ts index aca8d47ea4..6c74eedcbc 100644 --- a/plugins/client-resources/src/connection.ts +++ b/plugins/client-resources/src/connection.ts @@ -50,10 +50,11 @@ import core, { } from '@hcengineering/core' import platform, { PlatformError, + Severity, + Status, UNAUTHORIZED, broadcastEvent, - getMetadata, - unknownError + getMetadata } from '@hcengineering/platform' import { HelloRequest, HelloResponse, RPCHandler, ReqId, type Response } from '@hcengineering/rpc' @@ -551,7 +552,7 @@ class Connection implements ClientConnection { }): Promise { return this.ctx.newChild('send-request', {}).with(data.method, {}, async (ctx) => { if (this.closed) { - throw new PlatformError(unknownError('connection closed')) + throw new PlatformError(new Status(Severity.ERROR, platform.status.ConnectionClosed, {})) } if (data.once === true) { diff --git a/plugins/login-resources/src/utils.ts b/plugins/login-resources/src/utils.ts index 60d7f0df31..b156067923 100644 --- a/plugins/login-resources/src/utils.ts +++ b/plugins/login-resources/src/utils.ts @@ -16,7 +16,7 @@ import { Analytics } from '@hcengineering/analytics' import { AccountRole, concatLink, type BaseWorkspaceInfo, type Doc, type Ref } from '@hcengineering/core' import { loginId, type LoginInfo, type OtpInfo, type Workspace, type WorkspaceLoginInfo } from '@hcengineering/login' -import { +import platform, { OK, PlatformError, Severity, @@ -1058,6 +1058,10 @@ export async function restorePassword (token: string, password: string): Promise } async function handleStatusError (message: string, err: Status): Promise { + if (err.code === platform.status.InvalidPassword || err.code === platform.status.AccountNotFound) { + // No need to send to analytics + return + } const label = await translate(err.code, err.params, 'en') Analytics.handleError(new Error(`${message}: ${label}`)) } diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index c35e5b0126..6b596f92ec 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -2637,6 +2637,12 @@ function wrap ( error: new Status(Severity.ERROR, platform.status.Unauthorized, {}) } } + if (status.code === platform.status.InvalidPassword || status.code === platform.status.AccountNotFound) { + ctx.warn('error', { status: status.code, err: err.message }) + return { + error: status + } + } if (status.code === platform.status.InternalServerError) { Analytics.handleError(err) ctx.error('error', { status, err }) From b5166df83240abbcedfae2644233999dba1ff160 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 24 Dec 2024 14:46:18 +0700 Subject: [PATCH 3/8] Bump mongo driver to 6.12 (#7537) Signed-off-by: Andrey Sobolev --- dev/tool/package.json | 2 +- pods/account/package.json | 2 +- pods/authProviders/package.json | 2 +- pods/workspace/package.json | 2 +- server/account-service/package.json | 2 +- server/account/package.json | 2 +- server/collaborator/package.json | 2 +- server/mongo/package.json | 4 ++-- server/tool/package.json | 2 +- services/ai-bot/pod-ai-bot/package.json | 2 +- .../analytics-collector/pod-analytics-collector/package.json | 2 +- services/calendar/pod-calendar/package.json | 2 +- services/github/pod-github/package.json | 2 +- services/gmail/pod-gmail/package.json | 2 +- services/sign/pod-sign/package.json | 2 +- services/telegram-bot/pod-telegram-bot/package.json | 2 +- services/telegram/pod-telegram/package.json | 4 ++-- 17 files changed, 19 insertions(+), 19 deletions(-) diff --git a/dev/tool/package.json b/dev/tool/package.json index a1259cfa35..00cd10da27 100644 --- a/dev/tool/package.json +++ b/dev/tool/package.json @@ -163,7 +163,7 @@ "got": "^11.8.3", "libphonenumber-js": "^1.9.46", "mime-types": "~2.1.34", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "postgres": "^3.4.5", "ws": "^8.18.0", "bufferutil": "^4.0.8", diff --git a/pods/account/package.json b/pods/account/package.json index 72096b3b06..303a642672 100644 --- a/pods/account/package.json +++ b/pods/account/package.json @@ -57,7 +57,7 @@ "@hcengineering/platform": "^0.6.11", "@hcengineering/auth-providers": "^0.6.0", "@hcengineering/core": "^0.6.32", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "koa": "^2.15.3", "koa-router": "^12.0.1", "koa-bodyparser": "^4.4.1", diff --git a/pods/authProviders/package.json b/pods/authProviders/package.json index 0f98b563ab..ec9cdc1f28 100644 --- a/pods/authProviders/package.json +++ b/pods/authProviders/package.json @@ -45,7 +45,7 @@ "@types/querystringify": "^2.0.2" }, "dependencies": { - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "@hcengineering/core": "^0.6.32", "@hcengineering/account": "^0.6.0", "passport-custom": "~1.1.1", diff --git a/pods/workspace/package.json b/pods/workspace/package.json index 464535b50d..7250da29af 100644 --- a/pods/workspace/package.json +++ b/pods/workspace/package.json @@ -51,7 +51,7 @@ "@hcengineering/workspace-service": "^0.6.0", "@hcengineering/platform": "^0.6.11", "@hcengineering/core": "^0.6.32", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "@hcengineering/server-token": "^0.6.11", "@hcengineering/server-core": "^0.6.1", "@hcengineering/model-all": "^0.6.0", diff --git a/server/account-service/package.json b/server/account-service/package.json index cb7c4c5048..ffc9c91242 100644 --- a/server/account-service/package.json +++ b/server/account-service/package.json @@ -48,7 +48,7 @@ "@hcengineering/auth-providers": "^0.6.0", "@hcengineering/core": "^0.6.32", "@hcengineering/mongo": "^0.6.1", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "koa": "^2.15.3", "koa-router": "^12.0.1", "koa-bodyparser": "^4.4.1", diff --git a/server/account/package.json b/server/account/package.json index 65141c4815..1911805861 100644 --- a/server/account/package.json +++ b/server/account/package.json @@ -40,7 +40,7 @@ "dependencies": { "@hcengineering/mongo": "^0.6.1", "@hcengineering/postgres": "^0.6.0", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "postgres": "^3.4.5", "@hcengineering/platform": "^0.6.11", "@hcengineering/core": "^0.6.32", diff --git a/server/collaborator/package.json b/server/collaborator/package.json index 8f67264e18..490e6b1832 100644 --- a/server/collaborator/package.json +++ b/server/collaborator/package.json @@ -67,7 +67,7 @@ "@hocuspocus/transformer": "^2.13.5", "@tiptap/core": "^2.6.6", "@tiptap/html": "^2.6.6", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "yjs": "^13.6.19", "y-prosemirror": "^1.2.12", "express": "^4.19.2", diff --git a/server/mongo/package.json b/server/mongo/package.json index ab483a767f..a3aba3fb9e 100644 --- a/server/mongo/package.json +++ b/server/mongo/package.json @@ -37,7 +37,7 @@ "@hcengineering/core": "^0.6.32", "@hcengineering/platform": "^0.6.11", "@hcengineering/server-core": "^0.6.1", - "mongodb": "^6.10.0", - "bson": "^6.10.0" + "mongodb": "^6.12.0", + "bson": "^6.10.1" } } diff --git a/server/tool/package.json b/server/tool/package.json index 51c9436fe2..d76f238055 100644 --- a/server/tool/package.json +++ b/server/tool/package.json @@ -40,7 +40,7 @@ "@types/js-yaml": "^4.0.9" }, "dependencies": { - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "@hcengineering/platform": "^0.6.11", "@hcengineering/core": "^0.6.32", "@hcengineering/contact": "^0.6.24", diff --git a/services/ai-bot/pod-ai-bot/package.json b/services/ai-bot/pod-ai-bot/package.json index 36eb699b37..16a35158b9 100644 --- a/services/ai-bot/pod-ai-bot/package.json +++ b/services/ai-bot/pod-ai-bot/package.json @@ -80,7 +80,7 @@ "fast-equals": "^5.0.1", "form-data": "^4.0.0", "js-tiktoken": "^1.0.14", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "openai": "^4.56.0", "ws": "^8.18.0" } diff --git a/services/analytics-collector/pod-analytics-collector/package.json b/services/analytics-collector/pod-analytics-collector/package.json index b1f6b2f9c6..57f423a19a 100644 --- a/services/analytics-collector/pod-analytics-collector/package.json +++ b/services/analytics-collector/pod-analytics-collector/package.json @@ -102,7 +102,7 @@ "cors": "^2.8.5", "dotenv": "~16.0.0", "express": "^4.19.2", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "puppeteer": "^22.6.1" } } diff --git a/services/calendar/pod-calendar/package.json b/services/calendar/pod-calendar/package.json index 33b20179a8..96465224f0 100644 --- a/services/calendar/pod-calendar/package.json +++ b/services/calendar/pod-calendar/package.json @@ -73,7 +73,7 @@ "googleapis": "^122.0.0", "google-auth-library": "^8.0.2", "jwt-simple": "^0.5.6", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "ws": "^8.18.0" } } diff --git a/services/github/pod-github/package.json b/services/github/pod-github/package.json index e61017f66f..e79287a3f5 100644 --- a/services/github/pod-github/package.json +++ b/services/github/pod-github/package.json @@ -112,7 +112,7 @@ "express": "^4.19.2", "cors": "^2.8.5", "body-parser": "^1.20.2", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "@hcengineering/setting": "^0.6.17", "@hcengineering/preference": "^0.6.13", "@hcengineering/login": "^0.6.12", diff --git a/services/gmail/pod-gmail/package.json b/services/gmail/pod-gmail/package.json index 751082143f..d60f1c110d 100644 --- a/services/gmail/pod-gmail/package.json +++ b/services/gmail/pod-gmail/package.json @@ -75,7 +75,7 @@ "google-auth-library": "^8.0.2", "gaxios": "^5.0.1", "jwt-simple": "^0.5.6", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "uuid": "^8.3.2" } } diff --git a/services/sign/pod-sign/package.json b/services/sign/pod-sign/package.json index 415be1da07..5c224d383e 100644 --- a/services/sign/pod-sign/package.json +++ b/services/sign/pod-sign/package.json @@ -71,6 +71,6 @@ "@signpdf/utils": "^3.2.4", "node-forge": "^1.3.1", "pdf-lib": "^1.12.0", - "mongodb": "^6.10.0" + "mongodb": "^6.12.0" } } diff --git a/services/telegram-bot/pod-telegram-bot/package.json b/services/telegram-bot/pod-telegram-bot/package.json index 989a4a7f54..ab800fdc7f 100644 --- a/services/telegram-bot/pod-telegram-bot/package.json +++ b/services/telegram-bot/pod-telegram-bot/package.json @@ -78,7 +78,7 @@ "dotenv": "~16.0.0", "express": "^4.19.2", "htmlparser2": "^9.0.0", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "otp-generator": "^4.0.1", "telegraf": "^4.16.3", "ws": "^8.18.0" diff --git a/services/telegram/pod-telegram/package.json b/services/telegram/pod-telegram/package.json index 03d52045ed..7ba116be49 100644 --- a/services/telegram/pod-telegram/package.json +++ b/services/telegram/pod-telegram/package.json @@ -44,7 +44,7 @@ "@types/jest": "^29.5.5", "ts-node-dev": "^2.0.0", "ts-standard": "^12.0.2", - "bson": "^6.10.0", + "bson": "^6.10.1", "typescript": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", @@ -76,7 +76,7 @@ "htmlparser2": "^9.0.0", "jwt-simple": "^0.5.6", "mime": "^3.0.0", - "mongodb": "^6.10.0", + "mongodb": "^6.12.0", "telegram": "2.22.2", "uuid": "^8.3.2", "ws": "^8.18.0" From 0db7adccd6c05020ab2d67b07523726276d222c6 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 24 Dec 2024 14:48:18 +0700 Subject: [PATCH 4/8] UBERF-8993: Fix some flacky tests because of login (#7535) Signed-off-by: Andrey Sobolev --- tests/sanity/tests/model/login-page.ts | 8 -------- tests/sanity/tests/utils.ts | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/sanity/tests/model/login-page.ts b/tests/sanity/tests/model/login-page.ts index c32dd8fc26..96d07f13df 100644 --- a/tests/sanity/tests/model/login-page.ts +++ b/tests/sanity/tests/model/login-page.ts @@ -59,14 +59,6 @@ export class LoginPage { // ASSERTS - async checkingNeedReLogin (): Promise { - if (await this.profileButton().isVisible()) { - await this.openProfileMenu() - await this.popupItemButton('Sign out').click() - await this.loginWithPassword().waitFor({ state: 'visible', timeout: 15000 }) - } - } - async checkIfErrorMessageIsShown (): Promise { await expect(this.invalidPasswordMessage()).toContainText('Invalid password') } diff --git a/tests/sanity/tests/utils.ts b/tests/sanity/tests/utils.ts index 2e6816341a..f1615c2dbb 100644 --- a/tests/sanity/tests/utils.ts +++ b/tests/sanity/tests/utils.ts @@ -258,8 +258,7 @@ export async function createAccount (request: APIRequestContext, data: SignUpDat export async function reLogin (page: Page, data: TestData): Promise { const loginPage: LoginPage = new LoginPage(page) - await loginPage.checkingNeedReLogin() - await (await page.goto(`${PlatformURI}`))?.finished() + await (await page.goto(`${PlatformURI}/login/login`))?.finished() await loginPage.login(data.userName, '1234') const swp = new SelectWorkspacePage(page) await swp.selectWorkspace(data.workspaceName) From 35981be705c6f7d5a11de2666ed90c909f259f1e Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 24 Dec 2024 17:39:51 +0700 Subject: [PATCH 5/8] UBERF-8993: Part2 (#7532) Signed-off-by: Andrey Sobolev --- .gitignore | 1 + packages/core/src/operator.ts | 26 ++++----- .../src/components/DocPopup.svelte | 9 ++-- .../src/components/ObjectSearchPopup.svelte | 16 +++--- packages/presentation/src/utils.ts | 54 ++++++++++++------- packages/ui/src/components/CodeForm.svelte | 3 ++ packages/ui/src/focus.ts | 4 +- packages/ui/src/popups.ts | 11 ++-- packages/ui/src/utils.ts | 8 ++- .../chat-message/ChatMessagePresenter.svelte | 3 +- .../src/components/AccountArrayEditor.svelte | 8 +-- .../src/components/UserBoxList.svelte | 11 ++-- .../src/components/Move.svelte | 2 +- plugins/login-resources/src/utils.ts | 6 ++- .../love-resources/src/components/Room.svelte | 4 +- .../src/components/EditVacancy.svelte | 12 ++++- .../src/components/KanbanCard.svelte | 12 ++--- .../src/components/ClassAttributes.svelte | 2 +- .../project/ProjectSpacePresenter.svelte | 6 ++- .../src/components/extensions.ts | 16 +++--- .../projects/ProjectSpacePresenter.svelte | 4 +- .../view-resources/src/components/Menu.svelte | 6 +-- .../components/filter/FilterTypePopup.svelte | 4 +- .../model/documents/document-content-page.ts | 1 + server/account/src/operations.ts | 7 +-- .../tests/documents/documents-content.spec.ts | 11 ++-- tests/sanity/tests/tracker/issues.spec.ts | 39 +++++++++----- .../sanity/tests/tracker/public-link.spec.ts | 6 ++- 28 files changed, 178 insertions(+), 114 deletions(-) diff --git a/.gitignore b/.gitignore index 4def1db296..d6cf72d06f 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,4 @@ tests/profiles dump **/logs/** dev/tool/history.json +.aider* diff --git a/packages/core/src/operator.ts b/packages/core/src/operator.ts index 612426b066..ce72bc973e 100644 --- a/packages/core/src/operator.ts +++ b/packages/core/src/operator.ts @@ -28,22 +28,23 @@ function $push (document: Doc, keyval: Record): void { if (doc[key] === undefined) { doc[key] = [] } - const val = keyval[key] - if (typeof val === 'object') { + const kvk = keyval[key] + if (typeof kvk === 'object' && kvk != null) { const arr = doc[key] as Array - const desc = val as Position + const desc = kvk as Position if ('$each' in desc) { - if (arr != null) { + if (arr != null && Array.isArray(arr)) { arr.splice(desc.$position ?? 0, 0, ...desc.$each) } } else { - arr.push(val) + arr.push(kvk) } } else { - if (doc[key] == null) { - doc[key] = [] + if (doc[key] === null || doc[key] === undefined) { + doc[key] = [kvk] + } else { + doc[key].push(kvk) } - doc[key].push(val) } } } @@ -55,15 +56,16 @@ function $pull (document: Doc, keyval: Record): void { doc[key] = [] } const arr = doc[key] as Array - if (typeof keyval[key] === 'object' && keyval[key] !== null) { - const { $in } = keyval[key] as PullArray + const kvk = keyval[key] + if (typeof kvk === 'object' && kvk !== null) { + const { $in } = kvk as PullArray doc[key] = (arr ?? []).filter((val) => { if ($in !== undefined) { return !$in.includes(val) } else { // We need to match all fields - for (const [kk, kv] of Object.entries(keyval[key])) { + for (const [kk, kv] of Object.entries(kvk)) { if (val[kk] !== kv) { return true } @@ -72,7 +74,7 @@ function $pull (document: Doc, keyval: Record): void { } }) } else { - doc[key] = (arr ?? []).filter((val) => val !== keyval[key]) + doc[key] = (arr ?? []).filter((val) => val !== kvk) } } } diff --git a/packages/presentation/src/components/DocPopup.svelte b/packages/presentation/src/components/DocPopup.svelte index 44e47e1d8a..fe7eb71575 100644 --- a/packages/presentation/src/components/DocPopup.svelte +++ b/packages/presentation/src/components/DocPopup.svelte @@ -93,13 +93,14 @@ dispatch('update', selectedObjects) } - const client = getClient() - let selection = 0 let list: ListView async function handleSelection (evt: Event | undefined, objects: Doc[], selection: number): Promise { const item = objects[selection] + if (item === undefined) { + return + } if (!multiSelect) { if (allowDeselect) { @@ -140,7 +141,7 @@ showPopup(c.component, c.props ?? {}, 'top', async (res) => { if (res != null) { // We expect reference to new object. - const newPerson = await client.findOne(_class, { _id: res }) + const newPerson = await getClient().findOne(_class, { _id: res }) if (newPerson !== undefined) { search = c.update?.(newPerson) ?? '' dispatch('created', newPerson) @@ -163,7 +164,7 @@ } function findObjectPresenter (_class: Ref>): void { - const presenterMixin = client.getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter) + const presenterMixin = getClient().getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter) if (presenterMixin?.presenter !== undefined) { getResource(presenterMixin.presenter) .then((result) => { diff --git a/packages/presentation/src/components/ObjectSearchPopup.svelte b/packages/presentation/src/components/ObjectSearchPopup.svelte index 01655d2c17..9092643874 100644 --- a/packages/presentation/src/components/ObjectSearchPopup.svelte +++ b/packages/presentation/src/components/ObjectSearchPopup.svelte @@ -46,7 +46,6 @@ let categories: ObjectSearchCategory[] = [] let categoryStatus: Record, number> = {} - const client = getClient() let category: ObjectSearchCategory | undefined const categoryQuery: DocumentQuery = { @@ -56,10 +55,12 @@ categoryQuery._id = { $in: allowCategory } } - client.findAll(presentation.class.ObjectSearchCategory, categoryQuery).then((r) => { - categories = r.filter((it) => hasResource(it.query)) - category = categories[0] - }) + void getClient() + .findAll(presentation.class.ObjectSearchCategory, categoryQuery) + .then((r) => { + categories = r.filter((it) => hasResource(it.query)) + category = categories[0] + }) const dispatch = createEventDispatcher() @@ -96,7 +97,7 @@ key.preventDefault() key.stopPropagation() const item = items[selection] - if (item) { + if (item != null) { dispatchItem(item) return true } else { @@ -106,7 +107,7 @@ return false } - export function done () {} + export function done (): void {} const updateItems = reduceCalls(async function updateItems ( cat: ObjectSearchCategory | undefined, @@ -120,6 +121,7 @@ const newCategoryStatus: Record, number> = {} const f = await getResource(cat.query) + const client = getClient() const result = await f(client, query, { in: relatedDocuments, nin: ignore }) // We need to sure, results we return is for proper category. if (cat._id === category?._id) { diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index b92c77d889..8abd129f4c 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -53,7 +53,7 @@ import { getRawCurrentLocation, workspaceId, type AnyComponent, type AnySvelteCo import view, { type AttributeCategory, type AttributeEditor } from '@hcengineering/view' import { deepEqual } from 'fast-equals' import { onDestroy } from 'svelte' -import { get, writable, type Writable } from 'svelte/store' +import { get, writable } from 'svelte/store' import { type KeyedAttribute } from '..' import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline' @@ -63,7 +63,7 @@ export { reduceCalls } from '@hcengineering/core' let liveQuery: LQ let rawLiveQuery: LQ -let client: TxOperations & Client & OptimisticTxes +let client: TxOperations & Client let pipeline: PresentationPipeline const txListeners: Array<(...tx: Tx[]) => void> = [] @@ -89,13 +89,11 @@ export function removeTxListener (l: (tx: Tx) => void): void { } } -export interface OptimisticTxes { - pendingCreatedDocs: Writable, boolean>> -} - export const uiContext = new MeasureMetricsContext('client-ui', {}) -class UIClient extends TxOperations implements Client, OptimisticTxes { +export const pendingCreatedDocs = writable, boolean>>({}) + +class UIClient extends TxOperations implements Client { hook = getMetadata(plugin.metadata.ClientHook) constructor ( client: Client, @@ -105,14 +103,9 @@ class UIClient extends TxOperations implements Client, OptimisticTxes { } protected pendingTxes = new Set>() - protected _pendingCreatedDocs = writable, boolean>>({}) - - get pendingCreatedDocs (): typeof this._pendingCreatedDocs { - return this._pendingCreatedDocs - } async doNotify (...tx: Tx[]): Promise { - const pending = get(this._pendingCreatedDocs) + const pending = get(pendingCreatedDocs) let pendingUpdated = false tx.forEach((t) => { if (this.pendingTxes.has(t._id)) { @@ -129,7 +122,7 @@ class UIClient extends TxOperations implements Client, OptimisticTxes { } }) if (pendingUpdated) { - this._pendingCreatedDocs.set(pending) + pendingCreatedDocs.set(pending) } // We still want to notify about all transactions because there might be queries created after @@ -214,9 +207,9 @@ class UIClient extends TxOperations implements Client, OptimisticTxes { } if (innerTx._class === core.class.TxCreateDoc) { - const pending = get(this._pendingCreatedDocs) + const pending = get(pendingCreatedDocs) pending[innerTx.objectId] = true - this._pendingCreatedDocs.set(pending) + pendingCreatedDocs.set(pending) } this.pendingTxes.add(tx._id) @@ -231,11 +224,33 @@ class UIClient extends TxOperations implements Client, OptimisticTxes { } } +const hierarchyProxy = new Proxy( + {}, + { + get (target, p, receiver) { + const h = client.getHierarchy() + return Reflect.get(h, p) + } + } +) as TxOperations & Client + +// We need a proxy to handle all the calls to the proper client. +const clientProxy = new Proxy( + {}, + { + get (target, p, receiver) { + if (p === 'getHierarchy') { + return () => hierarchyProxy + } + return Reflect.get(client, p) + } + } +) as TxOperations & Client /** * @public */ -export function getClient (): TxOperations & Client & OptimisticTxes { - return client +export function getClient (): TxOperations & Client { + return clientProxy } let txQueue: Tx[] = [] @@ -252,6 +267,7 @@ export function addRefreshListener (r: RefreshListener): void { * @public */ export async function setClient (_client: Client): Promise { + pendingCreatedDocs.set({}) if (liveQuery !== undefined) { await liveQuery.close() } @@ -700,7 +716,7 @@ export function isSpace (space: Doc): space is Space { } export function isSpaceClass (_class: Ref>): boolean { - return getClient().getHierarchy().isDerived(_class, core.class.Space) + return client.getHierarchy().isDerived(_class, core.class.Space) } export function setPresentationCookie (token: string, workspaceId: string): void { diff --git a/packages/ui/src/components/CodeForm.svelte b/packages/ui/src/components/CodeForm.svelte index 3481922172..0c885e26cf 100644 --- a/packages/ui/src/components/CodeForm.svelte +++ b/packages/ui/src/components/CodeForm.svelte @@ -59,6 +59,9 @@ } function onKeydown (e: KeyboardEvent): void { + if (e.key === undefined) { + return + } const key = e.key.toLowerCase() const target = e.target as HTMLInputElement if (key !== 'backspace' && key !== 'delete') return diff --git a/packages/ui/src/focus.ts b/packages/ui/src/focus.ts index 820a81b73c..3a73ebecb6 100644 --- a/packages/ui/src/focus.ts +++ b/packages/ui/src/focus.ts @@ -63,7 +63,7 @@ class FocusManagerImpl implements FocusManager { return } this.current = this.elements.findIndex((it) => it.id === idx) ?? 0 - this.elements[Math.abs(this.current) % this.elements.length].focus() + this.elements[Math.abs(this.current) % this.elements.length]?.focus() } setFocusPos (order: number): void { @@ -73,7 +73,7 @@ class FocusManagerImpl implements FocusManager { const idx = this.elements.findIndex((it) => it.order === order) if (idx !== undefined) { this.current = idx - this.elements[Math.abs(this.current) % this.elements.length].focus() + this.elements[Math.abs(this.current) % this.elements.length]?.focus() } } diff --git a/packages/ui/src/popups.ts b/packages/ui/src/popups.ts index d5447490e5..b2e53371fa 100644 --- a/packages/ui/src/popups.ts +++ b/packages/ui/src/popups.ts @@ -149,16 +149,17 @@ export function closePopup (category?: string): void { } else { for (let i = popups.length - 1; i >= 0; i--) { if (popups[i].type !== 'popup') continue - if ((popups[i] as CompAndProps).options.fixed !== true) { - const isClosing = (popups[i] as CompAndProps).closing ?? false + const popi = popups[i] as CompAndProps + if (popi.options.fixed !== true) { + const isClosing = popi.closing ?? false if (popups[i].type === 'popup') { - ;(popups[i] as CompAndProps).closing = true + popi.closing = true } if (!isClosing) { // To prevent possible recursion, we need to check if we call some code from popup close, to do close. - ;(popups[i] as CompAndProps).onClose?.(undefined) + popi.onClose?.(undefined) } - ;(popups[i] as CompAndProps).closing = false + popi.closing = false popups.splice(i, 1) break } diff --git a/packages/ui/src/utils.ts b/packages/ui/src/utils.ts index 60c88455e2..1d3c88fd81 100644 --- a/packages/ui/src/utils.ts +++ b/packages/ui/src/utils.ts @@ -214,8 +214,12 @@ export function replaceURLs (text: string): string { * @returns {string} string with parsed URL */ export function parseURL (text: string): string { - const matches = autolinker.parse(text, { urls: true }) - return matches.length > 0 ? matches[0].getAnchorHref() : '' + try { + const matches = autolinker.parse(text ?? '', { urls: true }) + return matches.length > 0 ? matches[0].getAnchorHref() : '' + } catch (err: any) { + return '' + } } /** diff --git a/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte b/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte index 5dc8ed6605..295f79e975 100644 --- a/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte +++ b/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte @@ -16,7 +16,7 @@ import contact, { Person, PersonAccount } from '@hcengineering/contact' import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources' import { Class, Doc, getCurrentAccount, Markup, Ref, Space, WithLookup } from '@hcengineering/core' - import { getClient, MessageViewer } from '@hcengineering/presentation' + import { getClient, MessageViewer, pendingCreatedDocs } from '@hcengineering/presentation' import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources' import { getDocLinkTitle } from '@hcengineering/view-resources' import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui' @@ -58,7 +58,6 @@ export let onReply: ((message: ActivityMessage) => void) | undefined = undefined const client = getClient() - const { pendingCreatedDocs } = client const hierarchy = client.getHierarchy() const STALE_TIMEOUT_MS = 5000 const currentAccount = getCurrentAccount() diff --git a/plugins/contact-resources/src/components/AccountArrayEditor.svelte b/plugins/contact-resources/src/components/AccountArrayEditor.svelte index c336ac027c..edea6072c9 100644 --- a/plugins/contact-resources/src/components/AccountArrayEditor.svelte +++ b/plugins/contact-resources/src/components/AccountArrayEditor.svelte @@ -82,9 +82,11 @@ included = [] } - $: employees = Array.from( - (value ?? []).map((it) => $personAccountByIdStore.get(it as Ref)?.person) - ).filter((it) => it !== undefined) as Ref[] + $: employees = Array.isArray(value) + ? (Array.from((value ?? []).map((it) => $personAccountByIdStore.get(it as Ref)?.person)).filter( + (it) => it !== undefined + ) as Ref[]) + : [] $: docQuery = excluded.length === 0 && included.length === 0 diff --git a/plugins/contact-resources/src/components/UserBoxList.svelte b/plugins/contact-resources/src/components/UserBoxList.svelte index c838460ab7..ee1b3a16dd 100644 --- a/plugins/contact-resources/src/components/UserBoxList.svelte +++ b/plugins/contact-resources/src/components/UserBoxList.svelte @@ -13,7 +13,7 @@ // limitations under the License. --> {#if object && renderChannel && visible} +
+ +
[] = [] export let adaptive: HeaderAdaptive = 'default' export let hideActions: boolean = false @@ -118,6 +120,12 @@ {/if} + + {#if withPresence && object} + + {/if} + + {#if withSearch} { - typingInfo = res - }) - } else { - typingInfoQuery.unsubscribe() - } + let typingInfo: PresenceTyping[] = [] + $: presence = $presenceByObjectId.get(object._id) ?? [] + $: typingInfo = presence.map((p) => p.presence.typing).filter((p) => p !== undefined) function clear (): void { currentMessage = getDefault() @@ -111,29 +107,18 @@ async function deleteTypingInfo (): Promise { if (!withTypingInfo) return - const myTypingInfo = typingInfo.find((info) => info.person === me.person) - if (myTypingInfo === undefined) return - await client.remove(myTypingInfo) + const room = { objectId: object._id, objectClass: object._class } + updateMyPresence(room, { typing: undefined }) } async function updateTypingInfo (): Promise { if (!withTypingInfo) return - const myTypingInfo = typingInfo.find((info) => info.person === me.person) - if (myTypingInfo === undefined) { - await client.createDoc(chunter.class.TypingInfo, getSpace(object), { - objectId: object._id, - objectClass: object._class, - person: me.person, - lastTyping: Date.now() - }) - } else { - throttle.call(() => { - void client.update(myTypingInfo, { - lastTyping: Date.now() - }) - }) - } + throttle.call(() => { + const room = { objectId: object._id, objectClass: object._class } + const typing = { person: me.person, lastTyping: Date.now() } + updateMyPresence(room, { typing }) + }) } function onUpdate (event: CustomEvent): void { diff --git a/plugins/chunter-resources/src/types.ts b/plugins/chunter-resources/src/types.ts new file mode 100644 index 0000000000..a7194a266b --- /dev/null +++ b/plugins/chunter-resources/src/types.ts @@ -0,0 +1,22 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Person } from '@hcengineering/contact' +import type { Ref, Timestamp } from '@hcengineering/core' + +export interface PresenceTyping { + person: Ref + lastTyping: Timestamp +} diff --git a/plugins/presence-resources/.eslintrc.js b/plugins/presence-resources/.eslintrc.js new file mode 100644 index 0000000000..bb8fd7450d --- /dev/null +++ b/plugins/presence-resources/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'], + parserOptions: { tsconfigRootDir: __dirname } +} diff --git a/plugins/presence-resources/.prettierrc b/plugins/presence-resources/.prettierrc new file mode 100644 index 0000000000..792942803a --- /dev/null +++ b/plugins/presence-resources/.prettierrc @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "trailingComma": "none", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "printWidth": 120, + "useTabs": false, + "bracketSpacing": true, + "proseWrap": "preserve", + "plugins": [ + "prettier-plugin-svelte" + ], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} \ No newline at end of file diff --git a/plugins/presence-resources/config/rig.json b/plugins/presence-resources/config/rig.json new file mode 100644 index 0000000000..bcad6f7c33 --- /dev/null +++ b/plugins/presence-resources/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "ui" +} diff --git a/plugins/presence-resources/jest.config.js b/plugins/presence-resources/jest.config.js new file mode 100644 index 0000000000..3656e284d3 --- /dev/null +++ b/plugins/presence-resources/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'] +} diff --git a/plugins/presence-resources/package.json b/plugins/presence-resources/package.json new file mode 100644 index 0000000000..3b0c799f18 --- /dev/null +++ b/plugins/presence-resources/package.json @@ -0,0 +1,54 @@ +{ + "name": "@hcengineering/presence-resources", + "version": "0.6.0", + "main": "src/index.ts", + "author": "Hardcore Engineering Inc.", + "license": "EPL-2.0", + "scripts": { + "build": "compile ui", + "build:docs": "api-extractor run --local", + "format": "format src", + "svelte-check": "do-svelte-check", + "_phase:svelte-check": "do-svelte-check", + "build:watch": "compile ui", + "_phase:build": "compile ui", + "_phase:format": "format src", + "_phase:validate": "compile validate" + }, + "devDependencies": { + "svelte-loader": "^3.2.0", + "sass": "^1.53.0", + "svelte-preprocess": "^5.1.3", + "@hcengineering/platform-rig": "^0.6.0", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "eslint-config-standard-with-typescript": "^40.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.4.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-svelte": "^2.35.1", + "prettier-plugin-svelte": "^3.2.2", + "eslint": "^8.54.0", + "prettier": "^3.1.0", + "svelte-check": "^3.6.9", + "typescript": "^5.3.3", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "@types/jest": "^29.5.5", + "svelte-eslint-parser": "^0.33.1" + }, + "dependencies": { + "@hcengineering/platform": "^0.6.11", + "@hcengineering/core": "^0.6.32", + "@hcengineering/presentation": "^0.6.3", + "@hcengineering/theme": "^0.6.5", + "@hcengineering/ui": "^0.6.15", + "@hcengineering/view": "^0.6.13", + "@hcengineering/view-resources": "^0.6.0", + "@hcengineering/contact": "^0.6.24", + "@hcengineering/contact-resources": "^0.6.0", + "@hcengineering/presence": "^0.6.0", + "svelte": "^4.2.19", + "fast-equals": "^5.0.1" + } +} diff --git a/plugins/presence-resources/postcss.config.js b/plugins/presence-resources/postcss.config.js new file mode 100644 index 0000000000..88752c6cb0 --- /dev/null +++ b/plugins/presence-resources/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('autoprefixer') + ] +} diff --git a/plugins/presence-resources/src/client.ts b/plugins/presence-resources/src/client.ts new file mode 100644 index 0000000000..64337d265a --- /dev/null +++ b/plugins/presence-resources/src/client.ts @@ -0,0 +1,174 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { type Ref, concatLink, getCurrentAccount } from '@hcengineering/core' +import { type Person, type PersonAccount } from '@hcengineering/contact' +import { getMetadata } from '@hcengineering/platform' +import presence from '@hcengineering/presence' +import presentation from '@hcengineering/presentation' +import { type Unsubscriber, get } from 'svelte/store' + +import { myPresence, onPersonUpdate, onPersonLeave } from './store' +import { type RoomPresence } from './types' + +interface Message { + id: Ref + type: 'update' | 'remove' + presence?: RoomPresence[] + lastUpdate?: number +} + +export class PresenceClient implements Disposable { + private ws: WebSocket | null = null + private closed = false + private reconnectTimeout: number | undefined + private readonly reconnectInterval = 1000 + + private presence: RoomPresence[] + private readonly myPresenceUnsub: Unsubscriber + + constructor (private readonly url: string | URL) { + this.presence = get(myPresence) + this.myPresenceUnsub = myPresence.subscribe((presence) => { + this.handlePresenceChanged(presence) + }) + + this.connect() + } + + close (): void { + this.closed = true + clearTimeout(this.reconnectTimeout) + + this.myPresenceUnsub() + + if (this.ws !== null) { + this.ws.close() + this.ws = null + } + } + + private connect (): void { + try { + const ws = new WebSocket(this.url) + this.ws = ws + + ws.onopen = () => { + if (this.ws !== ws) { + return + } + + this.handleConnect() + } + + ws.onclose = (event: CloseEvent) => { + if (this.ws !== ws) { + ws.close() + return + } + + this.reconnect() + } + + ws.onmessage = (event: MessageEvent) => { + if (this.closed || this.ws !== ws) { + return + } + + this.handleMessage(event.data) + } + + ws.onerror = (event: Event) => { + if (this.ws !== ws) { + return + } + + console.log('client websocket error', event) + } + } catch (err: any) { + this.reconnect() + } + } + + private reconnect (): void { + clearTimeout(this.reconnectTimeout) + + if (!this.closed) { + this.reconnectTimeout = window.setTimeout(() => { + this.connect() + }, this.reconnectInterval) + } + } + + private handleConnect (): void { + const me = getCurrentAccount() as PersonAccount + this.sendPresence(me.person, this.presence) + } + + private handleMessage (data: string): void { + try { + const message = JSON.parse(data) as Message + if (message.type === 'update' && message.presence !== undefined) { + onPersonUpdate(message.id, message.presence ?? []) + } else if (message.type === 'remove') { + onPersonLeave(message.id) + } else { + console.warn('Unknown message type', message) + } + } catch (err: any) { + console.error('Error parsing message', err, data) + } + } + + private handlePresenceChanged (presence: RoomPresence[]): void { + const me = getCurrentAccount() as PersonAccount + this.presence = presence + this.sendPresence(me.person, this.presence) + } + + private sendPresence (person: Ref, presence: RoomPresence[]): void { + if (!this.closed && this.ws !== null && this.ws.readyState === WebSocket.OPEN) { + const message: Message = { id: person, type: 'update', presence } + this.ws.send(JSON.stringify(message)) + } + } + + [Symbol.dispose] (): void { + this.close() + } +} + +export function connect (): PresenceClient | undefined { + const workspaceId = getMetadata(presentation.metadata.WorkspaceId) + if (workspaceId === undefined) { + console.warn('Workspace ID is not defined') + return undefined + } + + const token = getMetadata(presentation.metadata.Token) + + const presenceUrl = getMetadata(presence.metadata.PresenceUrl) + if (presenceUrl === undefined || presenceUrl === '') { + console.warn('Presence URL is not defined') + return undefined + } + + const url = new URL(concatLink(presenceUrl, workspaceId)) + if (token !== undefined) { + url.searchParams.set('token', token) + } + + return new PresenceClient(url) +} diff --git a/plugins/presence-resources/src/components/Presence.svelte b/plugins/presence-resources/src/components/Presence.svelte new file mode 100644 index 0000000000..3d3b52d183 --- /dev/null +++ b/plugins/presence-resources/src/components/Presence.svelte @@ -0,0 +1,26 @@ + + + + +{#key object._id} + +{/key} diff --git a/plugins/presence-resources/src/components/PresenceAvatars.svelte b/plugins/presence-resources/src/components/PresenceAvatars.svelte new file mode 100644 index 0000000000..4708b5e079 --- /dev/null +++ b/plugins/presence-resources/src/components/PresenceAvatars.svelte @@ -0,0 +1,50 @@ + + + + +{#if items.length > 0} + {#if combine} + + {:else} +
+ {#each items as item} + {@const person = $personByIdStore.get(item)} + {#if person} +
+ +
+ {/if} + {/each} +
+ {/if} +{/if} diff --git a/plugins/presence-resources/src/components/PresenceContext.svelte b/plugins/presence-resources/src/components/PresenceContext.svelte new file mode 100644 index 0000000000..e46985ce17 --- /dev/null +++ b/plugins/presence-resources/src/components/PresenceContext.svelte @@ -0,0 +1,33 @@ + + + + + diff --git a/plugins/presence-resources/src/components/WorkbenchExtension.svelte b/plugins/presence-resources/src/components/WorkbenchExtension.svelte new file mode 100644 index 0000000000..1e93be67ae --- /dev/null +++ b/plugins/presence-resources/src/components/WorkbenchExtension.svelte @@ -0,0 +1,18 @@ + + + diff --git a/plugins/presence-resources/src/index.ts b/plugins/presence-resources/src/index.ts new file mode 100644 index 0000000000..3626781282 --- /dev/null +++ b/plugins/presence-resources/src/index.ts @@ -0,0 +1,33 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { type Resources } from '@hcengineering/platform' + +import Presence from './components/Presence.svelte' +import PresenceAvatars from './components/PresenceAvatars.svelte' +import WorkbenchExtension from './components/WorkbenchExtension.svelte' + +export { Presence, PresenceAvatars } +export { updateMyPresence, removeMyPresence, presenceByObjectId } from './store' + +export * from './types' + +export default async (): Promise => ({ + component: { + Presence, + PresenceAvatars, + WorkbenchExtension + } +}) diff --git a/plugins/presence-resources/src/plugin.ts b/plugins/presence-resources/src/plugin.ts new file mode 100644 index 0000000000..46bf751265 --- /dev/null +++ b/plugins/presence-resources/src/plugin.ts @@ -0,0 +1,19 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { mergeIds } from '@hcengineering/platform' +import presence, { presenceId } from '@hcengineering/presence' + +export default mergeIds(presenceId, presence, {}) diff --git a/plugins/presence-resources/src/store.ts b/plugins/presence-resources/src/store.ts new file mode 100644 index 0000000000..0888983c5a --- /dev/null +++ b/plugins/presence-resources/src/store.ts @@ -0,0 +1,81 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { type Doc, type Ref, getCurrentAccount } from '@hcengineering/core' +import { type Person, type PersonAccount } from '@hcengineering/contact' +import { type PresenceData } from '@hcengineering/presence' +import { type Readable, derived, writable } from 'svelte/store' + +import { type PersonRoomPresence, type Room, type RoomPresence } from './types' + +type PersonPresenceMap = Map, RoomPresence[]> + +export const myPresence = writable([]) +export const otherPresence = writable(new Map()) + +export const presenceByObjectId = derived, Map, PersonRoomPresence[]>>( + otherPresence, + ($presence) => { + const me = getCurrentAccount() as PersonAccount + const map = new Map, PersonRoomPresence[]>() + for (const [person, presences] of $presence.entries()) { + if (person === me.person) continue + + presences.forEach((presence) => { + const values = map.get(presence.room.objectId) ?? [] + values.push({ person, ...presence }) + + map.set(presence.room.objectId, values) + }) + } + + return map + } +) + +export function updateMyPresence (room: Room, presence: PresenceData): void { + myPresence.update((rooms) => { + const value = { room, presence, lastUpdated: Date.now() } + + const index = rooms.findIndex((it) => it.room.objectId === room.objectId) + if (index >= 0) { + rooms[index] = value + } else { + rooms.push(value) + } + + return rooms + }) +} + +export function removeMyPresence (room: Room): void { + myPresence.update((old) => { + return old.filter((it) => it.room.objectId !== room.objectId) + }) +} + +export function onPersonUpdate (person: Ref, presence: RoomPresence[]): void { + otherPresence.update((map) => { + map.set(person, [...presence]) + return map + }) +} + +export function onPersonLeave (person: Ref): void { + otherPresence.update((map) => { + map.delete(person) + return map + }) +} diff --git a/plugins/presence-resources/src/types.ts b/plugins/presence-resources/src/types.ts new file mode 100644 index 0000000000..5dc5bbb644 --- /dev/null +++ b/plugins/presence-resources/src/types.ts @@ -0,0 +1,42 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { type Class, type Doc, type Ref } from '@hcengineering/core' +import { type Person } from '@hcengineering/contact' +import { type PresenceData } from '@hcengineering/presence' + +export interface Room { + objectId: Ref + objectClass: Ref> +} + +export interface RoomPresence { + room: Room + presence: PresenceData + lastUpdated: number +} + +export interface PersonRoomPresence { + person: Ref + room: Room + presence: PresenceData + lastUpdated: number +} + +export interface PersonPresence { + person: Ref + presence: RoomPresence[] + lastUpdated: number +} diff --git a/plugins/presence-resources/svelte.config.js b/plugins/presence-resources/svelte.config.js new file mode 100644 index 0000000000..944a06f73e --- /dev/null +++ b/plugins/presence-resources/svelte.config.js @@ -0,0 +1,5 @@ +const sveltePreprocess = require('svelte-preprocess') + +module.exports = { + preprocess: sveltePreprocess() +}; \ No newline at end of file diff --git a/plugins/presence-resources/tsconfig.json b/plugins/presence-resources/tsconfig.json new file mode 100644 index 0000000000..fb6ccb2d18 --- /dev/null +++ b/plugins/presence-resources/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./types" + } +} \ No newline at end of file diff --git a/plugins/presence/.eslintrc.js b/plugins/presence/.eslintrc.js new file mode 100644 index 0000000000..72235dc283 --- /dev/null +++ b/plugins/presence/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/plugins/presence/.npmignore b/plugins/presence/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/plugins/presence/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/plugins/presence/config/rig.json b/plugins/presence/config/rig.json new file mode 100644 index 0000000000..0110930f55 --- /dev/null +++ b/plugins/presence/config/rig.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig" +} diff --git a/plugins/presence/jest.config.js b/plugins/presence/jest.config.js new file mode 100644 index 0000000000..2cfd408b67 --- /dev/null +++ b/plugins/presence/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + roots: ["./src"], + coverageReporters: ["text-summary", "html"] +} diff --git a/plugins/presence/package.json b/plugins/presence/package.json new file mode 100644 index 0000000000..5a02e0356c --- /dev/null +++ b/plugins/presence/package.json @@ -0,0 +1,48 @@ +{ + "name": "@hcengineering/presence", + "version": "0.6.0", + "main": "lib/index.js", + "svelte": "src/index.ts", + "types": "types/index.d.ts", + "files": [ + "lib/**/*", + "types/**/*", + "tsconfig.json" + ], + "author": "Hardcore Engineering Inc.", + "license": "EPL-2.0", + "scripts": { + "build": "compile", + "build:watch": "compile", + "format": "format src", + "test": "jest --passWithNoTests --silent", + "_phase:build": "compile transpile src", + "_phase:test": "jest --passWithNoTests --silent", + "_phase:format": "format src", + "_phase:validate": "compile validate" + }, + "devDependencies": { + "@hcengineering/platform-rig": "^0.6.0", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "eslint-config-standard-with-typescript": "^40.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.4.0", + "eslint-plugin-promise": "^6.1.1", + "eslint": "^8.54.0", + "prettier": "^3.1.0", + "typescript": "^5.3.3", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "@types/jest": "^29.5.5" + }, + "dependencies": { + "@hcengineering/platform": "^0.6.11", + "@hcengineering/core": "^0.6.32", + "@hcengineering/ui": "^0.6.15" + }, + "repository": "https://github.com/hcengineering/platform", + "publishConfig": { + "registry": "https://npm.pkg.github.com" + } +} diff --git a/plugins/presence/src/index.ts b/plugins/presence/src/index.ts new file mode 100644 index 0000000000..06c114f9ae --- /dev/null +++ b/plugins/presence/src/index.ts @@ -0,0 +1,21 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { presenceId, presencePlugin } from './plugin' + +export * from './types' + +export { presenceId } +export default presencePlugin diff --git a/plugins/presence/src/plugin.ts b/plugins/presence/src/plugin.ts new file mode 100644 index 0000000000..7f50ca851a --- /dev/null +++ b/plugins/presence/src/plugin.ts @@ -0,0 +1,33 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Metadata, Plugin } from '@hcengineering/platform' +import { plugin } from '@hcengineering/platform' +import { AnyComponent } from '@hcengineering/ui' + +/** @public */ +export const presenceId = 'presence' as Plugin + +export const presencePlugin = plugin(presenceId, { + metadata: { + PresenceUrl: '' as Metadata + }, + component: { + Presence: '' as AnyComponent, + PresenceAvatars: '' as AnyComponent + } +}) + +export default presencePlugin diff --git a/plugins/presence/src/types.ts b/plugins/presence/src/types.ts new file mode 100644 index 0000000000..07aa16820f --- /dev/null +++ b/plugins/presence/src/types.ts @@ -0,0 +1,16 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +export type PresenceData = Record diff --git a/plugins/presence/tsconfig.json b/plugins/presence/tsconfig.json new file mode 100644 index 0000000000..59e4fd4297 --- /dev/null +++ b/plugins/presence/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./types", + "tsBuildInfoFile": ".build/build.tsbuildinfo" + } +} \ No newline at end of file diff --git a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte index 1e02acb455..86c18ab186 100644 --- a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte +++ b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte @@ -71,7 +71,6 @@ } from '@hcengineering/text-editor' import { addTableHandler } from '../utils' - import CollaborationUsers from './CollaborationUsers.svelte' import TextEditorToolbar from './TextEditorToolbar.svelte' import { noSelectionRender, renderCursor } from './editor/collaboration' import { defaultEditorAttributes } from './editor/editorProps' @@ -584,11 +583,11 @@
-
+
{#if refActions.length > 0} diff --git a/pods/front/src/__start.ts b/pods/front/src/__start.ts index 2718d0f739..3a691438d0 100644 --- a/pods/front/src/__start.ts +++ b/pods/front/src/__start.ts @@ -37,6 +37,7 @@ startFront(metricsContext, { LOVE_ENDPOINT: process.env.LOVE_ENDPOINT ?? '', SIGN_URL: process.env.SIGN_URL ?? '', PRINT_URL: process.env.PRINT_URL ?? '', + PRESENCE_URL: process.env.PRESENCE_URL ?? '', PUSH_PUBLIC_KEY: process.env.PUSH_PUBLIC_KEY ?? '', POSTHOG_API_KEY: process.env.POSTHOG_API_KEY, POSTHOG_HOST: process.env.POSTHOG_HOST, diff --git a/rush.json b/rush.json index 80b75f0cc3..d167db3568 100644 --- a/rush.json +++ b/rush.json @@ -510,6 +510,21 @@ "projectFolder": "models/uploader", "shouldPublish": false }, + { + "packageName": "@hcengineering/presence", + "projectFolder": "plugins/presence", + "shouldPublish": false + }, + { + "packageName": "@hcengineering/presence-resources", + "projectFolder": "plugins/presence-resources", + "shouldPublish": false + }, + { + "packageName": "@hcengineering/model-presence", + "projectFolder": "models/presence", + "shouldPublish": false + }, { "packageName": "@hcengineering/collaborator-client", "projectFolder": "packages/collaborator-client",