diff --git a/CHANGELOG.md b/CHANGELOG.md
index db39825612..ab60d9dadb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
## vNEXT (not yet released)
+## v3.15.2
+
+### `@liveblocks/client`
+
+- Deprecate the `engine` option on `enterRoom()`. This flag no longer has any
+ effect.
+
+### `@liveblocks/react`
+
+- Deprecate the `engine` prop on `RoomProvider`. This flag no longer has any
+ effect.
+
+### `@liveblocks/node`
+
+- Deprecate the `engine` option on `createRoom()`. This flag no longer has any
+ effect.
+- Stop sending the `engine` field in the room creation request body.
+
## v3.15.1
### `@liveblocks/react-ui`
diff --git a/docs/pages/api-reference/liveblocks-node.mdx b/docs/pages/api-reference/liveblocks-node.mdx
index 9034b64275..71603fc661 100644
--- a/docs/pages/api-reference/liveblocks-node.mdx
+++ b/docs/pages/api-reference/liveblocks-node.mdx
@@ -78,6 +78,15 @@ const { body, status } = await liveblocks.identifyUser(
);
```
+
+
+Never cache your access token authentication endpoint, as your client will not
+function correctly. The Liveblocks client will cache results for you, only
+making requests to the endpoint if necessary, such as when the token has
+expired.
+
+
+
##### Granting ID token permissions
You can pass additional options to `identifyUser`, enabling you to create
@@ -230,8 +239,8 @@ assigning group permissions.
These are arbitrary identifiers that make sense to your app, and that you can
refer to in the [Permissions REST API][] when assigning group permissions.
-`organizationId` (optional) is the organization for this user, will be set to `default` if
-not provided.
+`organizationId` (optional) is the organization for this user, will be set to
+`default` if not provided.
`userInfo` (optional) is any custom JSON value, which you can use to attach
static metadata to this user’s session. This will be publicly visible to all
@@ -323,6 +332,15 @@ const session = liveblocks.prepareSession(
);
```
+
+
+Never cache your access token authentication endpoint, as your client will not
+function correctly. The Liveblocks client will cache results for you, only
+making requests to the endpoint if necessary, such as when the token has
+expired.
+
+
+
##### Granting access token permissions
Using `session.allow()`, you can grant full or read-only permissions to the user
@@ -353,10 +371,11 @@ session.allow("design-room:*", session.FULL_ACCESS);
Additionally, you can pass additional options to `prepareSession`, enabling you
-to create complex permissions using [organizations](/docs/authentication/organizations) and
+to create complex permissions using
+[organizations](/docs/authentication/organizations) and
[accesses](/docs/authentication/access-tokens/permissions). For example, this
-user can only see resources in the `acme-corp` organization, and they're part of a
-`marketing` group within it.
+user can only see resources in the `acme-corp` organization, and they're part of
+a `marketing` group within it.
```ts
const session = liveblocks.prepareSession(
diff --git a/docs/pages/api-reference/liveblocks-react.mdx b/docs/pages/api-reference/liveblocks-react.mdx
index 23f6183aa2..3f0d94d139 100644
--- a/docs/pages/api-reference/liveblocks-react.mdx
+++ b/docs/pages/api-reference/liveblocks-react.mdx
@@ -332,8 +332,10 @@ function App() {
>
The URL of your back end’s [authentication endpoint](/docs/authentication)
as a string, or an async callback function that returns a Liveblocks token
- result. Either `authEndpoint` or `publicApiKey` are required. Learn more
- about [using a URL string](#LiveblocksProviderAuthEndpoint) and [using a
+ result. The result is cached by the Liveblocks client, and called fresh only
+ when necessary, so never cache it yourself. Either `authEndpoint` or
+ `publicApiKey` are required. Learn more about [using a URL
+ string](#LiveblocksProviderAuthEndpoint) and [using a
callback](#LiveblocksProviderCallback).
@@ -475,6 +477,14 @@ function App() {
}
```
+
+
+Never cache your authentication endpoint, as your client will not function
+correctly. The Liveblocks client will cache results for you, only making
+requests to the endpoint if necessary, such as when the token has expired.
+
+
+
#### LiveblocksProvider with auth endpoint callback [#LiveblocksProviderCallback]
If you need to add additional headers or use your own function to call your
diff --git a/e2e/next-sandbox/pages/storage/stress/[roomId]/engine/[engine].tsx b/e2e/next-sandbox/pages/storage/stress/[roomId]/engine/[engine].tsx
deleted file mode 100644
index 1135904afc..0000000000
--- a/e2e/next-sandbox/pages/storage/stress/[roomId]/engine/[engine].tsx
+++ /dev/null
@@ -1,537 +0,0 @@
-import type { Lson, LsonObject } from "@liveblocks/client";
-import { LiveList, LiveMap, LiveObject } from "@liveblocks/client";
-import { createRoomContext } from "@liveblocks/react";
-import Link from "next/link";
-import { useRouter } from "next/router";
-import { useState } from "react";
-
-import { Row, styles, useRenderCount } from "../../../../../utils";
-import Button from "../../../../../utils/Button";
-import { createLiveblocksClient } from "../../../../../utils/createClient";
-
-const client = createLiveblocksClient({
- authEndpoint: "/api/auth/access-token",
-});
-
-// Storage starts empty, can grow arbitrarily
-type Storage = LsonObject;
-
-const {
- RoomProvider,
- useCanRedo,
- useCanUndo,
- useMutation,
- useRedo,
- useRoom,
- useStatus,
- useStorage,
- useSyncStatus,
- useUndo,
-} = createRoomContext(client);
-
-// JSON.stringify replacer to handle Map objects (LiveMap.toImmutable() returns Map)
-function mapReplacer(_key: string, value: unknown): unknown {
- if (value instanceof Map) {
- return Object.fromEntries(value);
- }
- return value;
-}
-
-// Deterministic JSON stringify with sorted keys (for consistent hashing)
-function stableStringify(obj: unknown): string {
- return JSON.stringify(obj, (_key, value: unknown) => {
- if (value instanceof Map) {
- // Sort map entries by key
- const sorted = [...(value as Map).entries()].sort(
- ([a], [b]) => a.localeCompare(b)
- );
- return Object.fromEntries(sorted);
- }
-
- if (value && typeof value === "object" && !Array.isArray(value)) {
- // Sort object keys
- const sorted: Record = {};
- for (const k of Object.keys(value).sort()) {
- sorted[k] = (value as Record)[k];
- }
- return sorted;
- }
- return value;
- });
-}
-
-function formatSize(bytes: number): string {
- if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
- if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
- return `${bytes} B`;
-}
-
-// Simple hash function for quick comparison
-function simpleHash(str: string): string {
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- const char = str.charCodeAt(i);
- hash = (hash << 5) - hash + char;
- hash = hash & hash; // Convert to 32bit integer
- }
- return Math.abs(hash).toString(16).padStart(8, "0");
-}
-
-function randomString(length: number): string {
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
- let result = "";
- for (let i = 0; i < length; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length));
- }
- return result;
-}
-
-function randomInt(max: number): number {
- return Math.floor(Math.random() * max);
-}
-
-type LiveNode = LiveObject | LiveList | LiveMap;
-
-// Create a random tree of given depth/breadth
-function createRandomTree(depth: number, breadth: number): LiveNode {
- const kind = randomInt(3); // 0=LiveObject, 1=LiveList, 2=LiveMap
-
- if (kind === 0) {
- const init: Record = {};
- for (let i = 0; i < breadth; i++) {
- const key = `k_${randomString(6)}`;
- if (depth > 0 && Math.random() > 0.3) {
- init[key] = createRandomTree(depth - 1, breadth);
- } else {
- init[key] = randomString(12);
- }
- }
- return new LiveObject(init);
- } else if (kind === 1) {
- const init: Lson[] = [];
- for (let i = 0; i < breadth; i++) {
- if (depth > 0 && Math.random() > 0.3) {
- init.push(createRandomTree(depth - 1, breadth));
- } else {
- init.push(randomString(12));
- }
- }
- return new LiveList(init);
- } else {
- const init: [string, Lson][] = [];
- for (let i = 0; i < breadth; i++) {
- const key = `m_${randomString(6)}`;
- if (depth > 0 && Math.random() > 0.3) {
- init.push([key, createRandomTree(depth - 1, breadth)]);
- } else {
- init.push([key, randomString(12)]);
- }
- }
- return new LiveMap(init);
- }
-}
-
-// Collect all attachment points in the tree
-type AttachmentPoint =
- | { type: "object"; node: LiveObject }
- | { type: "list"; node: LiveList }
- | { type: "map"; node: LiveMap };
-
-function collectAttachmentPoints(
- root: LiveObject
-): AttachmentPoint[] {
- const points: AttachmentPoint[] = [{ type: "object", node: root }];
-
- function visit(value: unknown) {
- if (value instanceof LiveObject) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- points.push({ type: "object", node: value as LiveObject });
- for (const child of Object.values(value.toObject())) {
- visit(child);
- }
- } else if (value instanceof LiveList) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- points.push({ type: "list", node: value as LiveList });
- for (const item of value) {
- visit(item);
- }
- } else if (value instanceof LiveMap) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- points.push({ type: "map", node: value as LiveMap });
- for (const [, v] of value) {
- visit(v);
- }
- }
- }
-
- for (const child of Object.values(root.toObject())) {
- visit(child);
- }
-
- return points;
-}
-
-// Size configurations with labels
-const SIZES = {
- S: { depth: 1, breadth: 2, label: "1×2" },
- M: { depth: 2, breadth: 2, label: "2×2" },
- L: { depth: 2, breadth: 3, label: "2×3" },
- XL: { depth: 3, breadth: 3, label: "3×3" },
- XXL: { depth: 3, breadth: 4, label: "3×4" },
- XXXL: { depth: 4, breadth: 4, label: "4×4" },
-} as const;
-
-const SHRINK_COUNTS = {
- S: { count: 1, label: "1" },
- M: { count: 3, label: "3" },
- L: { count: 8, label: "8" },
- XL: { count: 20, label: "20" },
- XXL: { count: 50, label: "50" },
- XXXL: { count: 100, label: "100" },
-} as const;
-
-const CHANGE_COUNTS = {
- S: { count: 1, label: "1" },
- M: { count: 5, label: "5" },
- L: { count: 15, label: "15" },
- XL: { count: 40, label: "40" },
- XXL: { count: 100, label: "100" },
- XXXL: { count: 250, label: "250" },
-} as const;
-
-export default function StressTestRoom() {
- const router = useRouter();
- const { roomId, engine: engineStr } = router.query;
-
- // Wait for router to be ready
- if (!router.isReady || typeof roomId !== "string") {
- return
);
}
diff --git a/packages/liveblocks-core/src/client.ts b/packages/liveblocks-core/src/client.ts
index 3090bea9a9..0d766d97d3 100644
--- a/packages/liveblocks-core/src/client.ts
+++ b/packages/liveblocks-core/src/client.ts
@@ -129,9 +129,8 @@ export type EnterOptions
=
autoConnect?: boolean;
/**
- * Preferred storage engine version to use when creating the room. Only
- * takes effect if the room doesn't exist yet. Version 2 can support larger
- * documents, is more performant, and will become the default in the future.
+ * @deprecated This flag no longer has any effect and will be removed in
+ * a future version. All rooms now use the v2 storage engine by default.
*/
engine?: 1 | 2;
}
@@ -514,10 +513,9 @@ export type ClientOptions = {
backgroundKeepAliveTimeout?: number; // in milliseconds
polyfills?: Polyfills;
/**
- * @deprecated For new rooms, use `engine: 2` instead. Rooms on the v2
- * Storage engine have native support for streaming. This flag will be
- * removed in a future version, but will continue to work for existing engine
- * v1 rooms for now.
+ * @deprecated All rooms will be migrated to the v2 storage engine in the
+ * future, which has native support for streaming. After that migration, this
+ * flag will no longer have any effect and will be removed in a future version.
*/
unstable_streamData?: boolean;
/**
@@ -777,8 +775,7 @@ export function createClient(
createSocket: makeCreateSocketDelegateForRoom(
roomId,
baseUrl,
- clientOptions.polyfills?.WebSocket,
- options.engine
+ clientOptions.polyfills?.WebSocket
),
authenticate: makeAuthDelegateForRoom(roomId, authManager),
},
diff --git a/packages/liveblocks-core/src/room.ts b/packages/liveblocks-core/src/room.ts
index 53d71fc9af..f8b1a249d7 100644
--- a/packages/liveblocks-core/src/room.ts
+++ b/packages/liveblocks-core/src/room.ts
@@ -1274,10 +1274,9 @@ export type RoomConfig = {
backgroundKeepAliveTimeout?: number;
/**
- * @deprecated For new rooms, use `engine: 2` instead. Rooms on the v2
- * Storage engine have native support for streaming. This flag will be
- * removed in a future version, but will continue to work for existing engine
- * v1 rooms for now.
+ * @deprecated All rooms will be migrated to the v2 storage engine in the
+ * future, which has native support for streaming. After that migration, this
+ * flag will no longer have any effect and will be removed in a future version.
*/
unstable_streamData?: boolean;
@@ -3355,8 +3354,7 @@ export function makeAuthDelegateForRoom(
export function makeCreateSocketDelegateForRoom(
roomId: string,
baseUrl: string,
- WebSocketPolyfill?: IWebSocket,
- engine?: 1 | 2
+ WebSocketPolyfill?: IWebSocket
) {
return (authValue: AuthValue): IWebSocketInstance => {
const ws: IWebSocket | undefined =
@@ -3381,9 +3379,6 @@ export function makeCreateSocketDelegateForRoom(
return assertNever(authValue, "Unhandled case");
}
url.searchParams.set("version", PKG_VERSION || "dev");
- if (engine !== undefined) {
- url.searchParams.set("e", String(engine));
- }
return new ws(url.toString());
};
}
diff --git a/packages/liveblocks-node/src/__tests__/client.test.ts b/packages/liveblocks-node/src/__tests__/client.test.ts
index a6eef6bf11..9af730883d 100644
--- a/packages/liveblocks-node/src/__tests__/client.test.ts
+++ b/packages/liveblocks-node/src/__tests__/client.test.ts
@@ -563,7 +563,6 @@ describe("client", () => {
usersAccesses: undefined,
organizationId: tenantId,
metadata: undefined,
- engine: undefined,
});
});
@@ -621,11 +620,10 @@ describe("client", () => {
usersAccesses: undefined,
organizationId,
metadata: undefined,
- engine: undefined,
});
});
- test("should pass engine to the request when createRoom is called with engine", async () => {
+ test("should not send engine in the request even when provided", async () => {
const roomId = "test-room";
const createRoomParams = {
defaultAccesses: ["room:write"] as ["room:write"],
@@ -649,38 +647,7 @@ describe("client", () => {
defaultAccesses: ["room:write"],
groupsAccesses: undefined,
usersAccesses: undefined,
- tenantId: undefined,
metadata: undefined,
- engine: 2,
- });
- });
-
- test("should not include engine in the request when createRoom is called without engine", async () => {
- const roomId = "test-room";
- const createRoomParams = {
- defaultAccesses: ["room:write"] as ["room:write"],
- };
-
- let capturedRequestData: unknown = null;
-
- server.use(
- http.post(`${DEFAULT_BASE_URL}/v2/rooms`, async ({ request }) => {
- capturedRequestData = await request.json();
- return HttpResponse.json(room, { status: 200 });
- })
- );
-
- const client = new Liveblocks({ secret: "sk_xxx" });
- await client.createRoom(roomId, createRoomParams);
-
- expect(capturedRequestData).toEqual({
- id: roomId,
- defaultAccesses: ["room:write"],
- groupsAccesses: undefined,
- usersAccesses: undefined,
- tenantId: undefined,
- metadata: undefined,
- engine: undefined,
});
});
});
diff --git a/packages/liveblocks-node/src/client.ts b/packages/liveblocks-node/src/client.ts
index 959c25713d..34dbfc942e 100644
--- a/packages/liveblocks-node/src/client.ts
+++ b/packages/liveblocks-node/src/client.ts
@@ -466,9 +466,8 @@ export type CreateRoomOptions = {
organizationId?: string;
/**
- * Preferred storage engine version to use when creating the room. Only takes
- * effect if the room doesn't exist yet. Version 2 can support larger
- * documents, is more performant, and will become the default in the future.
+ * @deprecated This flag no longer has any effect and will be removed in
+ * a future version. All rooms now use the v2 storage engine by default.
*/
engine?: 1 | 2;
};
@@ -1065,7 +1064,6 @@ export class Liveblocks {
metadata,
tenantId,
organizationId,
- engine,
} = params;
const body: {
@@ -1075,14 +1073,12 @@ export class Liveblocks {
usersAccesses?: RoomAccesses;
metadata?: RoomMetadata;
organizationId?: string;
- engine?: 1 | 2;
} = {
id: roomId,
defaultAccesses,
groupsAccesses,
usersAccesses,
metadata,
- engine,
};
if (organizationId !== undefined) {
diff --git a/packages/liveblocks-react/src/types/index.ts b/packages/liveblocks-react/src/types/index.ts
index 2aba3f615b..d56ab6bd71 100644
--- a/packages/liveblocks-react/src/types/index.ts
+++ b/packages/liveblocks-react/src/types/index.ts
@@ -345,9 +345,8 @@ export type RoomProviderProps
=
autoConnect?: boolean;
/**
- * Preferred storage engine version to use when creating the room. Only
- * takes effect if the room doesn't exist yet. Version 2 can support larger
- * documents, is more performant, and will become the default in the future.
+ * @deprecated This flag no longer has any effect and will be removed in
+ * a future version. All rooms now use the v2 storage engine by default.
*/
engine?: 1 | 2;
}