diff --git a/packages/core/src/__tests__/client.test.ts b/packages/core/src/__tests__/client.test.ts index 21173e041b..dde583377a 100644 --- a/packages/core/src/__tests__/client.test.ts +++ b/packages/core/src/__tests__/client.test.ts @@ -28,8 +28,8 @@ import { fillConfiguration, pluginFilterTx } from '../utils' import { connect } from './connection' import { genMinModel } from './minmodel' -function filterPlugin (plugin: Plugin): (txes: Tx[]) => Promise { - return async (txes) => { +function filterPlugin (plugin: Plugin): (txes: Tx[]) => Tx[] { + return (txes) => { const configs = new Map, PluginConfiguration>() fillConfiguration(txes, configs) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 348e92fb3f..f0c66ecffd 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -215,7 +215,7 @@ export interface TxPersistenceStore { store: (model: LoadModelResponse) => Promise } -export type ModelFilter = (tx: Tx[]) => Promise +export type ModelFilter = (tx: Tx[]) => Tx[] /** * @public @@ -256,17 +256,21 @@ export async function createClient ( } const conn = await ctx.with('connect', {}, () => connect(txHandler)) - const { mode, current, addition } = await ctx.with('load-model', {}, (ctx) => loadModel(ctx, conn, txPersistence)) + let { mode, current, addition } = await ctx.with('load-model', {}, (ctx) => loadModel(ctx, conn, txPersistence)) switch (mode) { case 'same': case 'upgrade': - await ctx.with('build-model', {}, (ctx) => buildModel(ctx, current, modelFilter, hierarchy, model)) + ctx.withSync('build-model', {}, (ctx) => { + buildModel(ctx, current, modelFilter, hierarchy, model) + }) break case 'addition': - await ctx.with('build-model', {}, (ctx) => + ctx.withSync('build-model', {}, (ctx) => { buildModel(ctx, current.concat(addition), modelFilter, hierarchy, model) - ) + }) } + current = [] + addition = [] txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model) @@ -287,7 +291,7 @@ export async function createClient ( return } // Find all new transactions and apply - const { mode, current, addition } = await ctx.with('load-model', {}, (ctx) => loadModel(ctx, conn, txPersistence)) + let { mode, current, addition } = await ctx.with('load-model', {}, (ctx) => loadModel(ctx, conn, txPersistence)) switch (mode) { case 'upgrade': @@ -296,16 +300,21 @@ export async function createClient ( model = new ModelDb(hierarchy) ;(client as ClientImpl).setModel(hierarchy, model) - await ctx.with('build-model', {}, (ctx) => buildModel(ctx, current, modelFilter, hierarchy, model)) + ctx.withSync('build-model', {}, (ctx) => { + buildModel(ctx, current, modelFilter, hierarchy, model) + }) + current = [] await oldOnConnect?.(ClientConnectEvent.Upgraded, _lastTx, data) // No need to fetch more stuff since upgrade was happened. break case 'addition': - await ctx.with('build-model', {}, (ctx) => + ctx.withSync('build-model', {}, (ctx) => { buildModel(ctx, current.concat(addition), modelFilter, hierarchy, model) - ) + }) break } + current = [] + addition = [] if (lastTx === undefined) { // No need to do anything here since we connected. @@ -391,13 +400,13 @@ async function loadModel ( return { mode: 'addition', current: current.transactions, addition: result.transactions } } -async function buildModel ( +function buildModel ( ctx: MeasureContext, transactions: Tx[], modelFilter: ModelFilter | undefined, hierarchy: Hierarchy, model: ModelDb -): Promise { +): void { const systemTx: Tx[] = [] const userTx: Tx[] = [] @@ -416,7 +425,7 @@ async function buildModel ( let txes = systemTx.concat(userTx) if (modelFilter !== undefined) { - txes = await modelFilter(txes) + txes = modelFilter(txes) } ctx.withSync('build hierarchy', {}, () => { diff --git a/plugins/client-resources/src/connection.ts b/plugins/client-resources/src/connection.ts index 64e921f851..7f566857b9 100644 --- a/plugins/client-resources/src/connection.ts +++ b/plugins/client-resources/src/connection.ts @@ -87,6 +87,8 @@ class RequestPromise { chunks?: { index: number, data: FindResult }[] } +const globalRPCHandler: RPCHandler = new RPCHandler() + class Connection implements ClientConnection { private websocket: ClientSocket | null = null binaryMode = false @@ -115,7 +117,7 @@ class Connection implements ClientConnection { onConnect?: (event: ClientConnectEvent, lastTx: string | undefined, data: any) => Promise - rpcHandler = new RPCHandler() + rpcHandler: RPCHandler lastHash?: string @@ -145,6 +147,7 @@ class Connection implements ClientConnection { } else { this.sessionId = generateId() } + this.rpcHandler = opt?.useGlobalRPCHandler === true ? globalRPCHandler : new RPCHandler() this.onConnect = opt?.onConnect @@ -187,6 +190,8 @@ class Connection implements ClientConnection { this.pingResponse = Date.now() } } + }).catch((err) => { + this.ctx.error('failed to send msg', { err }) }) } else { clearInterval(this.interval) @@ -336,7 +341,9 @@ class Connection implements ClientConnection { helloResp.reconnect === true ? ClientConnectEvent.Reconnected : ClientConnectEvent.Connected, helloResp.lastTx, this.sessionId - ) + )?.catch((err) => { + this.ctx.error('failed to call onConnect', { err }) + }) this.schedulePing(socketId) return } else { @@ -345,7 +352,9 @@ class Connection implements ClientConnection { return } if (resp.result === pingConst) { - void this.sendRequest({ method: pingConst, params: [] }) + void this.sendRequest({ method: pingConst, params: [] }).catch((err) => { + this.ctx.error('failed to send ping', { err }) + }) return } if (resp.id !== undefined) { @@ -416,14 +425,21 @@ class Connection implements ClientConnection { promise.reject(new PlatformError(resp.error)) } else { if (request?.handleResult !== undefined) { - void request.handleResult(resp.result).then(() => { - promise.resolve(resp.result) - }) + void request + .handleResult(resp.result) + .then(() => { + promise.resolve(resp.result) + }) + .catch((err) => { + this.ctx.error('failed to handleResult', { err }) + }) } else { promise.resolve(resp.result) } } - void broadcastEvent(client.event.NetworkRequests, this.requests.size) + void broadcastEvent(client.event.NetworkRequests, this.requests.size).catch((err) => { + this.ctx.error('failed to broadcast', { err }) + }) } else { const txArr = Array.isArray(resp.result) ? (resp.result as Tx[]) : [resp.result as Tx] @@ -437,10 +453,14 @@ class Connection implements ClientConnection { this.handler(...txArr) clearTimeout(this.incomingTimer) - void broadcastEvent(client.event.NetworkRequests, this.requests.size + 1) + void broadcastEvent(client.event.NetworkRequests, this.requests.size + 1).catch((err) => { + this.ctx.error('failed to broadcast', { err }) + }) this.incomingTimer = setTimeout(() => { - void broadcastEvent(client.event.NetworkRequests, this.requests.size) + void broadcastEvent(client.event.NetworkRequests, this.requests.size).catch((err) => { + this.ctx.error('failed to broadcast', { err }) + }) }, 500) } } @@ -476,7 +496,9 @@ class Connection implements ClientConnection { this.dialTimer = setTimeout(() => { this.dialTimer = null if (!opened && !this.closed) { - void this.opt?.onDialTimeout?.() + void this.opt?.onDialTimeout?.()?.catch((err) => { + this.ctx.error('failed to handle dial timeout', { err }) + }) this.scheduleOpen(this.ctx, true) } }, dialTimeout) @@ -494,7 +516,9 @@ class Connection implements ClientConnection { return } if (event.data === pingConst) { - void this.sendRequest({ method: pingConst, params: [] }) + void this.sendRequest({ method: pingConst, params: [] }).catch((err) => { + this.ctx.error('failed to send ping', { err }) + }) return } if ( @@ -503,7 +527,9 @@ class Connection implements ClientConnection { ) { const text = new TextDecoder().decode(event.data) if (text === pingConst) { - void this.sendRequest({ method: pingConst, params: [] }) + void this.sendRequest({ method: pingConst, params: [] }).catch((err) => { + this.ctx.error('failed to send ping', { err }) + }) } if (text === pongConst) { this.pingResponse = Date.now() @@ -511,27 +537,32 @@ class Connection implements ClientConnection { return } if (event.data instanceof Blob) { - void event.data.arrayBuffer().then((data) => { - if (this.compressionMode && this.helloReceived) { + void event.data + .arrayBuffer() + .then((data) => { + if (this.compressionMode && this.helloReceived) { + try { + data = uncompress(data) + } catch (err: any) { + // Ignore + console.error(err) + } + } try { - data = uncompress(data) + const resp = this.rpcHandler.readResponse(data, this.binaryMode) + this.handleMsg(socketId, resp) } catch (err: any) { - // Ignore - console.error(err) + if (!this.helloReceived) { + // Just error and ignore for now. + console.error(err) + } else { + throw err + } } - } - try { - const resp = this.rpcHandler.readResponse(data, this.binaryMode) - this.handleMsg(socketId, resp) - } catch (err: any) { - if (!this.helloReceived) { - // Just error and ignore for now. - console.error(err) - } else { - throw err - } - } - }) + }) + .catch((err) => { + this.ctx.error('failed to decode array buffer', { err }) + }) } else { let data = event.data if (this.compressionMode && this.helloReceived) { @@ -561,7 +592,9 @@ class Connection implements ClientConnection { return } // console.log('client websocket closed', socketId, ev?.reason) - void broadcastEvent(client.event.NetworkRequests, -1) + void broadcastEvent(client.event.NetworkRequests, -1).catch((err) => { + this.ctx.error('failed broadcast', { err }) + }) this.scheduleOpen(this.ctx, true) } wsocket.onopen = () => { @@ -591,7 +624,9 @@ class Connection implements ClientConnection { if (opened) { console.error('client websocket error:', socketId, this.url, this.workspace, this.email) } - void broadcastEvent(client.event.NetworkRequests, -1) + void broadcastEvent(client.event.NetworkRequests, -1).catch((err) => { + this.ctx.error('failed to broadcast', { err }) + }) } } @@ -669,7 +704,11 @@ class Connection implements ClientConnection { ctx.withSync('send-data', {}, () => { sendData() }) - void ctx.with('broadcast-event', {}, () => broadcastEvent(client.event.NetworkRequests, this.requests.size)) + void ctx + .with('broadcast-event', {}, () => broadcastEvent(client.event.NetworkRequests, this.requests.size)) + .catch((err) => { + this.ctx.error('failed to broadcast', { err }) + }) if (data.method !== pingConst) { return await promise.promise } diff --git a/plugins/client-resources/src/index.ts b/plugins/client-resources/src/index.ts index 089b3bbd4d..e907be70d9 100644 --- a/plugins/client-resources/src/index.ts +++ b/plugins/client-resources/src/index.ts @@ -133,7 +133,7 @@ export default async () => { return await Promise.resolve(clientConnection) } - const modelFilter: ModelFilter = async (txes) => { + const modelFilter: ModelFilter = (txes) => { if (filterModel === 'client') { return returnClientTxes(txes) } @@ -177,7 +177,18 @@ function returnClientTxes (txes: Tx[]): Tx[] { 'templates:class:TemplateField' as Ref>, 'activity:class:DocUpdateMessageViewlet' as Ref>, 'core:class:PluginConfiguration' as Ref>, - 'core:class:DomainIndexConfiguration' as Ref> + 'core:class:DomainIndexConfiguration' as Ref>, + 'view:class:ViewletDescriptor' as Ref>, + 'presentation:class:ComponentPointExtension' as Ref>, + 'activity:class:ActivityMessagesFilter' as Ref>, + 'view:class:ActionCategory' as Ref>, + 'activity:class:ActivityExtension' as Ref>, + 'chunter:class:ChatMessageViewlet' as Ref>, + 'activity:class:ActivityMessageControl' as Ref>, + 'notification:class:ActivityNotificationViewlet' as Ref>, + 'setting:class:SettingsCategory' as Ref>, + 'setting:class:WorkspaceSettingCategory' as Ref>, + 'notification:class:NotificationProvider' as Ref> ]) const result = pluginFilterTx(excludedPlugins, configs, txes).filter((tx) => { diff --git a/plugins/client/src/index.ts b/plugins/client/src/index.ts index 04f9245097..0e6c09e47e 100644 --- a/plugins/client/src/index.ts +++ b/plugins/client/src/index.ts @@ -65,6 +65,8 @@ export interface ClientFactoryOptions { onConnect?: (event: ClientConnectEvent, lastTx: string | undefined, data: any) => Promise ctx?: MeasureContext onDialTimeout?: () => void | Promise + + useGlobalRPCHandler?: boolean } /** diff --git a/plugins/view-resources/src/components/RelationsEditor.svelte b/plugins/view-resources/src/components/RelationsEditor.svelte index a6a8bdef76..a4fe577bdf 100644 --- a/plugins/view-resources/src/components/RelationsEditor.svelte +++ b/plugins/view-resources/src/components/RelationsEditor.svelte @@ -50,7 +50,7 @@ object._class, { _id: object._id }, (res) => { - relationsA = res[0].$associations ?? {} + relationsA = res?.[0]?.$associations ?? {} }, { associations: associationsA.map((a) => [a._id, 1]) } ) @@ -60,7 +60,7 @@ object._class, { _id: object._id }, (res) => { - relationsB = res[0].$associations ?? {} + relationsB = res?.[0]?.$associations ?? {} }, { associations: associationsB.map((a) => [a._id, -1]) } ) diff --git a/services/github/github-assets/lang/en.json b/services/github/github-assets/lang/en.json index 5fbb082ef4..67d1842b25 100644 --- a/services/github/github-assets/lang/en.json +++ b/services/github/github-assets/lang/en.json @@ -88,6 +88,7 @@ "AuthenticationRevokedGithub": "Re-authorization for {login} is required for the proper functioning of the GitHub App", "UnlinkInstallationTitle": "Uninstall Github App", "UnlinkInstallation": "Are you sure you want to uninstall the GitHub App? Synchronization will be disabled.", - "RemoveInstallation": "Uninstall" + "RemoveInstallation": "Uninstall", + "Suspended": "Suspended" } } diff --git a/services/github/github-assets/lang/pt.json b/services/github/github-assets/lang/pt.json index 29058c8671..563b6f3c9c 100644 --- a/services/github/github-assets/lang/pt.json +++ b/services/github/github-assets/lang/pt.json @@ -88,6 +88,7 @@ "AuthenticationRevokedGithub": "É necessária uma nova autorização para {login} para o funcionamento adequado do aplicativo GitHub", "UnlinkInstallationTitle": "Desinstalar aplicativo Github", "UnlinkInstallation": "Tem certeza de que deseja desinstalar o aplicativo GitHub? A sincronização será desabilitada.", - "RemoveInstallation": "Desinstalar" + "RemoveInstallation": "Desinstalar", + "Suspended": "Suspended" } } diff --git a/services/github/github-assets/lang/ru.json b/services/github/github-assets/lang/ru.json index b913aeba60..cd73632bca 100644 --- a/services/github/github-assets/lang/ru.json +++ b/services/github/github-assets/lang/ru.json @@ -88,6 +88,7 @@ "AuthenticationRevokedGithub": "Требуется повторная авторизация для {login} для правильной работы приложения GitHub", "UnlinkInstallationTitle": "Удалить приложение Github", "UnlinkInstallation": "Вы уверены, что хотите удалить приложение GitHub? Синхронизация будет отключена.", - "RemoveInstallation": "Удалить" + "RemoveInstallation": "Удалить", + "Suspended": "Suspended" } } diff --git a/services/github/github-assets/lang/sp.json b/services/github/github-assets/lang/sp.json index 1848350fc3..e59b65df37 100644 --- a/services/github/github-assets/lang/sp.json +++ b/services/github/github-assets/lang/sp.json @@ -88,6 +88,7 @@ "AuthenticationRevokedGithub": "Se requiere una nueva autorización para {login} para el correcto funcionamiento de la aplicación de GitHub", "UnlinkInstallationTitle": "Desinstalar aplicación de Github", "UnlinkInstallation": "¿Estás seguro de que deseas desinstalar la aplicación de GitHub? La sincronización se deshabilitará.", - "RemoveInstallation": "Desinstalar" + "RemoveInstallation": "Desinstalar", + "Suspended": "Suspended" } } diff --git a/services/github/github-resources/src/components/GithubRepositories.svelte b/services/github/github-resources/src/components/GithubRepositories.svelte index 79e67994ee..018e39de53 100644 --- a/services/github/github-resources/src/components/GithubRepositories.svelte +++ b/services/github/github-resources/src/components/GithubRepositories.svelte @@ -26,6 +26,7 @@ import ConnectProject from './ConnectProject.svelte' import { githubLanguageColors } from './languageColors' import { sendGHServiceRequest } from './utils' + import { BackgroundColor } from '@hcengineering/text' export let integration: WithLookup export let projects: Project[] = [] @@ -141,145 +142,151 @@ } - - - -
- -
- {#if integration.name.length > 0} - {integration.name} - {#if (integration.type ?? '') !== ''} - ({integration.type}) - {/if} - {:else} -
-
- -