Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ coverage
out/
build
dist
dist-server
# misc
.DS_Store
*.pem
Expand Down
1 change: 1 addition & 0 deletions .node_version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.22.3
3 changes: 3 additions & 0 deletions apps/meteor/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ tests/end-to-end/temporary_staged_test
/tests/e2e/.playwright
coverage
.nyc_output
.nyc_cache
/data
tests/e2e/test-failures/
out.txt
Expand All @@ -88,3 +89,5 @@ dist
matrix-federation-config/*
.eslintcache
tsconfig.typecheck.tsbuildinfo
.vite-inspect
.build
7 changes: 6 additions & 1 deletion apps/meteor/app/utils/client/getURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ export const getURL = function (
const cdnPrefix = settings.peek('CDN_PREFIX') || '';
const siteUrl = settings.peek('Site_Url') || '';

const isLocalhost =
typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');

const resolvedSiteUrl = params.full && isLocalhost ? window.location.origin : siteUrl;

if (cacheKey) {
path += `${path.includes('?') ? '&' : '?'}cacheKey=${Info.version}`;
}

return getURLWithoutSettings(path, params, cdnPrefix, siteUrl, cloudDeepLinkUrl);
return getURLWithoutSettings(path, params, cdnPrefix, resolvedSiteUrl, cloudDeepLinkUrl);
};
6 changes: 5 additions & 1 deletion apps/meteor/client/lib/cachedStores/CachedStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,11 @@ export abstract class CachedStore<T extends IRocketChatRecord, U = T> implements

export class PublicCachedStore<T extends IRocketChatRecord, U = T> extends CachedStore<T, U> {
protected override getToken() {
return undefined;
// The vite dev client is served from a fixed localhost origin regardless of which
// server it proxies to, so public caches must be keyed by the upstream server to
// avoid reusing another workspace's data. Undefined in the Meteor-served client,
// where the origin already identifies the workspace.
return __meteor_runtime_config__.UPSTREAM_ROOT_URL;
}

override clearCacheOnLogout() {
Expand Down
36 changes: 36 additions & 0 deletions apps/meteor/client/lib/rooms/registerRoomTypeRoutes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { lazy } from 'react';

import { roomCoordinator } from './roomCoordinator';
import { router } from '../../providers/RouterProvider';
import MainLayout from '../../views/root/MainLayout';
import { appLayout } from '../appLayout';

// Lazy, matching the convention in client/startup/routes.tsx: MainLayout is imported statically
// (it is generic and provides its own <Suspense>) while the page component is loaded on demand.
const RoomRoute = lazy(() => import('../../views/room/RoomRoute'));

/**
* Registers a router route for every room type that declares one.
*
* This lives in the view layer — rather than in the room-type coordinator — so the coordinator
* (a lib-level singleton) never imports the room view tree. That import used to form a dependency
* cycle (roomCoordinator → room UI → roomCoordinator) which made Vite re-execute the coordinator
* on every HMR edit of a room component, wiping its registered `roomTypes` and crashing the room.
*
* Must be called after all room types have been registered (see client/lib/rooms/roomTypes).
*/
export const registerRoomTypeRoutes = (): void => {
for (const { name, path, extractOpenRoomParams } of roomCoordinator.getRoomTypeRoutes()) {
router.defineRoutes([
{
path,
id: name,
element: appLayout.wrap(
<MainLayout>
<RoomRoute key={name} extractOpenRoomParams={extractOpenRoomParams} />
</MainLayout>,
),
},
]);
}
};
54 changes: 28 additions & 26 deletions apps/meteor/client/lib/rooms/roomCoordinator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IRoom, RoomType, IUser, AtLeast, ValueOf, ISubscription } from '@rocket.chat/core-typings';
import type { RouteName } from '@rocket.chat/ui-contexts';
import type { RouteName, RouterPathPattern } from '@rocket.chat/ui-contexts';

