Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
## vNEXT (not yet released)

## v3.24.1

### `@liveblocks/react`

- Add new hook `useMutableStorage()` to get direct access to the mutable Storage
root. See
[docs](https://liveblocks.io/docs/api-reference/liveblocks-react#useMutableStorage).
- Fix thread resolved/unresolved status being reverted under some conditions.
(Thanks @VihaanAgarwal for the contribution!)

## v3.24.0

Expand Down
14 changes: 14 additions & 0 deletions docs/pages/platform/data-storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ and compliance reports, such as SOC 2 Type II and HIPAA.

</Banner>

## Self-hosting

Enterprise customers can self-host Liveblocks Sync, running it in their own
cloud so that collaboration data stays within their infrastructure. Liveblocks
provides ongoing releases, security updates, deployment guidance, and enterprise
support. Self-hosting is available as a paid add-on on the
[Enterprise plan](/docs/pricing/plans/enterprise), and we can discuss whether it
meets your security, compliance, or data residency requirements. The rest of
this page describes how data is stored on the Liveblocks cloud.

<Button asChild className="not-markdown">
<a href="/contact/sales">Contact sales</a>
</Button>

## Data stored

In most cases, sensitive or personal information can be excluded from what's
Expand Down
25 changes: 13 additions & 12 deletions docs/pages/pricing/plans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ start of the next calendar month or you

## Plan inclusions

| | Free | Pro | Team | Enterprise |
| ------------------------------------------------------------------------- | ---- | -------- | ------------------------------------------------- | ---------- |
| Remove "Powered by Liveblocks" badge | — | Included | Included | Included |
| Multi-region hosting | — | — | — | Included |
| SOC 2 report | — | — | Included | Included |
| [SAML SSO](/docs/platform/account-management/saml) | — | — | Included | Included |
| [SCIM / directory sync](/docs/platform/account-management/directory-sync) | — | — | — | Included |
| Private Slack channel | — | — | Included | Included |
| HIPAA BAA | — | — | <Limits.HipaaBaaPricePerMonth /> per month add-on | Included |
| [Management API](/docs/platform/management-api) | — | — | — | Included |
| Role-based access control | — | — | Team level | Included |
| Uptime SLA | — | — | — | 99.99% |
| | Free | Pro | Team | Enterprise |
| ------------------------------------------------------------------------- | ---- | -------- | ------------------------------------------------- | ----------- |
| Remove "Powered by Liveblocks" badge | — | Included | Included | Included |
| Multi-region hosting | — | — | — | Included |
| SOC 2 report | — | — | Included | Included |
| [SAML SSO](/docs/platform/account-management/saml) | — | — | Included | Included |
| [SCIM / directory sync](/docs/platform/account-management/directory-sync) | — | — | — | Included |
| Private Slack channel | — | — | Included | Included |
| HIPAA BAA | — | — | <Limits.HipaaBaaPricePerMonth /> per month add-on | Included |
| [Management API](/docs/platform/management-api) | — | — | — | Included |
| Role-based access control | — | — | Team level | Included |
| [Self-hosting](/docs/platform/data-storage#Self-hosting) | — | — | — | Paid add-on |
| Uptime SLA | — | — | — | 99.99% |
13 changes: 13 additions & 0 deletions docs/pages/pricing/plans/enterprise.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ Enterprise limits are customized for your organization. See
| <LimitLabel tooltip="How long activity and audit events are retained.">Event log retention</LimitLabel> | <LimitHint tooltip={<>Up to <Limits.EnterpriseMaxEventLogRetention /></>}><Limits.EnterpriseEventLogRetention /></LimitHint> |
| Environments | <Limits.EnterpriseEnvironments /> |

## Self-hosting

Self-hosting is available on the Enterprise plan as a paid add-on. You run
Liveblocks Sync in your own cloud, so collaboration data stays within your
infrastructure, while Liveblocks provides ongoing releases, security updates,
deployment guidance, and enterprise support. See
[data storage](/docs/platform/data-storage#Self-hosting) for more information,
and get in touch to discuss whether we can meet your requirements.

<Button asChild className="not-markdown">
<a href="/contact/sales">Contact sales</a>
</Button>

## Security and compliance

Built-in support for global security and data protection standards, including:
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-chat-sdk-adapter/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/chat-sdk-adapter",
"version": "3.24.0",
"version": "3.24.1",
"description": "Liveblocks adapter for the Chat SDK.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/client",
"version": "3.24.0",
"version": "3.24.1",
"description": "A client that lets you interact with Liveblocks servers. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-codemirror/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/codemirror",
"version": "3.24.0",
"version": "3.24.1",
"description": "CodeMirror collaboration plugins backed by Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/core",
"version": "3.24.0",
"version": "3.24.1",
"description": "Private internals for Liveblocks. DO NOT import directly from this package!",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-emails/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/emails",
"version": "3.24.0",
"version": "3.24.1",
"description": "A set of functions and utilities to make sending emails based on Liveblocks notification events easy. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-lexical/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/lexical",
"version": "3.24.0",
"version": "3.24.1",
"description": "Lexical collaboration plugins backed by Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-node-lexical/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/node-lexical",
"version": "3.24.0",
"version": "3.24.1",
"description": "A server-side utility that lets you modify lexical documents hosted in Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-node-prosemirror/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/node-prosemirror",
"version": "3.24.0",
"version": "3.24.1",
"description": "A server-side utility that lets you modify prosemirror and tiptap documents hosted in Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/node",
"version": "3.24.0",
"version": "3.24.1",
"description": "A server-side utility that lets you set up a Liveblocks authentication endpoint. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-prosemirror/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/prosemirror",
"version": "3.24.0",
"version": "3.24.1",
"description": "ProseMirror collaboration plugins backed by Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-react-blocknote/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/react-blocknote",
"version": "3.24.0",
"version": "3.24.1",
"description": "An integration of BlockNote + React to enable collaboration, comments, live cursors, and more with Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-react-flow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/react-flow",
"version": "3.24.0",
"version": "3.24.1",
"description": "An integration of React Flow to enable collaboration and realtime cursors with Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-react-lexical/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/react-lexical",
"version": "3.24.0",
"version": "3.24.1",
"description": "An integration of Lexical + React to enable collaboration, comments, live cursors, and more with Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-react-tiptap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/react-tiptap",
"version": "3.24.0",
"version": "3.24.1",
"description": "An integration of TipTap + React to enable collaboration, comments, live cursors, and more with Liveblocks.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-react-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/react-ui",
"version": "3.24.0",
"version": "3.24.1",
"description": "A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
2 changes: 1 addition & 1 deletion packages/liveblocks-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@liveblocks/react",
"version": "3.24.0",
"version": "3.24.1",
"description": "A set of React hooks and providers to use Liveblocks declaratively. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
"license": "Apache-2.0",
"author": "Liveblocks Inc.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { createClient } from "@liveblocks/client";
import { describe, expect, test } from "vitest";

import { UmbrellaStore } from "../../umbrella-store";
import { createComment, createThread } from "./_dummies";

describe("patchThread", () => {
test("should keep a settled resolve after a stale refetch", () => {
const store = createStore();
const thread = makeThread(false);
store.updateThreadifications([thread], [], []);

const settledAt = new Date("2024-01-01T00:01:00Z");
const optimisticId = store.optimisticUpdates.add({
type: "mark-thread-as-resolved",
threadId: thread.id,
updatedAt: settledAt,
});
store.patchThread(thread.id, optimisticId, { resolved: true }, settledAt);

expect(store.outputs.threads.get().get(thread.id)).toMatchObject({
resolved: true,
updatedAt: settledAt,
});

// A refetch started before the mutation settled can return an older snapshot.
store.updateThreadifications([thread], [], []);

expect(store.outputs.threads.get().get(thread.id)).toMatchObject({
resolved: true,
updatedAt: settledAt,
});
});

test("should keep a settled unresolve after a stale refetch", () => {
const store = createStore();
const thread = makeThread(true);
store.updateThreadifications([thread], [], []);

const settledAt = new Date("2024-01-01T00:01:00Z");
const optimisticId = store.optimisticUpdates.add({
type: "mark-thread-as-unresolved",
threadId: thread.id,
updatedAt: settledAt,
});
store.patchThread(thread.id, optimisticId, { resolved: false }, settledAt);

store.updateThreadifications([thread], [], []);

expect(store.outputs.threads.get().get(thread.id)).toMatchObject({
resolved: false,
updatedAt: settledAt,
});
});
});

function createStore() {
return new UmbrellaStore(
createClient({
publicApiKey: "pk_xxx",
})
);
}

function makeThread(resolved: boolean) {
const createdAt = new Date("2024-01-01T00:00:00Z");
return createThread({
id: "th_1",
roomId: "room_1",
createdAt,
updatedAt: createdAt,
resolved,
comments: [
createComment({
threadId: "th_1",
roomId: "room_1",
createdAt,
}),
],
});
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nanoid, Permission } from "@liveblocks/core";
import { nanoid, Permission, Promise_withResolvers } from "@liveblocks/core";
import { act, renderHook } from "@testing-library/react";
import { HttpResponse } from "msw";
import { setupServer } from "msw/node";
Expand Down Expand Up @@ -27,16 +27,34 @@ beforeEach(() => {
});

afterEach(() => {
vi.useRealTimers();
MockWebSocket.reset();
server.resetHandlers();
});

afterAll(() => server.close());

describe("useMarkThreadAsResolved", () => {
test("should mark thread as resolved optimistically", async () => {
test("should keep a settled resolve after an in-flight stale refetch", async () => {
const initialDate = new Date("2024-01-01T00:00:00Z");
const requestedAt = new Date("2024-01-01T00:01:00Z");
const refetchedAt = new Date("2024-01-01T00:02:00Z");
const settledAt = new Date("2024-01-01T00:03:00Z");
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(requestedAt);

const roomId = nanoid();
const initialThread = dummyThreadData({ roomId, resolved: false });
const initialThread = dummyThreadData({
roomId,
createdAt: initialDate,
updatedAt: initialDate,
resolved: false,
});
const staleThread = {
...initialThread,
updatedAt: refetchedAt,
};
const mutationResponse = Promise_withResolvers<void>();
let hasCalledMarkThreadAsResolved = false;

server.use(
Expand All @@ -54,15 +72,17 @@ describe("useMarkThreadAsResolved", () => {
},
});
}),
mockMarkThreadAsResolved({ threadId: initialThread.id }, () => {
mockMarkThreadAsResolved({ threadId: initialThread.id }, async () => {
hasCalledMarkThreadAsResolved = true;
await mutationResponse.promise;

return HttpResponse.json(null, { status: 200 });
})
);

const {
room: { RoomProvider, useThreads, useMarkThreadAsResolved },
umbrellaStore,
} = createContextsForTest();

const { result, unmount } = renderHook(
Expand All @@ -89,8 +109,21 @@ describe("useMarkThreadAsResolved", () => {

await vi.waitFor(() => expect(hasCalledMarkThreadAsResolved).toEqual(true));

act(() => umbrellaStore.updateThreadifications([staleThread], [], []));

expect(result.current.threads![0]?.resolved).toBe(true);

vi.setSystemTime(settledAt);
act(() => mutationResponse.resolve());

await vi.waitFor(() =>
expect(umbrellaStore.optimisticUpdates.signal.get()).toHaveLength(0)
);
expect(result.current.threads?.[0]?.resolved).toBe(true);
expect(result.current.threads?.[0]?.updatedAt.getTime()).toBeGreaterThan(
refetchedAt.getTime()
);

unmount();
});
});
4 changes: 2 additions & 2 deletions packages/liveblocks-react/src/room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2804,7 +2804,7 @@ function useMarkRoomThreadAsResolved(roomId: string) {
threadId,
optimisticId,
{ resolved: true },
updatedAt
new Date()
);
},
(err: Error) =>
Expand Down Expand Up @@ -2868,7 +2868,7 @@ function useMarkRoomThreadAsUnresolved(roomId: string) {
threadId,
optimisticId,
{ resolved: false },
updatedAt
new Date()
);
},
(err: Error) =>
Expand Down
Loading
Loading