;
}
+
export const useTextComponentLock = ({ id, slide, defaultText, containerRef }: UseTextComponentLockArgs) => {
const spaceName = getSpaceNameFromUrl();
const { members, self } = useMembers();
@@ -32,7 +34,8 @@ export const useTextComponentLock = ({ id, slide, defaultText, containerRef }: U
const { channel } = useChannel(channelName, (message) => {
if (message.connectionId === self?.connectionId) return;
- updateContent(message.data);
+ const sanitizedValue = sanitize(message.data, { allowedTags: [] });
+ updateContent(sanitizedValue);
});
const optimisticallyLocked = !!activeMember;
diff --git a/demo/src/utils/active-member.ts b/demo/src/utils/active-member.ts
index e636d4b8..f860f327 100644
--- a/demo/src/utils/active-member.ts
+++ b/demo/src/utils/active-member.ts
@@ -5,6 +5,10 @@ export const findActiveMember = (id: string, slide: string, members?: Member[])
return members.find((member) => member.location?.element === id && member.location?.slide === slide);
};
+export const findActiveMembers = (id: string, slide: string, members?: Member[]) => {
+ return (members ?? []).filter((member) => member.location?.element === id && member.location?.slide === slide);
+};
+
export const getMemberFirstName = (member?: Member) => {
if (!member) return '';
return member.profileData.name.split(' ')[0];
From 22609850b284674413b28694d85338b0ba33e439 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Tue, 29 Aug 2023 17:06:33 +0100
Subject: [PATCH 028/191] Fix getAll no returing position or data values
This broke when we added offsets to the OutgoingBuffer in cursors. Unfortuntely, the relevant tests need to mock the whole presence message and did not catch this change.
When we implement integration tests, this should be a prime candidate for a golden path test as it needs to go via multiple separate modules as well as Ably.
---
src/CursorBatching.ts | 2 +-
src/CursorHistory.ts | 14 +++++++++++---
src/Cursors.test.ts | 7 +++++--
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/src/CursorBatching.ts b/src/CursorBatching.ts
index 43dcc8a6..58e4f507 100644
--- a/src/CursorBatching.ts
+++ b/src/CursorBatching.ts
@@ -4,7 +4,7 @@ import { CURSOR_UPDATE } from './CursorConstants.js';
import type { CursorUpdate } from './types.js';
import type { CursorsOptions } from './types.js';
-type OutgoingBuffer = { cursor: Pick; offset: number };
+export type OutgoingBuffer = { cursor: Pick; offset: number };
export default class CursorBatching {
outgoingBuffer: OutgoingBuffer[] = [];
diff --git a/src/CursorHistory.ts b/src/CursorHistory.ts
index d99d86a6..8a8e532a 100644
--- a/src/CursorHistory.ts
+++ b/src/CursorHistory.ts
@@ -2,6 +2,7 @@ import { Types } from 'ably';
import type { CursorUpdate } from './types.js';
import type { CursorsOptions } from './types.js';
+import type { OutgoingBuffer } from './CursorBatching.js';
type ConnectionId = string;
type ConnectionsLastPosition = Record;
@@ -35,10 +36,17 @@ export default class CursorHistory {
const lastMessage = page.items.find((item) => item.connectionId === connectionId);
if (!lastMessage) return [connectionId, cursors];
- const { data, clientId }: { data: CursorUpdate[] } & Pick = lastMessage;
+ const { data = [], clientId }: { data: OutgoingBuffer[] } & Pick = lastMessage;
- const lastUpdate =
- data?.length > 0 ? this.messageToUpdate(connectionId, clientId, data[data.length - 1]) : null;
+ const lastPositionSet = data[data.length - 1]?.cursor;
+ const lastUpdate = lastPositionSet
+ ? {
+ clientId,
+ connectionId,
+ position: lastPositionSet.position,
+ data: lastPositionSet.data,
+ }
+ : null;
return [connectionId, lastUpdate];
}),
diff --git a/src/Cursors.test.ts b/src/Cursors.test.ts
index 42b06b14..e51cb54a 100644
--- a/src/Cursors.test.ts
+++ b/src/Cursors.test.ts
@@ -387,13 +387,16 @@ describe('Cursors', () => {
const client1Message = {
connectionId: 'connectionId1',
clientId: 'clientId1',
- data: [{ position: { x: 1, y: 1 } }, { position: { x: 2, y: 3 }, data: { color: 'blue' } }],
+ data: [
+ { cursor: { position: { x: 1, y: 1 } } },
+ { cursor: { position: { x: 2, y: 3 }, data: { color: 'blue' } } },
+ ],
};
const client2Message = {
connectionId: 'connectionId2',
clientId: 'clientId2',
- data: [{ position: { x: 25, y: 44 } }],
+ data: [{ cursor: { position: { x: 25, y: 44 } } }],
};
vi.spyOn(channel.presence, 'get').mockImplementation(async () => [
From 87d3bba6818e518aab820d479ca5799bff940e29 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Tue, 29 Aug 2023 17:12:34 +0100
Subject: [PATCH 029/191] Remove mock-socket
mock-socket is no longer used
---
package-lock.json | 16 ----------------
package.json | 1 -
2 files changed, 17 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a4f600ce..24277586 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,7 +22,6 @@
"eslint-plugin-jsdoc": "^39.8.0",
"eslint-plugin-security": "^1.7.1",
"husky": "^8.0.0",
- "mock-socket": "^9.1.5",
"prettier": "^2.8.3",
"rollup": "^3.28.0",
"ts-node": "^10.9.1",
@@ -3724,15 +3723,6 @@
"ufo": "^1.3.0"
}
},
- "node_modules/mock-socket": {
- "version": "9.1.5",
- "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz",
- "integrity": "sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -8014,12 +8004,6 @@
"ufo": "^1.3.0"
}
},
- "mock-socket": {
- "version": "9.1.5",
- "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz",
- "integrity": "sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg==",
- "dev": true
- },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
diff --git a/package.json b/package.json
index 8fbae1db..0b421bc7 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,6 @@
"eslint-plugin-jsdoc": "^39.8.0",
"eslint-plugin-security": "^1.7.1",
"husky": "^8.0.0",
- "mock-socket": "^9.1.5",
"prettier": "^2.8.3",
"rollup": "^3.28.0",
"ts-node": "^10.9.1",
From 8ee7dbc3cafaf7f29f59638c4d6b9b13fde3e283 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 30 Aug 2023 08:24:04 +0000
Subject: [PATCH 030/191] build(deps-dev): bump rollup from 3.28.0 to 3.28.1
Bumps [rollup](https://github.com/rollup/rollup) from 3.28.0 to 3.28.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.28.0...v3.28.1)
---
updated-dependencies:
- dependency-name: rollup
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 24277586..dffd5147 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4320,9 +4320,9 @@
}
},
"node_modules/rollup": {
- "version": "3.28.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz",
- "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==",
+ "version": "3.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
+ "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -8398,9 +8398,9 @@
}
},
"rollup": {
- "version": "3.28.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz",
- "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==",
+ "version": "3.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
+ "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
From 6d2b7fa888ed4f9991e4bdbfa18afc45b66ed8f6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 30 Aug 2023 08:24:46 +0000
Subject: [PATCH 031/191] build(deps-dev): bump @typescript-eslint/parser from
5.51.0 to 5.62.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.51.0 to 5.62.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.62.0/packages/parser)
---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 141 ++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 129 insertions(+), 12 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index dffd5147..ec1209d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -897,14 +897,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "5.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz",
- "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==",
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
+ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "5.51.0",
- "@typescript-eslint/types": "5.51.0",
- "@typescript-eslint/typescript-estree": "5.51.0",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
"debug": "^4.3.4"
},
"engines": {
@@ -923,6 +923,80 @@
}
}
},
+ "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
+ "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
+ "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
+ "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
+ "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz",
@@ -5930,15 +6004,58 @@
}
},
"@typescript-eslint/parser": {
- "version": "5.51.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz",
- "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==",
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
+ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "5.51.0",
- "@typescript-eslint/types": "5.51.0",
- "@typescript-eslint/typescript-estree": "5.51.0",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
"debug": "^4.3.4"
+ },
+ "dependencies": {
+ "@typescript-eslint/scope-manager": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
+ "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
+ "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
+ "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
+ "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.62.0",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ }
}
},
"@typescript-eslint/scope-manager": {
From c9b1c8603be3b8dc0b3fc5779d8048a92952aecf Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 30 Aug 2023 08:24:23 +0000
Subject: [PATCH 032/191] build(deps-dev): bump @vitest/coverage-c8 from 0.28.4
to 0.33.0
Bumps [@vitest/coverage-c8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-c8) from 0.28.4 to 0.33.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v0.33.0/packages/coverage-c8)
---
updated-dependencies:
- dependency-name: "@vitest/coverage-c8"
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 670 ++++++----------------------------------------
package.json | 2 +-
2 files changed, 84 insertions(+), 588 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ec1209d5..29ceee98 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"@rollup/plugin-terser": "^0.4.3",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
- "@vitest/coverage-c8": "^0.28.4",
+ "@vitest/coverage-c8": "^0.33.0",
"eslint": "^8.33.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^39.8.0",
@@ -50,6 +50,19 @@
"bops": "^1.0.1"
}
},
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+ "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -1125,152 +1138,23 @@
}
},
"node_modules/@vitest/coverage-c8": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.28.4.tgz",
- "integrity": "sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==",
- "dev": true,
- "dependencies": {
- "c8": "^7.12.0",
- "picocolors": "^1.0.0",
- "std-env": "^3.3.1",
- "vitest": "0.28.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/@vitest/coverage-c8/node_modules/@vitest/expect": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.4.tgz",
- "integrity": "sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==",
- "dev": true,
- "dependencies": {
- "@vitest/spy": "0.28.4",
- "@vitest/utils": "0.28.4",
- "chai": "^4.3.7"
- }
- },
- "node_modules/@vitest/coverage-c8/node_modules/@vitest/runner": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.4.tgz",
- "integrity": "sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==",
- "dev": true,
- "dependencies": {
- "@vitest/utils": "0.28.4",
- "p-limit": "^4.0.0",
- "pathe": "^1.1.0"
- }
- },
- "node_modules/@vitest/coverage-c8/node_modules/@vitest/spy": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.4.tgz",
- "integrity": "sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==",
- "dev": true,
- "dependencies": {
- "tinyspy": "^1.0.2"
- }
- },
- "node_modules/@vitest/coverage-c8/node_modules/@vitest/utils": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.4.tgz",
- "integrity": "sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==",
- "dev": true,
- "dependencies": {
- "cli-truncate": "^3.1.0",
- "diff": "^5.1.0",
- "loupe": "^2.3.6",
- "picocolors": "^1.0.0",
- "pretty-format": "^27.5.1"
- }
- },
- "node_modules/@vitest/coverage-c8/node_modules/vite-node": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.4.tgz",
- "integrity": "sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==",
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.33.0.tgz",
+ "integrity": "sha512-DaF1zJz4dcOZS4k/neiQJokmOWqsGXwhthfmUdPGorXIQHjdPvV6JQSYhQDI41MyI8c+IieQUdIDs5XAMHtDDw==",
+ "deprecated": "v8 coverage is moved to @vitest/coverage-v8 package",
"dev": true,
"dependencies": {
- "cac": "^6.7.14",
- "debug": "^4.3.4",
- "mlly": "^1.1.0",
- "pathe": "^1.1.0",
- "picocolors": "^1.0.0",
- "source-map": "^0.6.1",
- "source-map-support": "^0.5.21",
- "vite": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "vite-node": "vite-node.mjs"
- },
- "engines": {
- "node": ">=v14.16.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/@vitest/coverage-c8/node_modules/vitest": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.4.tgz",
- "integrity": "sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==",
- "dev": true,
- "dependencies": {
- "@types/chai": "^4.3.4",
- "@types/chai-subset": "^1.3.3",
- "@types/node": "*",
- "@vitest/expect": "0.28.4",
- "@vitest/runner": "0.28.4",
- "@vitest/spy": "0.28.4",
- "@vitest/utils": "0.28.4",
- "acorn": "^8.8.1",
- "acorn-walk": "^8.2.0",
- "cac": "^6.7.14",
- "chai": "^4.3.7",
- "debug": "^4.3.4",
- "local-pkg": "^0.4.2",
- "pathe": "^1.1.0",
+ "@ampproject/remapping": "^2.2.1",
+ "c8": "^7.14.0",
+ "magic-string": "^0.30.1",
"picocolors": "^1.0.0",
- "source-map": "^0.6.1",
- "std-env": "^3.3.1",
- "strip-literal": "^1.0.0",
- "tinybench": "^2.3.1",
- "tinypool": "^0.3.1",
- "tinyspy": "^1.0.2",
- "vite": "^3.0.0 || ^4.0.0",
- "vite-node": "0.28.4",
- "why-is-node-running": "^2.2.2"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": ">=v14.16.0"
+ "std-env": "^3.3.3"
},
"funding": {
- "url": "https://github.com/sponsors/antfu"
+ "url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@edge-runtime/vm": "*",
- "@vitest/browser": "*",
- "@vitest/ui": "*",
- "happy-dom": "*",
- "jsdom": "*"
- },
- "peerDependenciesMeta": {
- "@edge-runtime/vm": {
- "optional": true
- },
- "@vitest/browser": {
- "optional": true
- },
- "@vitest/ui": {
- "optional": true
- },
- "happy-dom": {
- "optional": true
- },
- "jsdom": {
- "optional": true
- }
+ "vitest": ">=0.30.0 <1"
}
},
"node_modules/@vitest/expect": {
@@ -1719,9 +1603,9 @@
}
},
"node_modules/c8": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz",
- "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==",
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz",
+ "integrity": "sha512-i04rtkkcNcCf7zsQcSv/T9EbUn4RXQ6mropeMcjFOsQXQ0iGLAr/xT6TImQg4+U9hmNpN9XdvPkjUL1IzbgxJw==",
"dev": true,
"dependencies": {
"@bcoe/v8-coverage": "^0.2.3",
@@ -1845,72 +1729,6 @@
"node": "*"
}
},
- "node_modules/cli-truncate": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
- "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
- "dev": true,
- "dependencies": {
- "slice-ansi": "^5.0.0",
- "string-width": "^5.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cli-truncate/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/cli-truncate/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
- },
- "node_modules/cli-truncate/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cli-truncate/node_modules/strip-ansi": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
- "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -2095,15 +1913,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/diff": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
- "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
- "dev": true,
- "engines": {
- "node": ">=0.3.1"
- }
- },
"node_modules/diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -2137,12 +1946,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true
- },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -3517,23 +3320,23 @@
}
},
"node_modules/istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
+ "make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
}
},
"node_modules/istanbul-reports": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
- "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
"dev": true,
"dependencies": {
"html-escaper": "^2.0.0",
@@ -3704,29 +3507,20 @@
"dev": true
},
"node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"dependencies": {
- "semver": "^6.0.0"
+ "semver": "^7.5.3"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/make-dir/node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -4185,32 +3979,6 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "engines": {
- "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -4271,12 +4039,6 @@
"safe-buffer": "^5.1.0"
}
},
- "node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true
- },
"node_modules/regexp-tree": {
"version": "0.1.24",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz",
@@ -4494,9 +4256,9 @@
}
},
"node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -4573,46 +4335,6 @@
"node": ">=8"
}
},
- "node_modules/slice-ansi": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
- "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^6.0.0",
- "is-fullwidth-code-point": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/slice-ansi/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
- "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/smob": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz",
@@ -4853,24 +4575,6 @@
"integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==",
"dev": true
},
- "node_modules/tinypool": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz",
- "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==",
- "dev": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tinyspy": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz",
- "integrity": "sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==",
- "dev": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5122,9 +4826,9 @@
"dev": true
},
"node_modules/v8-to-istanbul": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz",
- "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
+ "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
@@ -5469,6 +5173,16 @@
"bops": "^1.0.1"
}
},
+ "@ampproject/remapping": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+ "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
"@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -6128,109 +5842,16 @@
}
},
"@vitest/coverage-c8": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.28.4.tgz",
- "integrity": "sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==",
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.33.0.tgz",
+ "integrity": "sha512-DaF1zJz4dcOZS4k/neiQJokmOWqsGXwhthfmUdPGorXIQHjdPvV6JQSYhQDI41MyI8c+IieQUdIDs5XAMHtDDw==",
"dev": true,
"requires": {
- "c8": "^7.12.0",
+ "@ampproject/remapping": "^2.2.1",
+ "c8": "^7.14.0",
+ "magic-string": "^0.30.1",
"picocolors": "^1.0.0",
- "std-env": "^3.3.1",
- "vitest": "0.28.4"
- },
- "dependencies": {
- "@vitest/expect": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.4.tgz",
- "integrity": "sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==",
- "dev": true,
- "requires": {
- "@vitest/spy": "0.28.4",
- "@vitest/utils": "0.28.4",
- "chai": "^4.3.7"
- }
- },
- "@vitest/runner": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.4.tgz",
- "integrity": "sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==",
- "dev": true,
- "requires": {
- "@vitest/utils": "0.28.4",
- "p-limit": "^4.0.0",
- "pathe": "^1.1.0"
- }
- },
- "@vitest/spy": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.4.tgz",
- "integrity": "sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==",
- "dev": true,
- "requires": {
- "tinyspy": "^1.0.2"
- }
- },
- "@vitest/utils": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.4.tgz",
- "integrity": "sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==",
- "dev": true,
- "requires": {
- "cli-truncate": "^3.1.0",
- "diff": "^5.1.0",
- "loupe": "^2.3.6",
- "picocolors": "^1.0.0",
- "pretty-format": "^27.5.1"
- }
- },
- "vite-node": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.4.tgz",
- "integrity": "sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==",
- "dev": true,
- "requires": {
- "cac": "^6.7.14",
- "debug": "^4.3.4",
- "mlly": "^1.1.0",
- "pathe": "^1.1.0",
- "picocolors": "^1.0.0",
- "source-map": "^0.6.1",
- "source-map-support": "^0.5.21",
- "vite": "^3.0.0 || ^4.0.0"
- }
- },
- "vitest": {
- "version": "0.28.4",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.4.tgz",
- "integrity": "sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==",
- "dev": true,
- "requires": {
- "@types/chai": "^4.3.4",
- "@types/chai-subset": "^1.3.3",
- "@types/node": "*",
- "@vitest/expect": "0.28.4",
- "@vitest/runner": "0.28.4",
- "@vitest/spy": "0.28.4",
- "@vitest/utils": "0.28.4",
- "acorn": "^8.8.1",
- "acorn-walk": "^8.2.0",
- "cac": "^6.7.14",
- "chai": "^4.3.7",
- "debug": "^4.3.4",
- "local-pkg": "^0.4.2",
- "pathe": "^1.1.0",
- "picocolors": "^1.0.0",
- "source-map": "^0.6.1",
- "std-env": "^3.3.1",
- "strip-literal": "^1.0.0",
- "tinybench": "^2.3.1",
- "tinypool": "^0.3.1",
- "tinyspy": "^1.0.2",
- "vite": "^3.0.0 || ^4.0.0",
- "vite-node": "0.28.4",
- "why-is-node-running": "^2.2.2"
- }
- }
+ "std-env": "^3.3.3"
}
},
"@vitest/expect": {
@@ -6565,9 +6186,9 @@
"dev": true
},
"c8": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz",
- "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==",
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz",
+ "integrity": "sha512-i04rtkkcNcCf7zsQcSv/T9EbUn4RXQ6mropeMcjFOsQXQ0iGLAr/xT6TImQg4+U9hmNpN9XdvPkjUL1IzbgxJw==",
"dev": true,
"requires": {
"@bcoe/v8-coverage": "^0.2.3",
@@ -6658,50 +6279,6 @@
"integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
"dev": true
},
- "cli-truncate": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
- "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
- "dev": true,
- "requires": {
- "slice-ansi": "^5.0.0",
- "string-width": "^5.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "dev": true
- },
- "emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
- },
- "string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "requires": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- }
- },
- "strip-ansi": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
- "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
- "dev": true,
- "requires": {
- "ansi-regex": "^6.0.1"
- }
- }
- }
- },
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -6841,12 +6418,6 @@
"object-keys": "^1.1.1"
}
},
- "diff": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
- "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
- "dev": true
- },
"diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -6871,12 +6442,6 @@
"esutils": "^2.0.2"
}
},
- "eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true
- },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -7900,20 +7465,20 @@
"dev": true
},
"istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"requires": {
"istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
+ "make-dir": "^4.0.0",
"supports-color": "^7.1.0"
}
},
"istanbul-reports": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
- "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
"dev": true,
"requires": {
"html-escaper": "^2.0.0",
@@ -8050,20 +7615,12 @@
}
},
"make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"requires": {
- "semver": "^6.0.0"
- },
- "dependencies": {
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
- }
+ "semver": "^7.5.3"
}
},
"make-error": {
@@ -8376,25 +7933,6 @@
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"dev": true
},
- "pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true
- }
- }
- },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -8432,12 +7970,6 @@
"safe-buffer": "^5.1.0"
}
},
- "react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true
- },
"regexp-tree": {
"version": "0.1.24",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz",
@@ -8571,9 +8103,9 @@
}
},
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -8632,30 +8164,6 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
- "slice-ansi": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
- "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
- "dev": true,
- "requires": {
- "ansi-styles": "^6.0.0",
- "is-fullwidth-code-point": "^4.0.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
- "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
- "dev": true
- }
- }
- },
"smob": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz",
@@ -8842,18 +8350,6 @@
"integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==",
"dev": true
},
- "tinypool": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz",
- "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==",
- "dev": true
- },
- "tinyspy": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz",
- "integrity": "sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==",
- "dev": true
- },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -9033,9 +8529,9 @@
"dev": true
},
"v8-to-istanbul": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz",
- "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
+ "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==",
"dev": true,
"requires": {
"@jridgewell/trace-mapping": "^0.3.12",
diff --git a/package.json b/package.json
index 0b421bc7..5ba0a557 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
"@rollup/plugin-terser": "^0.4.3",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
- "@vitest/coverage-c8": "^0.28.4",
+ "@vitest/coverage-c8": "^0.33.0",
"eslint": "^8.33.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^39.8.0",
From 6f396c394b551009d8185715df30431c93abe051 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Wed, 30 Aug 2023 11:17:30 +0100
Subject: [PATCH 033/191] Update channel behaviours doc
---
docs/channel-behaviors.md | 41 +++++++++------------------------------
1 file changed, 9 insertions(+), 32 deletions(-)
diff --git a/docs/channel-behaviors.md b/docs/channel-behaviors.md
index e3949151..99362840 100644
--- a/docs/channel-behaviors.md
+++ b/docs/channel-behaviors.md
@@ -1,44 +1,21 @@
-# Channel Behaviors
-
-**Contents**
-
-- [Channel Behaviors](#channel-behaviors)
- - [Introduction \& Context](#introduction--context)
- - [Channels](#channels)
- - [Space](#space)
- - [Events Fired](#events-fired)
- - [Cursors](#cursors)
- - [Events Fired](#events-fired-1)
+# Channel usage
## Introduction & Context
-These channels are used by the Spaces library and features of the Spaces library including Live Cursors.
-
-It is not recommended that you rely on these channels directly, without the Spaces library, so this documentation is provided with the expectation that the channels are used for the purposes of monitoring, debugging, or learning more about how the Space API works.
-
-## Channels
+The below channels are used by the Spaces library internally.
-### Space
+### Space channel
-Each `Space` (as defined by the [`Space` class](/docs/class-definitions.md#space)) has its own `channel` assigned to it.
+Each `Space` (as defined by the [`Space` class](/docs/class-definitions.md#space)) creates its own [Ably Channel](https://ably.com/docs/channels).
-The `channel` name is defined by the `name` of the `Space` and takes the form: `_ably_space_${name}`.
-
-The full name of a `channel` belonging to a `Space` called 'my_space' would therefore be `_ably_space_my_space`.
-
-#### Events Fired
-
-1. `membersUpdate` - an update to any number of members of the Space
-2. `locationUpdate` - an update specifically to the `Location` of any number of members of the Space
+The channel name is defined by the `name` of the Space and takes the form: `_ably_space_${name}`. The full name of a `channel` belonging to a `Space` called 'my_space' would therefore be `_ably_space_my_space`.
### Cursors
-Any `Space` may also make use of the Spaces library's `Cursors` feature.
-
-The `Cursors` `channel` name is defined by the `channelName` of the Space with the `_cursors` suffix, taking the form `${space.getChannelName()}_cursors`.
+If any member of a `Space` subscribes to or sets cursor updates a channel is created for `cursors` updates.
-The full name of a `channel` belonging to a set of `Cursors` belonging to a `Space` called 'my_space' would therefore be `_ably_space_my_space_cursors`.
+The channel name is defined by the channel name of the Space with the `_cursors` suffix, taking the form: `${space.getChannelName()}_cursors`. The full name of a channel belonging to a `Space` called 'my_space' would therefore be `_ably_space_my_space_cursors`.
-#### Events Fired
+#### Events published
-1. `cursorUpdate` - an update to any number of cursors associated with any number of members of the Space
+1. `cursorUpdate` - a batch of cursor updates passed to [`set`](/docs/class-definitions.md#set).
From 34d1b0b44268652c0a9b8946b58190e36c47badb Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Wed, 30 Aug 2023 11:17:56 +0100
Subject: [PATCH 034/191] Rename doc
---
docs/{channel-behaviors.md => channel-usage.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/{channel-behaviors.md => channel-usage.md} (100%)
diff --git a/docs/channel-behaviors.md b/docs/channel-usage.md
similarity index 100%
rename from docs/channel-behaviors.md
rename to docs/channel-usage.md
From c5bd90996479a75a106d9e9e2e945b8c357c8c7e Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Wed, 30 Aug 2023 17:17:58 +0100
Subject: [PATCH 035/191] Flatten lock structure
Move properties from the request key to the lock.
Having the request property proved to be not-intuitive and wordy.
---
src/Locks.test.ts | 15 +++----
src/Locks.ts | 88 ++++++++++++++++++++++--------------------
src/index.ts | 1 -
src/types.ts | 6 +--
src/utilities/types.ts | 4 +-
5 files changed, 57 insertions(+), 57 deletions(-)
diff --git a/src/Locks.test.ts b/src/Locks.test.ts
index 18d510b8..5c1023d3 100644
--- a/src/Locks.test.ts
+++ b/src/Locks.test.ts
@@ -106,7 +106,8 @@ describe('Locks (mockClient)', () => {
const lockEvent = (member: SpaceMember, status: LockStatus) =>
expect.objectContaining({
member: member,
- request: expect.objectContaining({ id: lockID, status }),
+ id: lockID,
+ status,
});
it('sets a PENDING request to LOCKED', async ({ space }) => {
@@ -130,7 +131,7 @@ describe('Locks (mockClient)', () => {
});
await space.locks.processPresenceMessage(msg);
- const lock = space.locks.getLockRequest(lockID, member.connectionId)!;
+ const lock = space.locks.getLock(lockID, member.connectionId)!;
expect(lock.status).toBe('locked');
expect(emitSpy).toHaveBeenCalledWith('update', lockEvent(member, 'locked'));
});
@@ -214,10 +215,10 @@ describe('Locks (mockClient)', () => {
});
await space.locks.processPresenceMessage(msg);
const selfMember = (await space.members.getByConnectionId(client.connection.id!))!;
- const selfLock = space.locks.getLockRequest(lockID, selfMember.connectionId)!;
+ const selfLock = space.locks.getLock(lockID, selfMember.connectionId)!;
expect(selfLock.status).toBe(expectedSelfStatus);
const otherMember = (await space.members.getByConnectionId(otherConnId))!;
- const otherLock = space.locks.getLockRequest(lockID, otherMember.connectionId)!;
+ const otherLock = space.locks.getLock(lockID, otherMember.connectionId)!;
expect(otherLock.status).toBe(expectedOtherStatus);
if (expectedSelfStatus === 'unlocked') {
@@ -259,7 +260,7 @@ describe('Locks (mockClient)', () => {
});
await space.locks.processPresenceMessage(msg);
- const lock = space.locks.getLockRequest(lockID, member.connectionId);
+ const lock = space.locks.getLock(lockID, member.connectionId);
expect(lock).not.toBeDefined();
expect(emitSpy).toHaveBeenCalledWith('update', lockEvent(member, 'unlocked'));
});
@@ -393,7 +394,7 @@ describe('Locks (mockClient)', () => {
const locks = space.locks.getAll();
expect(locks.length).toEqual(3);
for (const lock of locks) {
- switch (lock.request.id) {
+ switch (lock.id) {
case 'lock1':
case 'lock2':
expect(lock.member).toEqual(member1);
@@ -402,7 +403,7 @@ describe('Locks (mockClient)', () => {
expect(lock.member).toEqual(member2);
break;
default:
- throw new Error(`unexpected lock id: ${lock.request.id}`);
+ throw new Error(`unexpected lock id: ${lock.id}`);
}
}
});
diff --git a/src/Locks.ts b/src/Locks.ts
index 06a8144a..feb20e3f 100644
--- a/src/Locks.ts
+++ b/src/Locks.ts
@@ -1,7 +1,7 @@
import { Types } from 'ably';
import Space from './Space.js';
-import type { Lock, LockRequest, SpaceMember } from './types.js';
+import type { Lock, SpaceMember } from './types.js';
import type { PresenceMember } from './utilities/types.js';
import {
ERR_LOCK_IS_LOCKED,
@@ -52,18 +52,18 @@ export default class Locks extends EventEmitter {
const locks = this.locks.get(id);
if (!locks) return;
for (const lock of locks.values()) {
- if (lock.request.status === 'locked') {
+ if (lock.status === 'locked') {
return lock;
}
}
}
getAll(): Lock[] {
- const allLocks = [];
+ const allLocks: Lock[] = [];
for (const locks of this.locks.values()) {
for (const lock of locks.values()) {
- if (lock.request.status === 'locked') {
+ if (lock.status === 'locked') {
allLocks.push(lock);
}
}
@@ -72,7 +72,7 @@ export default class Locks extends EventEmitter {
return allLocks;
}
- async acquire(id: string, opts?: LockOptions): Promise {
+ async acquire(id: string, opts?: LockOptions): Promise {
const self = await this.space.members.getSelf();
if (!self) {
throw ERR_NOT_ENTERED_SPACE;
@@ -80,26 +80,29 @@ export default class Locks extends EventEmitter {
// check there isn't an existing PENDING or LOCKED request for the current
// member, since we do not support nested locks
- let req = this.getLockRequest(id, self.connectionId);
- if (req && req.status !== 'unlocked') {
+ let lock = this.getLock(id, self.connectionId);
+ if (lock && lock.status !== 'unlocked') {
throw ERR_LOCK_REQUEST_EXISTS;
}
// initialise a new PENDING request
- req = {
+ lock = {
id,
status: 'pending',
timestamp: Date.now(),
+ member: self,
};
+
if (opts) {
- req.attributes = opts.attributes;
+ lock.attributes = opts.attributes;
}
- this.setLock({ member: self, request: req });
+
+ this.setLock(lock);
// reflect the change in the member's presence data
await this.updatePresence(self);
- return req;
+ return lock;
}
async release(id: string): Promise {
@@ -156,18 +159,20 @@ export default class Locks extends EventEmitter {
// existing locks for that member
for (const locks of this.locks.values()) {
const lock = locks.get(member.connectionId);
+
if (lock) {
- lock.request.status = 'unlocked';
- lock.request.reason = ERR_LOCK_RELEASED;
+ lock.status = 'unlocked';
+ lock.reason = ERR_LOCK_RELEASED;
locks.delete(member.connectionId);
this.emit('update', lock);
}
}
+
return;
}
- message.extras.locks.forEach((lock: LockRequest) => {
- const existing = this.getLockRequest(lock.id, member.connectionId);
+ message.extras.locks.forEach((lock: Lock) => {
+ const existing = this.getLock(lock.id, member.connectionId);
// special-case the handling of PENDING requests, which will eventually
// be done by the Ably system, at which point this can be removed
@@ -176,21 +181,23 @@ export default class Locks extends EventEmitter {
}
if (!existing || existing.status !== lock.status) {
- this.emit('update', { member, request: lock });
+ this.emit('update', { ...lock, member });
}
- this.setLock({ member, request: lock });
+ this.setLock({ ...lock, member });
});
// handle locks which have been removed from presence extras
for (const locks of this.locks.values()) {
const lock = locks.get(member.connectionId);
+
if (!lock) {
continue;
}
- if (!message.extras.locks.some((req: LockRequest) => req.id === lock.request.id)) {
- lock.request.status = 'unlocked';
- lock.request.reason = ERR_LOCK_RELEASED;
+
+ if (!message.extras.locks.some((l: Lock) => l.id === lock.id)) {
+ lock.status = 'unlocked';
+ lock.reason = ERR_LOCK_RELEASED;
locks.delete(member.connectionId);
this.emit('update', lock);
}
@@ -203,12 +210,12 @@ export default class Locks extends EventEmitter {
//
// TODO: remove this once the Ably system processes PENDING requests
// internally using this same logic.
- processPending(member: SpaceMember, pendingReq: LockRequest) {
+ processPending(member: SpaceMember, pendingLock: Lock) {
// if the requested lock ID is not currently locked, then mark the PENDING
- // request as LOCKED
- const lock = this.get(pendingReq.id);
+ // lock request as LOCKED
+ const lock = this.get(pendingLock.id);
if (!lock) {
- pendingReq.status = 'locked';
+ pendingLock.status = 'locked';
return;
}
@@ -231,20 +238,20 @@ export default class Locks extends EventEmitter {
// a connectionId which sorts lexicographically before the connectionId of
// the LOCKED request.
if (
- pendingReq.timestamp < lock.request.timestamp ||
- (pendingReq.timestamp == lock.request.timestamp && member.connectionId < lock.member.connectionId)
+ pendingLock.timestamp < lock.timestamp ||
+ (pendingLock.timestamp == lock.timestamp && member.connectionId < lock.member.connectionId)
) {
- pendingReq.status = 'locked';
- lock.request.status = 'unlocked';
- lock.request.reason = ERR_LOCK_INVALIDATED;
+ pendingLock.status = 'locked';
+ lock.status = 'unlocked';
+ lock.reason = ERR_LOCK_INVALIDATED;
this.emit('update', lock);
return;
}
// the lock is LOCKED and the PENDING request did not invalidate it, so
// mark the PENDING request as UNLOCKED with a reason.
- pendingReq.status = 'unlocked';
- pendingReq.reason = ERR_LOCK_IS_LOCKED;
+ pendingLock.status = 'unlocked';
+ pendingLock.reason = ERR_LOCK_IS_LOCKED;
}
updatePresence(member: SpaceMember) {
@@ -269,10 +276,10 @@ export default class Locks extends EventEmitter {
}
setLock(lock: Lock) {
- let locks = this.locks.get(lock.request.id);
+ let locks = this.locks.get(lock.id);
if (!locks) {
locks = new Map();
- this.locks.set(lock.request.id, locks);
+ this.locks.set(lock.id, locks);
}
locks.set(lock.member.connectionId, lock);
}
@@ -283,25 +290,22 @@ export default class Locks extends EventEmitter {
return locks.delete(connectionId);
}
- getLockRequest(id: string, connectionId: string): LockRequest | undefined {
- const lock = this.getLock(id, connectionId);
- if (!lock) return;
- return lock.request;
- }
+ getLocksForConnectionId(connectionId: string): Lock[] {
+ const requests: Lock[] = [];
- getLockRequests(connectionId: string): LockRequest[] {
- const requests = [];
for (const locks of this.locks.values()) {
const lock = locks.get(connectionId);
+
if (lock) {
- requests.push(lock.request);
+ requests.push(lock);
}
}
+
return requests;
}
getLockExtras(connectionId: string): PresenceMember['extras'] {
- const locks = this.getLockRequests(connectionId);
+ const locks = this.getLocksForConnectionId(connectionId);
if (locks.length === 0) return;
return { locks };
}
diff --git a/src/index.ts b/src/index.ts
index 4b47cb95..f51aacdb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -15,7 +15,6 @@ export type {
ProfileData,
SpaceMember,
Lock,
- LockRequest,
LockStatus,
} from './types.js';
diff --git a/src/types.ts b/src/types.ts
index 9698e220..9641b53c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -42,13 +42,9 @@ export interface SpaceMember {
export type LockStatus = 'pending' | 'locked' | 'unlocked';
export type Lock = {
- member: SpaceMember;
- request: LockRequest;
-};
-
-export type LockRequest = {
id: string;
status: LockStatus;
+ member: SpaceMember;
timestamp: number;
attributes?: LockAttributes;
reason?: Types.ErrorInfo;
diff --git a/src/utilities/types.ts b/src/utilities/types.ts
index 26fb1094..cd3e2f5a 100644
--- a/src/utilities/types.ts
+++ b/src/utilities/types.ts
@@ -1,7 +1,7 @@
import type { Types } from 'ably';
import type { EventKey, EventListener, EventMap } from './EventEmitter.js';
-import type { ProfileData, LockRequest } from '../types.js';
+import type { ProfileData, Lock } from '../types.js';
export type PresenceMember = {
data: {
@@ -16,7 +16,7 @@ export type PresenceMember = {
};
};
extras?: {
- locks: LockRequest[];
+ locks: Lock[];
};
} & Omit;
From bde04489e96f88a07c79714e53fd3fd219ee411c Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Tue, 29 Aug 2023 13:19:05 +0100
Subject: [PATCH 036/191] Documentation tweaks
---
README.md | 28 ++++++++++++++--------------
docs/class-definitions.md | 18 +++++++++---------
docs/usage.md | 6 +++---
3 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index b7a6db3b..b4f66116 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ To instantiate the Spaces SDK, create an [Ably client](https://ably.com/docs/get
import Spaces from '@ably-labs/spaces';
import { Realtime } from 'ably';
-const client = new Realtime.Promise({key: "", clientId: ""});
+const client = new Realtime.Promise({ key: "", clientId: "" });
const spaces = new Spaces(client);
```
You can use [basic authentication](https://ably.com/docs/auth/basic) i.e. the API Key directly for testing purposes, however it is strongly recommended that you use [token authentication](https://ably.com/docs/auth/token) in production environments.
@@ -107,7 +107,7 @@ space.subscribe('update', (spaceState) => {
});
// Enter a space, publishing an update event, including optional profile data
-space.enter({
+await space.enter({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/clemons.png',
});
@@ -136,7 +136,7 @@ The following is an example event payload received by subscribers when a user en
## Space members
-The `members` namespace within a Space is a client-side filtered listener optimized for building avatar stacks. Subscribe to members entering, leaving, being removed from the Space (after a timeout) or updating their profile information.
+The `members` namespace contains methods dedicated to building avatar stacks. Subscribe to members entering, leaving, being removed from the Space (after a timeout) or updating their profile information.
```ts
// Subscribe to all member events in a space
@@ -201,17 +201,17 @@ const othersMemberInfo = await space.members.getOthers();
## Member locations
-The `locations` namespace within a Space is a client-side filtered listener optimized for building member locations which enable you to track where users are within an application. A location could be a form field, multiple cells in a spreadsheet or a slide in a slide deck editor.
+The `locations` namespace contains methods dedicated to building member locations, enabling you to track where users are within an application. A location could be a form field, multiple cells in a spreadsheet or a slide in a slide deck editor.
```ts
// You need to enter a space before publishing your location
-space.enter({
+await space.enter({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/clemons.png',
});
// Publish your location based on the UI element selected
-space.locations.set({ slide: '3', component: 'slide-title' });
+await space.locations.set({ slide: '3', component: 'slide-title' });
// Subscribe to location events from all members in a space
space.locations.subscribe('update', (locationUpdate) => {
@@ -258,22 +258,22 @@ Member locations has methods to get the current snapshot of member state:
```ts
// Get a snapshot of all the member locations
-const allLocations = space.locations.getAll();
+const allLocations = await space.locations.getAll();
// Get a snapshot of my location
-const myLocation = space.locations.getSelf();
+const myLocation = await space.locations.getSelf();
// Get a snapshot of everyone else's locations
-const othersLocations = space.locations.getOthers();
+const othersLocations = await space.locations.getOthers();
```
## Live cursors
-The `cursors` namespace within a Space is a client-side filtered listener optimized for building live cursors which allows you to track a member's pointer position updates across an application. Events can also include associated data, such as pointer attributes and the IDs of associated UI elements:
+The `cursors` namespace contains methods dedicated to building live cursors, allowing you to track a member's pointer position updates across an application. Events can also include associated data, such as pointer attributes and the IDs of associated UI elements:
```ts
// You need to enter a space before publishing your cursor updates
-space.enter({
+await space.enter({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/clemons.png',
});
@@ -306,11 +306,11 @@ Member cursors has methods to get the current snapshot of member state:
```ts
// Get a snapshot of all the cursors
-const allCursors = space.cursors.getAll();
+const allCursors = await space.cursors.getAll();
// Get a snapshot of my cursor
-const myCursor = space.cursors.getSelf();
+const myCursor = await space.cursors.getSelf();
// Get a snapshot of everyone else's cursors
-const othersCursors = space.cursors.getOthers();
+const othersCursors = await space.cursors.getOthers();
```
diff --git a/docs/class-definitions.md b/docs/class-definitions.md
index fdc4fce7..4c087364 100644
--- a/docs/class-definitions.md
+++ b/docs/class-definitions.md
@@ -224,10 +224,10 @@ space.members.unsubscribe('leave');
#### getSelf()
-Returns a Promise which resolves to the [SpaceMember](#spacemember) object relating to the local connection. Will resolve to `undefined` if the client hasn't entered the space yet.
+Returns a Promise which resolves to the [SpaceMember](#spacemember) object relating to the local connection. Will resolve to `null` if the client hasn't entered the space yet.
```ts
-type getSelf = () => Promise;
+type getSelf = () => Promise;
```
Example:
@@ -355,7 +355,7 @@ space.locations.unsubscribe('update');
Get location for self.
```ts
-type getSelf = () => Promise;
+type getSelf = () => Promise;
```
Example:
@@ -465,13 +465,13 @@ space.cursors.unsubscribe('update');
Get the last `CursorUpdate` object for self.
```ts
-type getSelf = () => ;
+type getSelf = () => Promise;
```
Example:
```ts
-const selfPosition = space.cursors.getSelf();
+const selfPosition = await space.cursors.getSelf();
```
#### getAll()
@@ -479,13 +479,13 @@ const selfPosition = space.cursors.getSelf();
Get the last `CursorUpdate` object for all the members.
```ts
-type getAll = () => Record;
+type getAll = () => Promise>;
```
Example:
```ts
-const allLatestPositions = space.cursors.getAll();
+const allLatestPositions = await space.cursors.getAll();
```
#### getOthers()
@@ -493,13 +493,13 @@ const allLatestPositions = space.cursors.getAll();
Get the last `CursorUpdate` object for everyone else but yourself.
```ts
-type getOthers = () => Record;
+type getOthers = () => Promise>;
```
Example:
```ts
-const otherPositions = space.cursors.getOthers();
+const otherPositions = await space.cursors.getOthers();
```
### Related types
diff --git a/docs/usage.md b/docs/usage.md
index 62d22c35..82b69a4d 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -96,13 +96,13 @@ space.unsubscribe();
To become a member of a space (and use the other APIs, like location or cursors) a client needs to enter a space.
```ts
-space.enter();
+await space.enter();
```
This method can take an optional object called `profileData` so that users can include convenient metadata to update an avatar stack, such as a username and profile picture.
```ts
-space.enter({
+await space.enter({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/clemons.png',
});
@@ -121,7 +121,7 @@ A leave event does not remove the member immediately from `space.members`. Inste
As with `enter`, you can update the `profileData` on leave:
```ts
-space.leave({
+await space.leave({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/inactive.png',
});
From 32d9c14d1d3a4d033a166a8ca28f8a11a367a0eb Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Fri, 11 Aug 2023 13:06:15 +0100
Subject: [PATCH 037/191] Add documentation for simple connection and channels
management
---
docs/class-definitions.md | 12 ++++-
docs/connection-and-channel-management.md | 53 +++++++++++++++++++++++
2 files changed, 63 insertions(+), 2 deletions(-)
create mode 100644 docs/connection-and-channel-management.md
diff --git a/docs/class-definitions.md b/docs/class-definitions.md
index 4c087364..993ab045 100644
--- a/docs/class-definitions.md
+++ b/docs/class-definitions.md
@@ -20,12 +20,20 @@ Refer to the [Ably docs for the JS SDK](https://ably.com/docs/getting-started/se
### Properties
-#### ably
+#### client
Instance of the [Ably-JS](https://github.com/ably/ably-js#introduction) client that was passed to the [constructor](#constructor).
```ts
-type ably = Ably.RealtimePromise;
+type client = Ably.RealtimePromise;
+```
+
+#### connection
+
+Instance of the [Ably-JS](https://github.com/ably/ably-js#introduction) connection, belonging to the client that was passed to the [constructor](#constructor).
+
+```ts
+type connection = Ably.ConnectionPromise;
```
#### version
diff --git a/docs/connection-and-channel-management.md b/docs/connection-and-channel-management.md
new file mode 100644
index 00000000..d72dd016
--- /dev/null
+++ b/docs/connection-and-channel-management.md
@@ -0,0 +1,53 @@
+# Connection and channel management
+
+Spaces SDK uses the Ably Core SDK client [connections](https://ably.com/docs/connect) and [channels](https://ably.com/docs/channels) to provide higher level features like cursors or locations. Both connections and channels will transition through multiple states throughout their lifecycle. Most state transitions (like a short loss in connectivity) will be handled by the Ably SDK, but there will be use cases where developers will need to observe these states and handle them accordingly.
+
+This document describes how to access a connection and channels on Spaces, and where to find information about how to handle their state changes.
+
+## Connection
+
+When initializing the Spaces SDK, an Ably client is passed as a required argument:
+
+```ts
+const client = new Realtime.Promise({ key: "", clientId: "" });
+const spaces = new Spaces(client);
+```
+
+The Spaces instance exposes the underlying connection which is an event emitter. It can be used to listen for changes in connection state. The client and connection are both available on the Spaces instance:
+
+```ts
+spaces.client.connection.on('disconnected', () => {})
+spaces.connection.on('disconnected', () => {})
+```
+
+### Read more on:
+
+- [Connections](https://ably.com/docs/connect)
+- [Connection state and recovery](https://ably.com/docs/connect/states)
+
+## Channels
+
+When a Space is instantiated, it creates an underlying [Ably Channel](https://ably.com/docs/channels) which is used to deliver the functionality of each space. The Ably Channel object is available in the `.channel` attribute.
+
+Similar to a connection, a channel is an event emitter, allowing us to listen for state changes:
+
+```ts
+const mySpace = spaces.get('mySpace');
+mySpace.channel.once('suspended', () => {});
+```
+
+When using the Cursors API, an additional channel is used, but it will only be created if we attach a subscriber or set a cursor position:
+
+```ts
+mySpace.cursors.channel.once('attached', () => {});
+
+// but .channel will only be defined if one of these was called before
+mySpace.cursors.set({ position: { x, y } });
+mySpace.cursors.subscribe('update', () => {});
+```
+
+### Read more on:
+
+- [Channels](https://ably.com/docs/channels)
+- [Channel states](https://ably.com/docs/channels#states)
+- [Handle channel failure](https://ably.com/docs/channels#failure)
\ No newline at end of file
From e2b935d3edcd63d1858fea179c2b37a34d6e79ff Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Fri, 11 Aug 2023 13:17:05 +0100
Subject: [PATCH 038/191] Make channel on Cursors public
---
src/Cursors.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/Cursors.ts b/src/Cursors.ts
index b7101723..7cc02a1a 100644
--- a/src/Cursors.ts
+++ b/src/Cursors.ts
@@ -24,9 +24,10 @@ export default class Cursors extends EventEmitter {
private readonly cursorBatching: CursorBatching;
private readonly cursorDispensing: CursorDispensing;
private readonly cursorHistory: CursorHistory;
- private channel?: Types.RealtimeChannelPromise;
readonly options: CursorsOptions;
+ public channel?: Types.RealtimeChannelPromise;
+
constructor(private space: Space) {
super();
From 207dc952838dea9301778c1be23727c78218337f Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Tue, 29 Aug 2023 17:22:00 +0100
Subject: [PATCH 039/191] Rename .ably to .client
---
src/Spaces.test.ts | 4 ++--
src/Spaces.ts | 14 +++++++-------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/Spaces.test.ts b/src/Spaces.test.ts
index f204abab..a061fe35 100644
--- a/src/Spaces.test.ts
+++ b/src/Spaces.test.ts
@@ -16,7 +16,7 @@ describe('Spaces', () => {
it('expects the injected client to be of the type RealtimePromise', ({ client }) => {
const spaces = new Spaces(client);
- expectTypeOf(spaces.ably).toMatchTypeOf();
+ expectTypeOf(spaces.client).toMatchTypeOf();
});
it('creates and retrieves spaces successfully', async ({ client }) => {
@@ -41,7 +41,7 @@ describe('Spaces', () => {
it('extend the agents array when it already exists', ({ client }) => {
(client as ClientWithOptions).options.agents = { 'some-client': '1.2.3' };
const spaces = new Spaces(client);
- const ablyClient = spaces.ably as ClientWithOptions;
+ const ablyClient = spaces.client as ClientWithOptions;
expect(ablyClient.options.agents).toEqual({
'some-client': '1.2.3',
diff --git a/src/Spaces.ts b/src/Spaces.ts
index db589d0b..2f9c8012 100644
--- a/src/Spaces.ts
+++ b/src/Spaces.ts
@@ -14,14 +14,14 @@ export interface ClientWithOptions extends Types.RealtimePromise {
class Spaces {
private spaces: Record = {};
- ably: Types.RealtimePromise;
+ client: Types.RealtimePromise;
readonly version = '0.0.13';
constructor(client: Types.RealtimePromise) {
- this.ably = client;
- this.addAgent((this.ably as ClientWithOptions)['options']);
- this.ably.time();
+ this.client = client;
+ this.addAgent((this.client as ClientWithOptions)['options']);
+ this.client.time();
}
private addAgent(options: { agents?: Record }) {
@@ -36,11 +36,11 @@ class Spaces {
if (this.spaces[name]) return this.spaces[name];
- if (this.ably.connection.state !== 'connected') {
- await this.ably.connection.once('connected');
+ if (this.client.connection.state !== 'connected') {
+ await this.client.connection.once('connected');
}
- const space = new Space(name, this.ably, options);
+ const space = new Space(name, this.client, options);
this.spaces[name] = space;
return space;
From 32ce87efdcf67bc8975a99b3296ba9a47bee94d2 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Tue, 29 Aug 2023 17:23:38 +0100
Subject: [PATCH 040/191] Expose connection on Spaces directly
---
src/Spaces.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/Spaces.ts b/src/Spaces.ts
index 2f9c8012..bd9a5f8a 100644
--- a/src/Spaces.ts
+++ b/src/Spaces.ts
@@ -15,11 +15,13 @@ export interface ClientWithOptions extends Types.RealtimePromise {
class Spaces {
private spaces: Record = {};
client: Types.RealtimePromise;
+ connection: Types.ConnectionPromise;
readonly version = '0.0.13';
constructor(client: Types.RealtimePromise) {
this.client = client;
+ this.connection = client.connection;
this.addAgent((this.client as ClientWithOptions)['options']);
this.client.time();
}
@@ -36,8 +38,8 @@ class Spaces {
if (this.spaces[name]) return this.spaces[name];
- if (this.client.connection.state !== 'connected') {
- await this.client.connection.once('connected');
+ if (this.connection.state !== 'connected') {
+ await this.connection.once('connected');
}
const space = new Space(name, this.client, options);
From 9883e1f03ec97a778cc368fa79ec2f4d2714ea25 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Fri, 1 Sep 2023 15:37:18 +0100
Subject: [PATCH 041/191] Add getSelf and getOthers methods to locks
---
src/Locks.test.ts | 84 +++++++++++++++++++++++++++++++++++++----------
src/Locks.ts | 21 +++++++++++-
2 files changed, 86 insertions(+), 19 deletions(-)
diff --git a/src/Locks.test.ts b/src/Locks.test.ts
index 5c1023d3..1db845be 100644
--- a/src/Locks.test.ts
+++ b/src/Locks.test.ts
@@ -343,8 +343,8 @@ describe('Locks (mockClient)', () => {
});
});
- describe('getAll', () => {
- it('returns all locks in the LOCKED state', async ({ space }) => {
+ describe('get*', () => {
+ beforeEach(async ({ space }) => {
await space.locks.processPresenceMessage(
Realtime.PresenceMessage.fromValues({
action: 'update',
@@ -381,31 +381,79 @@ describe('Locks (mockClient)', () => {
},
}),
);
+ });
- const member1 = await space.members.getByConnectionId('1')!;
- const member2 = await space.members.getByConnectionId('2')!;
+ it('correctly sets up locks', ({ space }) => {
const lock1 = space.locks.get('lock1');
expect(lock1).toBeDefined();
+
const lock2 = space.locks.get('lock2');
expect(lock2).toBeDefined();
+
const lock3 = space.locks.get('lock3');
expect(lock3).toBeDefined();
+ });
- const locks = space.locks.getAll();
- expect(locks.length).toEqual(3);
- for (const lock of locks) {
- switch (lock.id) {
- case 'lock1':
- case 'lock2':
- expect(lock.member).toEqual(member1);
- break;
- case 'lock3':
- expect(lock.member).toEqual(member2);
- break;
- default:
- throw new Error(`unexpected lock id: ${lock.id}`);
+ describe('getSelf', () => {
+ it('returns all locks in the LOCKED state that belong to self', async ({ space }) => {
+ const member1 = await space.members.getByConnectionId('1')!;
+
+ const locks = await space.locks.getSelf();
+ expect(locks.length).toEqual(2);
+
+ for (const lock of locks) {
+ switch (lock.id) {
+ case 'lock1':
+ case 'lock2':
+ expect(lock.member).toEqual(member1);
+ break;
+ default:
+ throw new Error(`unexpected lock id: ${lock.id}`);
+ }
}
- }
+ });
+ });
+
+ describe('getOthers', () => {
+ it('returns all locks in the LOCKED state for every member but self', async ({ space }) => {
+ const member2 = await space.members.getByConnectionId('2')!;
+
+ const locks = await space.locks.getOthers();
+ expect(locks.length).toEqual(1);
+
+ for (const lock of locks) {
+ switch (lock.id) {
+ case 'lock3':
+ expect(lock.member).toEqual(member2);
+ break;
+ default:
+ throw new Error(`unexpected lock id: ${lock.id}`);
+ }
+ }
+ });
+ });
+
+ describe('getAll', () => {
+ it('returns all locks in the LOCKED state', async ({ space }) => {
+ const member1 = await space.members.getByConnectionId('1')!;
+ const member2 = await space.members.getByConnectionId('2')!;
+
+ const locks = await space.locks.getAll();
+ expect(locks.length).toEqual(3);
+ for (const lock of locks) {
+ switch (lock.id) {
+ case 'lock1':
+ case 'lock2':
+ expect(lock.member).toEqual(member1);
+ break;
+ case 'lock3':
+ expect(lock.member).toEqual(member2);
+ break;
+ default:
+ throw new Error(`unexpected lock id: ${lock.id}`);
+ }
+ }
+ });
});
});
});
diff --git a/src/Locks.ts b/src/Locks.ts
index feb20e3f..0b08b2d7 100644
--- a/src/Locks.ts
+++ b/src/Locks.ts
@@ -58,7 +58,9 @@ export default class Locks extends EventEmitter {
}
}
- getAll(): Lock[] {
+ // This will be async in the future, when pending requests are no longer processed
+ // in the library.
+ async getAll(): Promise {
const allLocks: Lock[] = [];
for (const locks of this.locks.values()) {
@@ -72,6 +74,23 @@ export default class Locks extends EventEmitter {
return allLocks;
}
+ async getSelf(): Promise {
+ const self = await this.space.members.getSelf();
+
+ if (!self) return [];
+
+ return this.getLocksForConnectionId(self.connectionId).filter((lock) => lock.status === 'locked');
+ }
+
+ async getOthers(): Promise {
+ const self = await this.space.members.getSelf();
+ const allLocks = await this.getAll();
+
+ if (!self) return allLocks;
+
+ return allLocks.filter((lock) => lock.member.connectionId !== self.connectionId);
+ }
+
async acquire(id: string, opts?: LockOptions): Promise {
const self = await this.space.members.getSelf();
if (!self) {
From ec80ff6d17a22d76123fdcd710aa1918533e80be Mon Sep 17 00:00:00 2001
From: Lewis Marshall
Date: Mon, 14 Aug 2023 16:20:25 +0100
Subject: [PATCH 042/191] docs: Add Component Locking section to README
Signed-off-by: Lewis Marshall
---
README.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/README.md b/README.md
index b4f66116..eea4ae84 100644
--- a/README.md
+++ b/README.md
@@ -314,3 +314,103 @@ const myCursor = await space.cursors.getSelf();
// Get a snapshot of everyone else's cursors
const othersCursors = await space.cursors.getOthers();
```
+
+### Component Locking
+
+Use the Component Locking API to lock stateful components whilst being edited by members to reduce the chances of conflicting changes being made.
+
+Locks are identified using a unique string, and the Spaces SDK maintains that at most one member holds a lock with a given string at any given time.
+
+The Component Locking API supports four operations: Query, Acquire, Release, and Subscribe.
+
+### Query
+
+`space.locks.get` is used to query whether a lock identifier is currently locked and by whom. It returns a `Lock` type which has the following fields:
+
+```ts
+type Lock = {
+ id: string;
+ status: LockStatus;
+ member: SpaceMember;
+ timestamp: number;
+ attributes?: LockAttributes;
+ reason?: Types.ErrorInfo;
+};
+```
+
+For example:
+
+```ts
+// check if the id is locked
+const isLocked = space.locks.get(id) !== undefined;
+
+// check which member has the lock
+const { member } = space.locks.get(id);
+
+// check the lock attributes assigned by the member holding the lock
+const { attributes } = space.locks.get(id);
+const value = attributes.get(key);
+```
+
+`space.locks.getAll` returns all lock identifiers which are currently locked as an array of `Lock`:
+
+```ts
+const allLocks = space.locks.getAll();
+
+for (const lock of allLocks) {
+ // ...
+}
+```
+
+### Acquire
+
+`space.locks.acquire` sends a request to acquire a lock using presence.
+
+It returns a Promise which resolves once the presence request has been sent.
+
+```ts
+const req = await space.locks.acquire(id);
+
+// or with some attributes
+const attributes = new Map();
+attributes.set('key', 'value');
+const req = await space.locks.acquire(id, { attributes });
+```
+
+It throws an error if a lock request already exists for the given identifier with a status of `pending` or `locked`.
+
+### Release
+
+`space.locks.release` releases a previously requested lock by removing it from presence.
+
+It returns a Promise which resolves once the presence request has been sent.
+
+```ts
+await space.locks.release(id);
+```
+
+### Subscribe
+
+`space.locks.subscribe` subscribes to changes in lock status across all members.
+
+The callback is called with a value of type `Lock`.
+
+```ts
+space.locks.subscribe('update', (lock) => {
+ // lock.member is the requesting member
+ // lock.request is the request made by the member
+});
+
+// or with destructuring:
+space.locks.subscribe('update', ({ member, request }) => {
+ // request.status is the status of the request, one of PENDING, LOCKED, or UNLOCKED
+ // request.reason is an ErrorInfo if the status is UNLOCKED
+});
+```
+
+Such changes occur when:
+
+- a `pending` request transitions to `locked` because the requesting member now holds the lock
+- a `pending` request transitions to `unlocked` because the requesting member does not hold the lock since another member already does
+- a `locked` request transitions to `unlocked` because the lock was either released or invalidated by a concurrent request which took precedence
+- an `unlocked` request transitions to `locked` because the requesting member reacquired a lock
From a94389dfddbc50935bed76c9f557c8b5721f0a05 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Mon, 4 Sep 2023 10:24:15 +0100
Subject: [PATCH 043/191] Add class definitions for locks
---
docs/class-definitions.md | 166 ++++++++++++++++++++++++++++++++++++--
1 file changed, 160 insertions(+), 6 deletions(-)
diff --git a/docs/class-definitions.md b/docs/class-definitions.md
index 993ab045..86fc5c13 100644
--- a/docs/class-definitions.md
+++ b/docs/class-definitions.md
@@ -168,13 +168,15 @@ Handles members within a space.
Listen to member events for the space. See [EventEmitter](/docs/usage.md#event-emitters) for overloaded usage.
- ```ts
- space.members.subscribe((member: SpaceMember) => {});
- ```
+The argument supplied to the callback is the [SpaceMember](#spacemember) object representing the member that triggered the event.
+
+Example:
- The argument supplied to the callback is the [SpaceMember](#spacemember) object representing the member that triggered the event.
+```ts
+space.members.subscribe((member: SpaceMember) => {});
+```
- Available events:
+Available events:
- ##### **enter**
@@ -449,7 +451,7 @@ Set the position of a cursor. If a member has not yet entered the space, this me
A event payload returned contains an object with 2 properties. `position` is an object with 2 required properties, `x` and `y`. These represent the position of the cursor on a 2D plane. A second optional property, `data` can also be passed. This is an object of any shape and is meant for data associated with the cursor movement (like drag or hover calculation results):
```ts
-type set = (update: { position: CursorPosition, data?: CursorData })
+type set = (update: { position: CursorPosition, data?: CursorData }) => void;
```
Example usage:
@@ -544,3 +546,155 @@ Represent data that can be associated with a cursor update.
```ts
type CursorData = Record;
```
+
+## Component Locking
+
+Provides a mechanism to "lock" a component, reducing the chances of conflict in an application whilst being edited by multiple members. Inherits from [EventEmitter](/docs/usage.md#event-emitters).
+
+### Methods
+
+#### acquire()
+
+Send a request to acquire a lock. Returns a Promise which resolves once the request has been sent. A resolved Promise holds a `pending` [Lock](#lock). An error will be thrown if a lock request with a status of `pending` or `locked` already exists, returning a rejected promise.
+
+When a lock acquisition by a member is confirmed with the `locked` status, an `update` event will be emitted. Hence to handle lock acquisition, `acquire()` needs to always be used together with `subscribe()`.
+
+```ts
+type acquire = (lockId: string) => Promise;
+```
+
+Example:
+
+```ts
+const id = "/slide/1/element/3";
+const lockRequest = await space.locks.acquire(id);
+```
+
+#### release()
+
+Releases a previously requested lock.
+
+```ts
+type release = (lockId: string) => Promise;
+```
+
+Example:
+
+```ts
+const id = "/slide/1/element/3";
+await space.locks.release(id);
+```
+
+#### subscribe()
+
+Listen to lock events. See [EventEmitter](/docs/usage.md#event-emitters) for overloaded usage.
+
+Available events:
+
+- ##### **update**
+
+ Listen to changes to locks.
+
+ ```ts
+ space.locks.subscribe('update', (lock: Lock) => {})
+ ```
+
+ The argument supplied to the callback is a [Lock](#lock), representing the lock request and it's status.
+
+#### unsubscribe()
+
+Remove all event listeners, all event listeners for an event, or specific listeners. See [EventEmitter](/docs/usage.md#event-emitters) for detailed usage.
+
+```ts
+space.locks.unsubscribe('update');
+```
+
+#### get()
+
+Get a lock by its id.
+
+```ts
+type get = (lockId: string) => Lock | undefined
+```
+
+Example:
+
+```ts
+const id = "/slide/1/element/3";
+const lock = space.locks.get(id);
+```
+
+#### getSelf()
+
+Get all locks belonging to self that have the `locked` status.
+
+```ts
+type getSelf = () => Promise
+```
+
+Example:
+
+```ts
+const locks = await space.locks.getSelf();
+```
+
+#### getOthers()
+
+Get all locks belonging to all members except self that have the `locked` status.
+
+```ts
+type getOthers = () => Promise
+```
+
+Example:
+
+```ts
+const locks = await space.locks.getOthers();
+```
+
+#### getAll()
+
+Get all locks that have the `locked` status.
+
+```ts
+type getAll = () => Promise
+```
+
+Example:
+
+```ts
+const locks = await space.locks.getAll();
+```
+
+### Related types
+
+#### Lock
+
+Represents a Lock.
+
+```ts
+type Lock = {
+ id: string;
+ status: LockStatus;
+ member: SpaceMember;
+ timestamp: number;
+ attributes?: LockAttributes;
+ reason?: Types.ErrorInfo;
+};
+```
+
+#### LockStatus
+
+Represents a status of a lock.
+
+```ts
+type LockStatus = 'pending' | 'locked' | 'unlocked';
+```
+
+#### LockAttributes
+
+Additional attributes that can be set when acquiring a lock.
+
+```ts
+type LockAttributes = Map;
+```
\ No newline at end of file
From 053af865eedcfba4a984e46276dcd706ef5d722c Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Mon, 4 Sep 2023 13:16:58 +0100
Subject: [PATCH 044/191] Change name of test watch task
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 5ba0a557..d823d3cf 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"format": "prettier --write --ignore-path .gitignore src demo",
"format:check": "prettier --check --ignore-path .gitignore src demo",
"test": "vitest run",
- "watch": "vitest watch",
+ "test:watch": "vitest watch",
"coverage": "vitest run --coverage",
"build": "npm run build:mjs && npm run build:cjs && npm run build:iife",
"build:mjs": "npx tsc --project tsconfig.mjs.json && cp res/package.mjs.json dist/mjs/package.json",
From 7f9acf9a8d07c39369d2f83db190481801f2d89d Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Mon, 4 Sep 2023 12:22:05 +0100
Subject: [PATCH 045/191] Emit update event as a catch all for all members
namespace events
This change bring "update" inline with how we describe "update" in the docs - as an events that catches all events, instead of just profile updates.
To accomodate only profile update changes, an updateProfile event was added.
This is based on feedback, where users found that "update" did was inconsistent with other APIs.
---
src/Members.test.ts | 181 ++++++++++++++++++++++++++++++++++++++++++++
src/Members.ts | 5 ++
src/Space.test.ts | 136 +++++++--------------------------
3 files changed, 213 insertions(+), 109 deletions(-)
create mode 100644 src/Members.test.ts
diff --git a/src/Members.test.ts b/src/Members.test.ts
new file mode 100644
index 00000000..08203d23
--- /dev/null
+++ b/src/Members.test.ts
@@ -0,0 +1,181 @@
+import { it, describe, expect, vi, beforeEach, afterEach } from 'vitest';
+import { Types, Realtime } from 'ably/promises';
+
+import Space from './Space.js';
+
+import { createPresenceEvent, createSpaceMember, createProfileUpdate } from './utilities/test/fakes.js';
+
+interface SpaceTestContext {
+ client: Types.RealtimePromise;
+ space: Space;
+ presence: Types.RealtimePresencePromise;
+ presenceMap: Map;
+}
+
+vi.mock('ably/promises');
+vi.mock('nanoid');
+
+describe('Members', () => {
+ beforeEach((context) => {
+ const client = new Realtime({});
+ const space = new Space('test', client);
+ const presence = space.channel.presence;
+ const presenceMap = new Map();
+
+ vi.spyOn(presence, 'get').mockImplementation(async () => {
+ return Array.from(presenceMap.values());
+ });
+
+ context.client = client;
+ context.space = space;
+ context.presence = presence;
+ context.presenceMap = presenceMap;
+ });
+
+ describe('subscribe', () => {
+ it('calls enter and update on enter presence events', async ({ space, presenceMap }) => {
+ const updateSpy = vi.fn();
+ const enterSpy = vi.fn();
+ space.members.subscribe('update', updateSpy);
+ space.members.subscribe('enter', enterSpy);
+
+ await createPresenceEvent(space, presenceMap, 'enter');
+
+ const member1 = createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } });
+ expect(updateSpy).toHaveBeenNthCalledWith(1, member1);
+ expect(enterSpy).toHaveBeenNthCalledWith(1, member1);
+
+ await createPresenceEvent(space, presenceMap, 'enter', {
+ clientId: '2',
+ connectionId: '2',
+ data: createProfileUpdate({ current: { name: 'Betty' } }),
+ });
+
+ const member2 = createSpaceMember({
+ clientId: '2',
+ connectionId: '2',
+ lastEvent: { name: 'enter', timestamp: 1 },
+ profileData: { name: 'Betty' },
+ });
+
+ expect(updateSpy).toHaveBeenNthCalledWith(2, member2);
+ expect(enterSpy).toHaveBeenNthCalledWith(2, member2);
+ });
+
+ it('calls updateProfile and update on update presence events', async ({ space, presenceMap }) => {
+ const updateSpy = vi.fn();
+ const updateProfileSpy = vi.fn();
+ space.members.subscribe('update', updateSpy);
+ space.members.subscribe('updateProfile', updateProfileSpy);
+
+ await createPresenceEvent(space, presenceMap, 'enter');
+ expect(updateSpy).toHaveBeenNthCalledWith(1, createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } }));
+
+ await createPresenceEvent(space, presenceMap, 'update', {
+ data: createProfileUpdate({ current: { name: 'Betty' } }),
+ });
+
+ const memberUpdate = createSpaceMember({ profileData: { name: 'Betty' } });
+ expect(updateSpy).toHaveBeenNthCalledWith(2, memberUpdate);
+ expect(updateProfileSpy).toHaveBeenNthCalledWith(1, memberUpdate);
+ });
+
+ it('updates the connected status of clients who have left', async ({ space, presenceMap }) => {
+ const updateSpy = vi.fn();
+ const leaveSpy = vi.fn();
+ space.members.subscribe('update', updateSpy);
+ space.members.subscribe('leave', leaveSpy);
+
+ await createPresenceEvent(space, presenceMap, 'enter');
+ expect(updateSpy).toHaveBeenNthCalledWith(1, createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } }));
+
+ await createPresenceEvent(space, presenceMap, 'leave');
+ const memberUpdate = createSpaceMember({ isConnected: false, lastEvent: { name: 'leave', timestamp: 1 } });
+ expect(updateSpy).toHaveBeenNthCalledWith(2, memberUpdate);
+ expect(leaveSpy).toHaveBeenNthCalledWith(1, memberUpdate);
+ });
+
+ describe('leavers', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('removes a member who has left after the offlineTimeout', async ({ space, presenceMap }) => {
+ const leaveSpy = vi.fn();
+ const removeSpy = vi.fn();
+ space.members.subscribe('leave', leaveSpy);
+ space.members.subscribe('remove', removeSpy);
+
+ await createPresenceEvent(space, presenceMap, 'enter');
+ await createPresenceEvent(space, presenceMap, 'leave');
+
+ const memberUpdate = createSpaceMember({ isConnected: false, lastEvent: { name: 'leave', timestamp: 1 } });
+ expect(leaveSpy).toHaveBeenNthCalledWith(1, memberUpdate);
+
+ await vi.advanceTimersByTimeAsync(130_000);
+
+ expect(removeSpy).toHaveBeenNthCalledWith(1, memberUpdate);
+ });
+
+ it('does not remove a member that has rejoined', async ({ space, presenceMap }) => {
+ const callbackSpy = vi.fn();
+ space.members.subscribe('update', callbackSpy);
+
+ await createPresenceEvent(space, presenceMap, 'enter');
+ expect(callbackSpy).toHaveBeenNthCalledWith(
+ 1,
+ createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } }),
+ );
+ await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2', connectionId: '2' });
+ expect(callbackSpy).toHaveBeenNthCalledWith(
+ 2,
+ createSpaceMember({ clientId: '2', connectionId: '2', lastEvent: { name: 'enter', timestamp: 1 } }),
+ );
+
+ await createPresenceEvent(space, presenceMap, 'leave');
+ expect(callbackSpy).toHaveBeenNthCalledWith(
+ 3,
+ createSpaceMember({ lastEvent: { name: 'leave', timestamp: 1 }, isConnected: false }),
+ );
+
+ await vi.advanceTimersByTimeAsync(60_000);
+ await createPresenceEvent(space, presenceMap, 'enter');
+
+ expect(callbackSpy).toHaveBeenNthCalledWith(
+ 4,
+ createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } }),
+ );
+
+ await vi.advanceTimersByTimeAsync(130_000); // 2:10 passed, default timeout is 2 min
+ expect(callbackSpy).toHaveBeenCalledTimes(4);
+ });
+
+ it('unsubscribes when unsubscribe is called', async ({ space, presenceMap }) => {
+ const spy = vi.fn();
+ space.members.subscribe('update', spy);
+ await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
+ space.members.unsubscribe('update', spy);
+ await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
+
+ expect(spy).toHaveBeenCalledOnce();
+ });
+
+ it('unsubscribes when unsubscribe is called with no arguments', async ({
+ space,
+ presenceMap,
+ }) => {
+ const spy = vi.fn();
+ space.members.subscribe('update', spy);
+ await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
+ space.members.unsubscribe();
+ await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
+
+ expect(spy).toHaveBeenCalledOnce();
+ });
+ });
+ });
+});
diff --git a/src/Members.ts b/src/Members.ts
index d0d56796..2edae1f3 100644
--- a/src/Members.ts
+++ b/src/Members.ts
@@ -14,6 +14,7 @@ type MemberEventsMap = {
leave: SpaceMember;
enter: SpaceMember;
update: SpaceMember;
+ updateProfile: SpaceMember;
remove: SpaceMember;
};
@@ -34,18 +35,21 @@ class Members extends EventEmitter {
if (action === 'leave') {
this.leavers.addLeaver(member, () => this.onMemberOffline(member));
this.emit('leave', member);
+ this.emit('update', member);
} else if (isLeaver) {
this.leavers.removeLeaver(connectionId);
}
if (action === 'enter') {
this.emit('enter', member);
+ this.emit('update', member);
}
// Emit profileData updates only if they are different then the last held update.
// A locationUpdate is handled in Locations.
if (message.data.profileUpdate.id && this.lastMemberUpdate[connectionId] !== message.data.profileUpdate.id) {
this.lastMemberUpdate[message.connectionId] = message.data.profileUpdate.id;
+ this.emit('updateProfile', member);
this.emit('update', member);
}
}
@@ -122,6 +126,7 @@ class Members extends EventEmitter {
this.leavers.removeLeaver(member.connectionId);
this.emit('remove', member);
+ this.emit('update', member);
if (member.location) {
this.space.locations.emit('update', {
diff --git a/src/Space.test.ts b/src/Space.test.ts
index 67db9d9a..b2810cee 100644
--- a/src/Space.test.ts
+++ b/src/Space.test.ts
@@ -1,4 +1,4 @@
-import { it, describe, expect, vi, beforeEach, expectTypeOf, afterEach } from 'vitest';
+import { it, describe, expect, vi, beforeEach, expectTypeOf } from 'vitest';
import { Realtime, Types } from 'ably/promises';
import Space from './Space.js';
@@ -10,6 +10,7 @@ import {
createPresenceMessage,
createSpaceMember,
createProfileUpdate,
+ createLocationUpdate,
} from './utilities/test/fakes.js';
interface SpaceTestContext {
@@ -192,7 +193,7 @@ describe('Space', () => {
expect(spy).toHaveBeenCalledTimes(1);
});
- it('adds new members', async ({ space, presenceMap }) => {
+ it('is called when members enter', async ({ space, presenceMap }) => {
const callbackSpy = vi.fn();
space.subscribe('update', callbackSpy);
await createPresenceEvent(space, presenceMap, 'enter');
@@ -208,49 +209,53 @@ describe('Space', () => {
data: createProfileUpdate({ current: { name: 'Betty' } }),
});
+ const member2 = createSpaceMember({
+ clientId: '2',
+ connectionId: '2',
+ lastEvent: { name: 'enter', timestamp: 1 },
+ profileData: { name: 'Betty' },
+ });
+
expect(callbackSpy).toHaveBeenNthCalledWith(2, {
- members: [
- member1,
- createSpaceMember({
- clientId: '2',
- connectionId: '2',
- lastEvent: { name: 'enter', timestamp: 1 },
- profileData: { name: 'Betty' },
- }),
- ],
+ members: [member1, member2],
});
});
- it('updates the data of members', async ({ space, presenceMap }) => {
+ it('is called when members leave', async ({ space, presenceMap }) => {
const callbackSpy = vi.fn();
space.subscribe('update', callbackSpy);
await createPresenceEvent(space, presenceMap, 'enter');
- expect(callbackSpy).toHaveBeenNthCalledWith(1, {
- members: [createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } })],
- });
+ let member = createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } });
- await createPresenceEvent(space, presenceMap, 'update', {
- data: createProfileUpdate({ current: { name: 'Betty' } }),
+ expect(callbackSpy).toHaveBeenNthCalledWith(1, {
+ members: [member],
});
+ await createPresenceEvent(space, presenceMap, 'leave');
+ member = createSpaceMember({ isConnected: false, lastEvent: { name: 'leave', timestamp: 1 } });
expect(callbackSpy).toHaveBeenNthCalledWith(2, {
- members: [createSpaceMember({ profileData: { name: 'Betty' } })],
+ members: [member],
});
});
- it('updates the connected status of clients who have left', async ({ space, presenceMap }) => {
+ it('is called when members location changes', async ({ space, presenceMap }) => {
const callbackSpy = vi.fn();
space.subscribe('update', callbackSpy);
await createPresenceEvent(space, presenceMap, 'enter');
+ let member = createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } });
expect(callbackSpy).toHaveBeenNthCalledWith(1, {
- members: [createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } })],
+ members: [member],
});
- await createPresenceEvent(space, presenceMap, 'leave');
+ await createPresenceEvent(space, presenceMap, 'update', {
+ data: createLocationUpdate({ current: 'newLocation' }),
+ });
+
+ member = createSpaceMember({ lastEvent: { name: 'update', timestamp: 1 }, location: 'newLocation' });
expect(callbackSpy).toHaveBeenNthCalledWith(2, {
- members: [createSpaceMember({ isConnected: false, lastEvent: { name: 'leave', timestamp: 1 } })],
+ members: [member],
});
});
@@ -263,93 +268,6 @@ describe('Space', () => {
members: [createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } })],
});
});
-
- describe('leavers', () => {
- beforeEach(() => {
- vi.useFakeTimers();
- });
-
- afterEach(() => {
- vi.useRealTimers();
- });
-
- it('removes a member who has left after the offlineTimeout', async ({ space, presenceMap }) => {
- const callbackSpy = vi.fn();
- space.subscribe('update', callbackSpy);
-
- await createPresenceEvent(space, presenceMap, 'enter');
- expect(callbackSpy).toHaveBeenNthCalledWith(1, {
- members: [createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } })],
- });
-
- await createPresenceEvent(space, presenceMap, 'leave');
- expect(callbackSpy).toHaveBeenNthCalledWith(2, {
- members: [createSpaceMember({ isConnected: false, lastEvent: { name: 'leave', timestamp: 1 } })],
- });
-
- await vi.advanceTimersByTimeAsync(130_000);
-
- expect(callbackSpy).toHaveBeenNthCalledWith(3, { members: [] });
- expect(callbackSpy).toHaveBeenCalledTimes(3);
- });
-
- it('does not remove a member that has rejoined', async ({ space, presenceMap }) => {
- const callbackSpy = vi.fn();
- space.subscribe('update', callbackSpy);
-
- await createPresenceEvent(space, presenceMap, 'enter');
- await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2', connectionId: '2' });
- expect(callbackSpy).toHaveBeenNthCalledWith(2, {
- members: [
- createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } }),
- createSpaceMember({ clientId: '2', connectionId: '2', lastEvent: { name: 'enter', timestamp: 1 } }),
- ],
- });
-
- await createPresenceEvent(space, presenceMap, 'leave');
- expect(callbackSpy).toHaveBeenNthCalledWith(3, {
- members: [
- createSpaceMember({ clientId: '2', connectionId: '2', lastEvent: { name: 'enter', timestamp: 1 } }),
- createSpaceMember({ lastEvent: { name: 'leave', timestamp: 1 }, isConnected: false }),
- ],
- });
-
- await vi.advanceTimersByTimeAsync(60_000);
- await createPresenceEvent(space, presenceMap, 'enter');
- expect(callbackSpy).toHaveBeenNthCalledWith(4, {
- members: [
- createSpaceMember({ clientId: '2', connectionId: '2', lastEvent: { name: 'enter', timestamp: 1 } }),
- createSpaceMember({ lastEvent: { name: 'enter', timestamp: 1 } }),
- ],
- });
-
- await vi.advanceTimersByTimeAsync(130_000); // 2:10 passed, default timeout is 2 min
- expect(callbackSpy).toHaveBeenCalledTimes(4);
- });
-
- it('unsubscribes when unsubscribe is called', async ({ space, presenceMap }) => {
- const spy = vi.fn();
- space.subscribe('update', spy);
- await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
- space.unsubscribe('update', spy);
- await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
-
- expect(spy).toHaveBeenCalledOnce();
- });
-
- it('unsubscribes when unsubscribe is called with no arguments', async ({
- space,
- presenceMap,
- }) => {
- const spy = vi.fn();
- space.subscribe('update', spy);
- await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
- space.unsubscribe();
- await createPresenceEvent(space, presenceMap, 'enter', { clientId: '2' });
-
- expect(spy).toHaveBeenCalledOnce();
- });
- });
});
describe('locations', () => {
From a16eef3cbdec58549aedaaf360c0687830928b53 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Mon, 4 Sep 2023 12:42:04 +0100
Subject: [PATCH 046/191] Update docs
---
README.md | 9 +++++++--
docs/class-definitions.md | 14 ++++++++++++--
docs/usage.md | 16 +++++++++++++---
3 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index eea4ae84..39d2e82b 100644
--- a/README.md
+++ b/README.md
@@ -159,10 +159,15 @@ space.members.subscribe('remove', (memberRemoved) => {
console.log(memberRemoved);
});
-// Subscribe to member profile update events only
-space.members.subscribe('update', (memberProfileUpdated) => {
+// Subscribe to profile updates on members only
+space.members.subscribe('updateProfile', (memberProfileUpdated) => {
console.log(memberProfileUpdated);
});
+
+// Subscribe to all updates to members
+space.members.subscribe('update', (memberUpdate) => {
+ console.log(memberUpdate);
+});
```
The following is an example event payload received by subscribers when member updates occur in a space:
diff --git a/docs/class-definitions.md b/docs/class-definitions.md
index 86fc5c13..2c8d5543 100644
--- a/docs/class-definitions.md
+++ b/docs/class-definitions.md
@@ -207,15 +207,25 @@ Available events:
The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member removed from the space.
-- ##### **update**
+- ##### **updateProfile**
Listen to profile update events of members.
```ts
- space.members.subscribe('update', (member: SpaceMember) => {})
+ space.members.subscribe('updateProfile', (member: SpaceMember) => {})
```
The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member entering the space.
+- ##### **update**
+
+ Listen to `enter`, `leave`, `updateProfile` and `remove` events.
+
+ ```ts
+ space.members.subscribe('update', (member: SpaceMember) => {})
+ ```
+
+ The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member affected by the change.
+
#### unsubscribe()
diff --git a/docs/usage.md b/docs/usage.md
index 82b69a4d..110a6688 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -165,7 +165,9 @@ See [SpaceMember](/docs/class-definitions.md#spacemember) for details on propert
### Member events
-Subscribe to either all the member events or specifically to `enter`, `leave`, `remove` or `update` events related to members in a space.
+Subscribe to either all the member events or specifically to `enter`, `leave`, `remove` or `updateProfile` events related to members in a space.
+
+To listen to all events pass either no event name or `update`:
```ts
space.members.subscribe((memberUpdate) => {
@@ -173,6 +175,12 @@ space.members.subscribe((memberUpdate) => {
});
```
+```ts
+space.members.subscribe('update', (memberUpdate) => {
+ console.log(memberUpdate);
+});
+```
+
#### enter
Emitted when a member enters a space. Called with the member entering the space.
@@ -203,16 +211,18 @@ space.members.subscribe('remove', (memberRemoved) => {
});
```
-#### update
+#### updateProfile
Emitted when a member updates their `profileData` via `space.updateProfileData()`:
```ts
-space.members.subscribe('update', (memberProfileUpdated) => {
+space.members.subscribe('updateProfile', (memberProfileUpdated) => {
console.log(memberProfileUpdated);
});
```
+
+
To stop listening to member events, users can call the `space.members.unsubscribe()` method. See [Event emitters](#event-emitters) for options and usage.
From d44b0b463ee5b9866a42a160f92da8ddd9001e53 Mon Sep 17 00:00:00 2001
From: Dominik Piatek
Date: Mon, 4 Sep 2023 12:48:07 +0100
Subject: [PATCH 047/191] Update repository references
---
.eslintrc.js | 2 +-
README.md | 6 +++---
demo/src/utils/types.d.ts | 2 +-
docs/class-definitions.md | 2 +-
docs/usage.md | 6 +++---
examples/avatar-stack.ts | 2 +-
examples/live-cursors.ts | 2 +-
examples/locking.ts | 2 +-
package-lock.json | 4 ++--
package.json | 6 +++---
10 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 7868213f..a540ebff 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -26,7 +26,7 @@ module.exports = {
'no-undef': 'off',
'no-dupe-class-members': 'off',
// see:
- // https://github.com/ably-labs/spaces/issues/76
+ // https://github.com/ably/spaces/issues/76
// https://github.com/microsoft/TypeScript/issues/16577#issuecomment-703190339
'import/extensions': [
'error',
diff --git a/README.md b/README.md
index 39d2e82b..24c6524c 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
-
+
@@ -65,13 +65,13 @@ To start using this SDK, you will need the following:
Install the Ably JavaScript SDK and the Spaces SDK:
```sh
-npm install ably @ably-labs/spaces
+npm install ably @ably/spaces
```
To instantiate the Spaces SDK, create an [Ably client](https://ably.com/docs/getting-started/setup) and pass it into the Spaces constructor:
```ts
-import Spaces from '@ably-labs/spaces';
+import Spaces from '@ably/spaces';
import { Realtime } from 'ably';
const client = new Realtime.Promise({ key: "", clientId: "" });
diff --git a/demo/src/utils/types.d.ts b/demo/src/utils/types.d.ts
index f9b9d106..2ec5aee3 100644
--- a/demo/src/utils/types.d.ts
+++ b/demo/src/utils/types.d.ts
@@ -1,4 +1,4 @@
-import { type SpaceMember } from '@ably-labs/spaces';
+import { type SpaceMember } from '@ably/spaces';
interface ProfileData {
name: string;
diff --git a/docs/class-definitions.md b/docs/class-definitions.md
index 2c8d5543..dbc12d0c 100644
--- a/docs/class-definitions.md
+++ b/docs/class-definitions.md
@@ -6,7 +6,7 @@ Create a new instance of the Space SDK by passing an instance of the realtime, p
```ts
import { Realtime } from 'ably/promise';
-import Spaces from '@ably-labs/spaces';
+import Spaces from '@ably/spaces';
const client = new Realtime.Promise({ key: "", clientId: "" });
const spaces = new Spaces(client);
diff --git a/docs/usage.md b/docs/usage.md
index 110a6688..fd2c64b5 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -25,7 +25,7 @@ Spaces is built on top of the [Ably JavaScript SDK](https://github.com/ably/ably
You'll need to install both the ably client and the spaces client:
```sh
-npm install ably @ably-labs/spaces
+npm install ably @ably/spaces
```
### CDN
@@ -34,7 +34,7 @@ You can also use Spaces with a CDN like [unpkg](https://www.unpkg.com/):
```html
-
+
```
## Authentication and instantiation
@@ -45,7 +45,7 @@ The Ably client instantiation accepts client options. You will need at minimum a
```ts
import { Realtime } from 'ably/promise';
-import Spaces from '@ably-labs/spaces';
+import Spaces from '@ably/spaces';
const client = new Realtime.Promise({ key: "", clientId: "