import { hasPermission } from '../../../app/authorization/client';
import type {
Expand All @@ -14,10 +14,13 @@ import type {
import { RoomCoordinator } from '../../../lib/rooms/coordinator';
import { router } from '../../providers/RouterProvider';
import { Subscriptions } from '../../stores';
import RoomRoute from '../../views/room/RoomRoute';
import MainLayout from '../../views/root/MainLayout';
import { absoluteUrl } from '../absoluteUrl';
import { appLayout } from '../appLayout';

export type RoomTypeRoute = {
name: RouteName;
path: RouterPathPattern;
extractOpenRoomParams: NonNullable<IRoomTypeClientDirectives['extractOpenRoomParams']>;
};

class RoomCoordinatorClient extends RoomCoordinator {
public add(roomConfig: IRoomTypeClientConfig, directives: Partial<IRoomTypeClientDirectives>): void {
Expand Down Expand Up @@ -63,6 +66,27 @@ class RoomCoordinatorClient extends RoomCoordinator {
return this.roomTypes[roomType].directives as IRoomTypeClientDirectives;
}

/**
* Returns the routes that should be registered for the room types that declare one.
*
* Route registration itself lives in the view layer (see registerRoomTypeRoutes) so this
* coordinator stays free of view/router imports — importing the room view tree here created a
* dependency cycle that made Vite re-execute this module (and wipe its `roomTypes`) on every
* HMR edit of a room component.
*/
public getRoomTypeRoutes(): RoomTypeRoute[] {
return Object.values(this.roomTypes).flatMap(({ config, directives }) => {
const { route } = config;
const { extractOpenRoomParams } = directives as IRoomTypeClientDirectives;

if (!route?.path || !route.name || !extractOpenRoomParams) {
return [];
}

return [{ name: route.name, path: route.path, extractOpenRoomParams }];
});
}

public openRouteLink(
roomType: RoomType,
subData: RoomIdentification,
Expand Down Expand Up @@ -170,28 +194,6 @@ class RoomCoordinatorClient extends RoomCoordinator {
}
}

protected override addRoomType(roomConfig: IRoomTypeClientConfig, directives: IRoomTypeClientDirectives): void {
super.addRoomType(roomConfig, directives);

if (roomConfig.route?.path && roomConfig.route.name && directives.extractOpenRoomParams) {
const {
route: { name, path },
} = roomConfig;
const { extractOpenRoomParams } = directives;
router.defineRoutes([
{
path,
id: name,
element: appLayout.wrap(
<MainLayout>
<RoomRoute key={name} extractOpenRoomParams={extractOpenRoomParams} />
</MainLayout>,
),
},
]);
}
}

public getURL(roomType: string, subData: RoomIdentification): string | false {
const config = this.getRoomTypeConfig(roomType);
if (!config?.route) {
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/client/lib/rooms/roomTypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ import './livechat';
import './private';
import './public';
import './unread';
import { registerRoomTypeRoutes } from '../registerRoomTypeRoutes';

// Room types are registered above via import side effects; now that they all exist, wire up their
// routes. Route registration is deliberately kept out of the coordinator to avoid a view-layer
// import cycle (see registerRoomTypeRoutes).
registerRoomTypeRoutes();
12 changes: 6 additions & 6 deletions apps/meteor/client/meteor/minimongo/MinimongoCollection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Mongo } from 'meteor/mongo';
import type { StoreApi, UseBoundStore } from 'zustand';

import { LocalCollection } from './LocalCollection';
Expand All @@ -8,9 +7,12 @@ import type { IDocumentMapStore } from '../../lib/cachedStores/DocumentMapStore'
/**
* Implements a minimal version of a MongoDB collection using Zustand for state management.
*
* It's a middle layer between the Mongo.Collection and Zustand aiming for complete migration to Zustand.
* It's a middle layer between Meteor-style collection consumers and Zustand aiming for
* complete migration to Zustand. It intentionally does not extend Mongo.Collection: it is
* never named, never replicated over DDP, and nothing calls the inherited query/mutator
* API — the Zustand LocalCollection below is the only backing store.
*/
export class MinimongoCollection<T extends { _id: string }> extends Mongo.Collection<T> {
export class MinimongoCollection<T extends { _id: string }> {
private pendingRecomputations = new Set<Query<T>>();

recomputeAll() {
Expand Down Expand Up @@ -61,9 +63,7 @@ export class MinimongoCollection<T extends { _id: string }> extends Mongo.Collec
* queries that depend on the changed documents.
*/
public readonly use: UseBoundStore<StoreApi<IDocumentMapStore<T>>>,
) {
super(null);
}
) {}

/**
* Returns the Zustand store state that holds the records of the collection.
Expand Down
13 changes: 13 additions & 0 deletions apps/meteor/definition/externals/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ declare global {
const __meteor_runtime_config__: {
ROOT_URL_PATH_PREFIX: string;
ROOT_URL: string;
/** Set only by the vite dev client: the server behind the local proxy */
UPSTREAM_ROOT_URL?: string;
PUBLIC_SETTINGS?: Record<string, unknown>;
accountsConfigCalled?: boolean;
meteorEnv: {
TEST_METADATA?: string;
NODE_ENV?: string;
};
ACCOUNTS_CONNECTION_URL?: string;
isModern?: boolean;
gitCommitHash?: string;
meteorRelease?: string;
debug?: boolean;
};

interface Window {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/lib/omnichannel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { createDefaultPriorities } from './priorities';
patchOmniCore();

await License.onLicense('livechat-enterprise', async () => {
require('../../hooks/omnichannel');
await import('../../hooks/omnichannel');
await import('./startup');
const { createPermissions } = await import('./permissions');
const { createSettings } = await import('./settings');
Expand Down
16 changes: 16 additions & 0 deletions apps/meteor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<title>Rocket.Chat</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" sizes="16x16" type="image/png" href="/assets/favicon_16.png" />
<link rel="icon" sizes="32x32" type="image/png" href="/assets/favicon_32.png" />
<link rel="icon" sizes="any" type="image/png" href="/assets/favicon.svg" />
<link rel="manifest" href="images/manifest.json" />
</head>

<body>
<div id="react-root"></div>
<script type="module" src="./src/index.ts"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion apps/meteor/lib/getMessageUrlRegex.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const getMessageUrlRegex = (): RegExp =>
export const getMessageUrlRegex: () => RegExp = (): RegExp =>
/([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g;
8 changes: 7 additions & 1 deletion apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
"name": "Rocket.Chat",
"url": "https://rocket.chat/"
},
"type": "commonjs",
"type": "module",
"scripts": {
".testunit:definition": "mocha --config ./.mocharc.definition.js",
".testunit:jest": "TZ=UTC TS_NODE_COMPILER_OPTIONS='{\"allowJs\": false}' jest",
".testunit:server": "mocha --config ./.mocharc.js",
".testunit:server:cov": "nyc -r text -r lcov mocha --config ./.mocharc.js",
"build:ci": "METEOR_DEBUG_BUILD=1 METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build --verbose --server-only --directory /tmp/dist",
"build:server": "vite build --app --config ./vite.config.server.mts",
"coverage": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' nyc -r html mocha --config ./.mocharc.js",
"debug": "meteor run --inspect",
"debug-brk": "meteor run --inspect-brk",
Expand Down Expand Up @@ -130,6 +131,8 @@
"@rocket.chat/memo": "~0.31.25",
"@rocket.chat/message-parser": "workspace:^",
"@rocket.chat/message-types": "workspace:~",
"@rocket.chat/meteor-client": "workspace:^",
"@rocket.chat/meteor-server": "workspace:^",
"@rocket.chat/model-typings": "workspace:^",
"@rocket.chat/models": "workspace:^",
"@rocket.chat/mongo-adapter": "workspace:^",
Expand Down Expand Up @@ -419,6 +422,7 @@
"@types/ua-parser-js": "^0.7.39",
"@types/underscore": "^1.13.0",
"@types/xml-encryption": "~1.2.4",
"@vitejs/plugin-react": "~6.0.3",
"autoprefixer": "^10.5.4",
"babel-loader": "~10.0.0",
"babel-plugin-array-includes": "^2.0.3",
Expand Down Expand Up @@ -466,6 +470,8 @@
"ts-node": "^10.9.2",
"tsx": "~4.22.5",
"typescript": "~5.9.3",
"vite": "~8.1.4",
"vite-plugin-istanbul": "~9.0.1",
"webpack": "~5.104.1"
},
"volta": {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/server/lib/import/csv/CsvImporter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IImport } from '@rocket.chat/core-typings';
import { Settings, Users } from '@rocket.chat/models';
import { Random } from '@rocket.chat/random';
import { parse } from 'csv-parse/lib/sync';
import { parse } from 'csv-parse/sync';

import { Importer, ProgressStep, ImporterWebsocket } from '..';
import { notifyOnSettingChanged } from '../../notifyListener';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'node:fs';

import type { IImport } from '@rocket.chat/core-typings';
import { parse } from 'csv-parse/lib/sync';
import { parse } from 'csv-parse/sync';

import { Importer, ProgressStep, ImporterWebsocket } from '..';
import { addParsedContacts } from './addParsedContacts';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'node:fs';

import type { IImport, IImportUser } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { parse } from 'csv-parse/lib/sync';
import { parse } from 'csv-parse/sync';

import { Importer, ProgressStep } from '..';
import { RocketChatFile } from '../../media/file';
Expand Down
Loading
Loading