import core, { WorkspaceEvent, generateId, getTypeOf, type BulkUpdateEvent, type Class, type Doc, type FullParamsType, type MeasureContext, type ParamsType, type Ref, type TxWorkspaceEvent, type WorkspaceIdWithUrl, type Branding, type BrandingMap } from '@hcengineering/core' import { type Hash } from 'crypto' import fs from 'fs' import type { SessionContext } from './types' /** * Return some estimation for object size */ export function estimateDocSize (_obj: any): number { let result = 0 const toProcess = [_obj] while (toProcess.length > 0) { const obj = toProcess.shift() if (typeof obj === 'undefined') { continue } if (typeof obj === 'function') { continue } for (const key in obj) { // include prototype properties const value = obj[key] const type = getTypeOf(value) result += key.length switch (type) { case 'Array': result += 4 toProcess.push(value) break case 'Object': toProcess.push(value) break case 'Date': result += 24 // Some value break case 'string': result += (value as string).length break case 'number': result += 8 break case 'boolean': result += 1 break case 'symbol': result += (value as symbol).toString().length break case 'bigint': result += (value as bigint).toString().length break case 'undefined': result += 1 break case 'null': result += 1 break default: result += value.toString().length } } } return result } /** * Return some estimation for object size */ export function updateHashForDoc (hash: Hash, _obj: any): void { const toProcess = [_obj] while (toProcess.length > 0) { const obj = toProcess.shift() if (typeof obj === 'undefined') { continue } if (typeof obj === 'function') { continue } for (const key in obj) { // include prototype properties const value = obj[key] const type = getTypeOf(value) hash.update(key) switch (type) { case 'Array': toProcess.push(value) break case 'Object': toProcess.push(value) break case 'Date': hash.update(value.toString()) break case 'string': hash.update(value) break case 'number': hash.update((value as number).toString(16)) break case 'boolean': hash.update((value as boolean) ? '1' : '0') break case 'symbol': hash.update((value as symbol).toString()) break case 'bigint': hash.update((value as bigint).toString()) break case 'undefined': hash.update('und') break case 'null': hash.update('null') break default: hash.update(value.toString()) } } } } export class SessionContextImpl implements SessionContext { constructor ( readonly ctx: MeasureContext, readonly userEmail: string, readonly sessionId: string, readonly admin: boolean | undefined, readonly derived: SessionContext['derived'], readonly workspace: WorkspaceIdWithUrl, readonly branding: Branding | null, readonly isAsyncContext: boolean ) {} with<T>( name: string, params: ParamsType, op: (ctx: SessionContext) => T | Promise<T>, fullParams?: FullParamsType ): Promise<T> { return this.ctx.with( name, params, async (ctx) => await op( new SessionContextImpl( ctx, this.userEmail, this.sessionId, this.admin, this.derived, this.workspace, this.branding, this.isAsyncContext ) ), fullParams ) } } export function createBroadcastEvent (classes: Ref<Class<Doc>>[]): TxWorkspaceEvent<BulkUpdateEvent> { return { _class: core.class.TxWorkspaceEvent, _id: generateId(), event: WorkspaceEvent.BulkUpdate, params: { _class: classes }, modifiedBy: core.account.System, modifiedOn: Date.now(), objectSpace: core.space.DerivedTx, space: core.space.DerivedTx } } export function loadBrandingMap (brandingPath?: string): BrandingMap { let brandings: BrandingMap = {} if (brandingPath !== undefined && brandingPath !== '') { brandings = JSON.parse(fs.readFileSync(brandingPath, 'utf8')) for (const [host, value] of Object.entries(brandings)) { const protocol = value.protocol ?? 'https' value.front = `${protocol}://${host}/` } } return brandings }