Compare commits

..

13 Commits

Author SHA1 Message Date
Anselm
65a7a66c15 Publish
- jazz-example-todo@0.0.18
 - cojson@0.1.4
 - cojson-simple-sync@0.1.4
 - cojson-storage-sqlite@0.1.1
 - jazz-browser@0.1.4
 - jazz-browser-auth-local@0.1.4
 - jazz-react@0.1.5
 - jazz-react-auth-local@0.1.5
 - jazz-storage-indexeddb@0.1.4
2023-09-02 17:59:58 +01:00
Anselm
0f999a2c2d Add persistence to cojson-simple-sync 2023-09-02 17:58:49 +01:00
Anselm
2247c97080 Implement cojson-storage-sqlite 2023-09-02 17:58:38 +01:00
Anselm
cbdc722959 Fix region for new cluster 2023-09-01 16:39:55 +01:00
Anselm
bb157b6099 Deploy todo example to new cluster 2023-09-01 16:38:59 +01:00
Anselm
e1f8ec6f11 Publish
- jazz-example-todo@0.0.17
 - cojson-simple-sync@0.1.3
2023-09-01 14:20:39 +01:00
Anselm
9854238346 Add pinging to cojson-simple-sync 2023-09-01 14:20:15 +01:00
Anselm Eickhoff
3b5ab90006 Add QR code to invite toast 2023-08-29 12:27:52 +01:00
Anselm Eickhoff
988dc37902 Publish
- cojson-simple-sync@0.1.2
 - cojson@0.1.3
 - jazz-browser-auth-local@0.1.3
 - jazz-browser@0.1.3
 - jazz-react-auth-local@0.1.4
 - jazz-react@0.1.4
 - jazz-storage-indexeddb@0.1.3
2023-08-27 17:30:01 +01:00
Anselm Eickhoff
4ef4b87d95 Fix WS reconnect 2023-08-27 17:29:36 +01:00
Anselm Eickhoff
27f811b9e9 Permissioned -> secure 2023-08-20 16:19:49 +01:00
Anselm
52be603996 Distinguish between not yet implemented and documented 2023-08-20 00:28:20 +01:00
Anselm
d1123866c2 Initial docs 2023-08-20 00:27:02 +01:00
25 changed files with 3986 additions and 135 deletions

View File

@@ -71,11 +71,7 @@ jobs:
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
export DOCKER_TAG=${{ env.DOCKER_TAG }};
for region in ${{ vars.DEPLOY_REGIONS }}
do
export REGION=$region;
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN} ${REGION}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR='${{ secrets.NOMAD_ADDR }}' nomad job run job-instance.nomad;
done
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR='http://control1-london:4646' nomad job run job-instance.nomad;
working-directory: ./examples/todo

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

248
README.md
View File

@@ -2,7 +2,7 @@
Homepage: [jazz.tools](https://jazz.tools) &mdash; [Discord](https://discord.gg/utDMjHYg42)
Jazz is an open-source toolkit for *permissioned telepathic data.*
Jazz is an open-source toolkit for *secure telepathic data.*
- Ship faster & simplify your frontend and backend
- Get cross-device sync, real-time collaboration & offline support for free
@@ -11,14 +11,14 @@ Jazz is an open-source toolkit for *permissioned telepathic data.*
## What is Permissioned Telepathic Data?
## What is Secure Telepathic Data?
**Telepathic** means:
- **Read and write data as if it was local,** from anywhere in your app.
- **Always have that data synced, instantly.** Across devices of the same user &mdash; or to other users (coming soon: to your backend, workers, etc.)
**Permissioned** means:
**Secure** means:
- **Fine-grained, role-based permissions are *baked into* your data.**
- **Permissions are enforced everywhere, locally.** (using cryptography instead of through an API)
@@ -57,7 +57,7 @@ If you want to build something with Jazz, [join the Jazz Discord](https://discor
**`cojson`**
A library implementing abstractions and protocols for "Collaborative JSON". This will soon be standardized and forms the basis of permissioned telepathic data.
A library implementing abstractions and protocols for "Collaborative JSON". This will soon be standardized and forms the basis of secure telepathic data.
**`jazz-react`**
@@ -83,32 +83,256 @@ Provides local, offline-capable persistence. Included and enabled in `jazz-react
## `CoJSON`
CoJSON is the core implementation of permissioned telepathic data. It provides abstractions for Collaborative JSON values ("`CoValues`"), groups for permission management and a protocol for syncing between nodes. Our goal is to standardise CoJSON soon and port it to other languages and platforms.
CoJSON is the core implementation of secure telepathic data. It provides abstractions for Collaborative JSON values ("`CoValues`"), groups for permission management and a protocol for syncing between nodes. Our goal is to standardise CoJSON soon and port it to other languages and platforms.
---
### `LocalNode`
A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
A `LocalNode` can have peers that it syncs to, for example some form of local persistence, or a sync server, such as `sync.jazz.tools` (Jazz Global Mesh).
You typically get hold of a `LocalNode` using `jazz-react`'s `useJazz()`:
```typescript
const { localNode } = useJazz();
```
#### `LocalNode.load(id)`
```typescript
load<T extends ContentType>(id: CoID<T>): Promise<T>
```
Loads a CoValue's content, syncing from peers as necessary and resolving the returned promise once a first version has been loaded. See `ContentType.subscribe()` and `useTelepathicData` for listening to subsequent updates to the CoValue.
#### `LocalNode.loadProfile(id)`
```typescript
loadProfile(accountID: AccountID): Promise<Profile>
```
Loads a profile associated with an account. `Profile` is at least a `CoMap<{string: name}>`, but might contain other, app-specific properties.
#### `LocalNode.acceptInvite(valueOrGroup, inviteSecret)`
```typescript
acceptInvite<T extends ContentType>(
valueOrGroup: CoID<T>,
inviteSecret: InviteSecret
): Promise<void>
```
Accepts an invite for a group, or infers the group if given the `CoID` of a value owned by that group. Resolves upon successful joining of that group, at which point you should be able to `LocalNode.load` the value.
Invites can be created with `Group.createInvite(role)`.
#### `LocalNode.createGroup()`
```typescript
createGroup(): Group
```
Creates a new group (with the current account as the group's first admin).
---
### `Group`
### `CoValue` & `ContentType`s
A CoJSON group manages permissions of its members. A `Group` object exposes those capabilities and allows you to create new CoValues owned by that group.
#### `CoMap`
(Internally, a `Group` is also just a `CoMap`, mapping member accounts to roles and containing some state management for making cryptographic keys available to current members)
#### `CoList` (coming soon)
#### `Group.id`
#### `CoStram` (coming soon)
Returns the `CoID` of the `Group`.
#### `Static` (coming soon)
#### `Group.roleOf(accountID)`
```typescript
roleOf(accountID: AccountID): "reader" | "writer" | "admin" | undefined
```
Returns the current role of a given account.
#### `Group.myRole()`
```typescript
myRole(accountID: AccountID): "reader" | "writer" | "admin" | undefined
```
Returns the role of the current account in the group.
#### `Group.addMember(accountID, role)`
```typescript
addMember(
accountID: AccountIDOrAgentID,
role: "reader" | "writer" | "admin"
)
```
Directly grants a new member a role in the group. The current account must be an admin to be able to do so. Throws otherwise.
#### `Group.createInvite(role)`
```typescript
createInvite(role: "reader" | "writer" | "admin"): InviteSecret
```
Creates an invite for new members to indirectly join the group, allowing them to grant themselves the specified role with the InviteSecret (a string starting with "inviteSecret_") - use `LocalNode.acceptInvite()` for this purpose.
#### `Group.removeMember(accountID)`
```typescript
removeMember(accountID: AccountID)
```
Strips the specified member of all roles (preventing future writes) and rotates the read encryption key for that group (preventing reads of new content, including in covalues owned by this group)
#### `Group.createMap(meta?)`
```typescript
createMap<
M extends { [key: string]: JsonValue },
Meta extends JsonObject | null = null
>(meta?: Meta): CoMap<M, Meta>
```
Creates a new `CoMap` within this group, with the specified inner content type `M` and optional static metadata.
#### `Group.createList(meta?)` (coming soon)
#### `Group.createStream(meta?)` (coming soon)
#### `Group.createStatic(meta)` (coming soon)
---
### `CoValue` ContentType: `CoMap`
```typescript
class CoMap<
M extends { [key: string]: JsonValue; },
Meta extends JsonObject | null = null,
>
```
#### `CoMap.id`
```typescript
id: CoID<CoMap<M, Meta>>
```
Returns the CoMap's (precisely typed) `CoID`
#### `CoMap.keys()`
```typescript
keys(): (keyof M & string)[]
```
#### `CoMap.get(key)`
```typescript
get<K extends keyof M>(key: K): M[K] | undefined
```
Returns the current value for the given key.
#### `CoMap.getLastEditor(key)`
```typescript
getLastEditor<K extends keyof M>(key: K): AccountID | undefined
```
Returns the accountID of the last account to modify the value for the given key.
#### `CoMap.toJSON()`
```typescript
toJSON(): JsonObject
```
Returns a JSON representation of the state of the CoMap.
#### `CoMap.subscribe(listener)`
```typescript
subscribe(
listener: (coMap: CoMap<M, Meta>) => void
): () => void
```
Lets you subscribe to future updates to this CoMap (whether made locally or by other users). Takes a listener function that will be called with the current state for each update. Returns an unsubscribe function.
Used internally by `useTelepathicData()` for reactive updates on changes to a `CoMap`.
#### `CoMap.edit(editable => {...})`
```typescript
edit(changer: (editable: WriteableCoMap<M, Meta>) => void): CoMap<M, Meta>
```
Lets you apply edits to a `CoMap`, inside the changer callback, which receives a `WriteableCoMap`. A `WritableCoMap` has all the same methods as a `CoMap`, but all edits made to it with `set` or `delete` are reflected in it immediately - so it behaves mutably, whereas a `CoMap` is always immutable (you need to use `subscribe` to receive new versions of it).
```typescript
export class WriteableCoMap<
M extends { [key: string]: JsonValue; },
Meta extends JsonObject | null = null,
> extends CoMap<M, Meta>
```
#### `WritableCoMap.set(key, value)`
```typescript
set<K extends keyof M>(
key: K,
value: M[K],
privacy: "private" | "trusting" = "private"
): void
```
Sets a new value for the given key.
If `privacy` is `"private"` **(default)**, both `key` and `value` are encrypted in the transaction, only readable by other members of the group this `CoMap` belongs to. Not even sync servers can see the content in plaintext.
If `privacy` is `"trusting"`, both `key` and `value` are stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
#### `WritableCoMap.delete(key)`
```typescript
delete<K extends keyof M>(
key: K,
privacy: "private" | "trusting" = "private"
): void
```
Deletes the value for the given key (setting it to undefined).
If `privacy` is `"private"` **(default)**, `key` is encrypted in the transaction, only readable by other members of the group this `CoMap` belongs to. Not even sync servers can see the content in plaintext.
If `privacy` is `"trusting"`, `key` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
---
### `CoValue` ContentType: `CoList` (not yet implemented)
---
### `CoValue` ContentType: `CoStram` (not yet implemented)
---
### `CoValue` ContentType: `Static` (not yet implemented)
---
## `jazz-react`
---
### `<WithJazz>`
Not yet documented, see [`examples/todo`](./examples/todo/) for now
---
### `useJazz()`
Not yet documented, see [`examples/todo`](./examples/todo/) for now
---
### `useTelepathicData(coID)`
### `useProfile(accountID)`
Not yet documented, see [`examples/todo`](./examples/todo/) for now
---
### `useProfile(accountID)`
Not yet documented, see [`examples/todo`](./examples/todo/) for now

View File

@@ -1,9 +1,9 @@
job "example-todo$BRANCH_SUFFIX" {
region = "$REGION"
datacenters = ["$REGION"]
region = "global"
datacenters = ["*"]
group "static" {
// count = 3
count = 8
network {
port "http" {
@@ -14,13 +14,13 @@ job "example-todo$BRANCH_SUFFIX" {
constraint {
attribute = "${node.class}"
operator = "="
value = "edge"
value = "mesh"
}
// spread {
// attribute = "${node.datacenter}"
// weight = 100
// }
spread {
attribute = "${node.datacenter}"
weight = 100
}
task "server" {
driver = "docker"
@@ -37,9 +37,7 @@ job "example-todo$BRANCH_SUFFIX" {
service {
tags = ["public"]
meta {
public_name = "${BRANCH_SUBDOMAIN}example-todo"
}
name = "example-todo$BRANCH_SUFFIX"
port = "http"
provider = "consul"
}

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.16",
"version": "0.0.18",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,11 +13,13 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/qrcode": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "^0.1.3",
"jazz-react-auth-local": "^0.1.3",
"jazz-react": "^0.1.5",
"jazz-react-auth-local": "^0.1.5",
"lucide-react": "^0.265.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",

View File

@@ -18,6 +18,7 @@ import {
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import uniqolor from "uniqolor";
import QRCode from "qrcode";
type TaskContent = { done: boolean; text: string };
type Task = CoMap<TaskContent>;
@@ -223,16 +224,18 @@ function InviteButton({ list }: { list: TodoList }) {
className="py-0"
disabled={!list}
variant="outline"
onClick={() => {
onClick={async () => {
let inviteLink = existingInviteLink;
if (list && !inviteLink) {
inviteLink = createInviteLink(list, "writer");
setExistingInviteLink(inviteLink);
}
if (inviteLink) {
const qr = await QRCode.toDataURL(inviteLink, { errorCorrectionLevel: 'L' });
navigator.clipboard.writeText(inviteLink).then(() =>
toast({
description: "Copied invite link to clipboard!",
title: "Copied invite link to clipboard!",
description: <img src={qr} className="w-20 h-20"/>,
})
);
}

View File

@@ -170,4 +170,5 @@ dist
.DS_Store
out
out
sync.db*

View File

@@ -4,7 +4,7 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.1.1",
"version": "0.1.4",
"devDependencies": {
"@types/jest": "^29.5.3",
"@types/ws": "^8.5.5",
@@ -16,7 +16,8 @@
"typescript": "5.0.2"
},
"dependencies": {
"cojson": "^0.1.2",
"cojson": "^0.1.4",
"cojson-storage-sqlite": "^0.1.1",
"ws": "^8.13.0"
},
"scripts": {

View File

@@ -1,11 +1,12 @@
import { AnonymousControlledAccount, LocalNode, cojsonInternals } from "cojson";
import { WebSocketServer, createWebSocketStream } from "ws";
import { Duplex } from "node:stream";
import { TransformStream } from "node:stream/web"
import { TransformStream } from "node:stream/web";
import { SQLiteStorage } from "cojson-storage-sqlite";
const wss = new WebSocketServer({ port: 4200 });
console.log("COJSON sync server listening on port " + wss.options.port)
console.log("COJSON sync server listening on port " + wss.options.port);
const agentSecret = cojsonInternals.newRandomAgentSecret();
const agentID = cojsonInternals.getAgentID(agentSecret);
@@ -15,7 +16,25 @@ const localNode = new LocalNode(
cojsonInternals.newRandomSessionID(agentID)
);
SQLiteStorage.asPeer({ filename: "./sync.db" })
.then((storage) => localNode.sync.addPeer(storage))
.catch((e) => console.error(e));
wss.on("connection", function connection(ws, req) {
const pinging = setInterval(() => {
ws.send(
JSON.stringify({
type: "ping",
time: Date.now(),
dc: "cojson-simple-sync",
})
);
}, 2000);
ws.on("close", () => {
clearInterval(pinging);
});
const duplexStream = createWebSocketStream(ws, {
decodeStrings: false,
readableObjectMode: true,
@@ -24,18 +43,19 @@ wss.on("connection", function connection(ws, req) {
defaultEncoding: "utf-8",
});
const { readable: incomingStrings, writable: outgoingStrings } = Duplex.toWeb(duplexStream);
const { readable: incomingStrings, writable: outgoingStrings } =
Duplex.toWeb(duplexStream);
const toJSON = new TransformStream({
transform: (chunk, controller) => {
controller.enqueue(JSON.parse(chunk));
}
})
},
});
const fromJSON = new TransformStream({
transform: (chunk, controller) => {
controller.enqueue(JSON.stringify(chunk));
}
},
});
const clientAddress =

View File

@@ -0,0 +1,17 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
project: "./tsconfig.json",
},
root: true,
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
// "@typescript-eslint/no-floating-promises": "error",
},
};

View File

@@ -0,0 +1,171 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
.DS_Store

View File

@@ -0,0 +1,2 @@
coverage
node_modules

View File

@@ -0,0 +1,21 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.1.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^8.5.2",
"cojson": "^0.1.4",
"typescript": "^5.1.6"
},
"scripts": {
"lint": "eslint src/**/*.ts",
"build": "npm run lint && rm -rf ./dist && tsc --declaration --sourceMap --outDir dist",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.4"
}
}

View File

@@ -0,0 +1,383 @@
import {
cojsonInternals,
SyncMessage,
Peer,
CojsonInternalTypes,
SessionID,
// CojsonInternalTypes,
// SessionID,
} from "cojson";
import {
ReadableStream,
WritableStream,
ReadableStreamDefaultReader,
WritableStreamDefaultWriter,
} from "isomorphic-streams";
import Database, { Database as DatabaseT } from "better-sqlite3";
import { RawCoID } from "cojson/dist/ids";
type CoValueRow = {
id: CojsonInternalTypes.RawCoID;
header: string;
};
type StoredCoValueRow = CoValueRow & { rowID: number };
type SessionRow = {
coValue: number;
sessionID: SessionID;
lastIdx: number;
lastSignature: CojsonInternalTypes.Signature;
};
type StoredSessionRow = SessionRow & { rowID: number };
type TransactionRow = {
ses: number;
idx: number;
tx: string;
};
export class SQLiteStorage {
fromLocalNode!: ReadableStreamDefaultReader<SyncMessage>;
toLocalNode: WritableStreamDefaultWriter<SyncMessage>;
db: DatabaseT;
constructor(
db: DatabaseT,
fromLocalNode: ReadableStream<SyncMessage>,
toLocalNode: WritableStream<SyncMessage>
) {
this.db = db;
this.fromLocalNode = fromLocalNode.getReader();
this.toLocalNode = toLocalNode.getWriter();
(async () => {
let done = false;
while (!done) {
const result = await this.fromLocalNode.read();
done = result.done;
if (result.value) {
this.handleSyncMessage(result.value);
}
}
})();
}
static async asPeer({
filename,
trace,
localNodeName = "local",
}: {
filename: string;
trace?: boolean;
localNodeName?: string;
}): Promise<Peer> {
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "server", trace }
);
await SQLiteStorage.open(
filename,
localNodeAsPeer.incoming,
localNodeAsPeer.outgoing
);
return storageAsPeer;
}
static async open(
filename: string,
fromLocalNode: ReadableStream<SyncMessage>,
toLocalNode: WritableStream<SyncMessage>
) {
const db = Database(filename);
db.pragma("journal_mode = WAL");
db.prepare(
`CREATE TABLE IF NOT EXISTS transactions (
ses INTEGER,
idx INTEGER,
tx TEXT NOT NULL ,
PRIMARY KEY (ses, idx)
) WITHOUT ROWID;`
).run();
db.prepare(
`CREATE TABLE IF NOT EXISTS sessions (
rowID INTEGER PRIMARY KEY,
coValue INTEGER NOT NULL,
sessionID TEXT NOT NULL,
lastIdx INTEGER,
lastSignature TEXT,
UNIQUE (sessionID, coValue)
);`
).run();
db.prepare(
`CREATE INDEX IF NOT EXISTS sessionsByCoValue ON sessions (coValue);`
).run();
db.prepare(
`CREATE TABLE IF NOT EXISTS coValues (
rowID INTEGER PRIMARY KEY,
id TEXT NOT NULL UNIQUE,
header TEXT NOT NULL UNIQUE
);`
).run();
db.prepare(
`CREATE INDEX IF NOT EXISTS coValuesByID ON coValues (id);`
).run();
return new SQLiteStorage(db, fromLocalNode, toLocalNode);
}
async handleSyncMessage(msg: SyncMessage) {
switch (msg.action) {
case "load":
await this.handleLoad(msg);
break;
case "content":
await this.handleContent(msg);
break;
case "known":
await this.handleKnown(msg);
break;
case "done":
await this.handleDone(msg);
break;
}
}
async sendNewContentAfter(
theirKnown: CojsonInternalTypes.CoValueKnownState,
asDependencyOf?: CojsonInternalTypes.RawCoID
) {
const coValueRow = (await this.db
.prepare(`SELECT * FROM coValues WHERE id = ?`)
.get(theirKnown.id)) as StoredCoValueRow | undefined;
const allOurSessions = coValueRow
? (this.db
.prepare<number>(`SELECT * FROM sessions WHERE coValue = ?`)
.all(coValueRow.rowID) as StoredSessionRow[])
: [];
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
id: theirKnown.id,
header: !!coValueRow,
sessions: {},
};
const parsedHeader = (coValueRow?.header &&
JSON.parse(coValueRow.header)) as
| CojsonInternalTypes.CoValueHeader
| undefined;
const newContent: CojsonInternalTypes.NewContentMessage = {
action: "content",
id: theirKnown.id,
header: theirKnown.header ? undefined : parsedHeader,
new: {},
};
for (const sessionRow of allOurSessions) {
ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
if (
sessionRow.lastIdx >
(theirKnown.sessions[sessionRow.sessionID] || 0)
) {
const firstNewTxIdx =
theirKnown.sessions[sessionRow.sessionID] || 0;
const newTxInSession = this.db
.prepare<[number, number]>(
`SELECT * FROM transactions WHERE ses = ? AND idx > ?`
)
.all(sessionRow.rowID, firstNewTxIdx) as TransactionRow[];
newContent.new[sessionRow.sessionID] = {
after: firstNewTxIdx,
lastSignature: sessionRow.lastSignature,
newTransactions: newTxInSession.map((row) =>
JSON.parse(row.tx)
),
};
}
}
const dependedOnCoValues =
parsedHeader?.ruleset.type === "group"
? Object.values(newContent.new).flatMap((sessionEntry) =>
sessionEntry.newTransactions.flatMap((tx) => {
if (tx.privacy !== "trusting") return [];
return tx.changes
.map(
(change) =>
change &&
typeof change === "object" &&
"op" in change &&
change.op === "set" &&
"key" in change &&
change.key
)
.filter(
(key): key is CojsonInternalTypes.RawCoID =>
typeof key === "string" &&
key.startsWith("co_")
);
})
)
: parsedHeader?.ruleset.type === "ownedByGroup"
? [parsedHeader?.ruleset.group]
: [];
for (const dependedOnCoValue of dependedOnCoValues) {
await this.sendNewContentAfter(
{ id: dependedOnCoValue, header: false, sessions: {} },
asDependencyOf || theirKnown.id
);
}
await this.toLocalNode.write({
action: "known",
...ourKnown,
asDependencyOf,
});
if (newContent.header || Object.keys(newContent.new).length > 0) {
await this.toLocalNode.write(newContent);
}
}
handleLoad(msg: CojsonInternalTypes.LoadMessage) {
return this.sendNewContentAfter(msg);
}
async handleContent(msg: CojsonInternalTypes.NewContentMessage) {
let storedCoValueRowID = (
this.db
.prepare<RawCoID>(`SELECT rowID FROM coValues WHERE id = ?`)
.get(msg.id) as StoredCoValueRow | undefined
)?.rowID;
if (storedCoValueRowID === undefined) {
const header = msg.header;
if (!header) {
console.error("Expected to be sent header first");
await this.toLocalNode.write({
action: "known",
id: msg.id,
header: false,
sessions: {},
isCorrection: true,
});
return;
}
storedCoValueRowID = this.db
.prepare<[RawCoID, string]>(
`INSERT INTO coValues (id, header) VALUES (?, ?)`
)
.run(msg.id, JSON.stringify(header)).lastInsertRowid as number;
}
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
id: msg.id,
header: true,
sessions: {},
};
let invalidAssumptions = false;
this.db.transaction(() => {
const allOurSessions = (
this.db
.prepare<number>(`SELECT * FROM sessions WHERE coValue = ?`)
.all(storedCoValueRowID!) as StoredSessionRow[]
).reduce((acc, row) => {
acc[row.sessionID] = row;
return acc;
}, {} as { [sessionID: string]: StoredSessionRow });
for (const sessionID of Object.keys(msg.new) as SessionID[]) {
const sessionRow = allOurSessions[sessionID];
if (sessionRow) {
ourKnown.sessions[sessionRow.sessionID] =
sessionRow.lastIdx;
}
if (
(sessionRow?.lastIdx || 0) <
(msg.new[sessionID]?.after || 0)
) {
invalidAssumptions = true;
} else {
const newTransactions =
msg.new[sessionID]?.newTransactions || [];
const actuallyNewOffset =
(sessionRow?.lastIdx || 0) -
(msg.new[sessionID]?.after || 0);
const actuallyNewTransactions =
newTransactions.slice(actuallyNewOffset);
let nextIdx = sessionRow?.lastIdx || 0;
const sessionUpdate = {
coValue: storedCoValueRowID!,
sessionID: sessionID,
lastIdx:
(sessionRow?.lastIdx || 0) +
actuallyNewTransactions.length,
lastSignature: msg.new[sessionID]!.lastSignature,
};
const sessionRowID = this.db
.prepare<[number, string, number, string]>(
`INSERT INTO sessions (coValue, sessionID, lastIdx, lastSignature) VALUES (?, ?, ?, ?)
ON CONFLICT(coValue, sessionID) DO UPDATE SET lastIdx=excluded.lastIdx, lastSignature=excluded.lastSignature`
)
.run(
sessionUpdate.coValue,
sessionUpdate.sessionID,
sessionUpdate.lastIdx,
sessionUpdate.lastSignature
).lastInsertRowid as number;
for (const newTransaction of actuallyNewTransactions) {
nextIdx++;
this.db
.prepare<[number, number, string]>(
`INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)`
)
.run(
sessionRowID,
nextIdx,
JSON.stringify(newTransaction)
);
}
}
}
})();
if (invalidAssumptions) {
await this.toLocalNode.write({
action: "known",
...ourKnown,
isCorrection: invalidAssumptions,
});
}
}
handleKnown(msg: CojsonInternalTypes.KnownStateMessage) {
return this.sendNewContentAfter(msg);
}
handleDone(_msg: CojsonInternalTypes.DoneMessage) {}
}

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "ES2020",
"moduleResolution": "bundler",
"moduleDetection": "force",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
},
"include": ["./src/**/*"],
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"types": "dist/index.d.ts",
"type": "module",
"license": "MIT",
"version": "0.1.2",
"version": "0.1.4",
"devDependencies": {
"@types/jest": "^29.5.3",
"@typescript-eslint/eslint-plugin": "^6.2.1",

View File

@@ -149,16 +149,11 @@ export class SyncManager {
}
}
async subscribeToIncludingDependencies(
id: RawCoID,
peer: PeerState
) {
async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
const entry = this.local.coValues[id];
if (!entry) {
throw new Error(
"Expected coValue entry on subscribe"
);
throw new Error("Expected coValue entry on subscribe");
}
if (entry.state === "loading") {
@@ -212,10 +207,7 @@ export class SyncManager {
}
}
async sendNewContentIncludingDependencies(
id: RawCoID,
peer: PeerState
) {
async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
const coValue = this.local.expectCoValueLoaded(id);
for (const id of coValue.getDependedOnCoValues()) {
@@ -229,8 +221,7 @@ export class SyncManager {
if (newContent) {
await this.trySendToPeer(peer, newContent);
peer.optimisticKnownStates[id] = combinedKnownStates(
peer.optimisticKnownStates[id] ||
emptyKnownState(id),
peer.optimisticKnownStates[id] || emptyKnownState(id),
coValue.knownState()
);
}
@@ -265,17 +256,22 @@ export class SyncManager {
}
const readIncoming = async () => {
for await (const msg of peerState.incoming) {
try {
await this.handleSyncMessage(msg, peerState);
} catch (e) {
console.error(
`Error reading from peer ${peer.id}`,
JSON.stringify(msg),
e
);
try {
for await (const msg of peerState.incoming) {
try {
await this.handleSyncMessage(msg, peerState);
} catch (e) {
console.error(
`Error reading from peer ${peer.id}, handling msg`,
JSON.stringify(msg),
e
);
}
}
} catch (e) {
console.error(`Error reading from peer ${peer.id}`, e);
}
console.log("Peer disconnected:", peer.id);
delete this.peers[peer.id];
};
@@ -285,7 +281,11 @@ export class SyncManager {
trySendToPeer(peer: PeerState, msg: SyncMessage) {
return peer.outgoing.write(msg).catch((e) => {
console.error(new Error(`Error writing to peer ${peer.id}, disconnecting`, {cause: e}));
console.error(
new Error(`Error writing to peer ${peer.id}, disconnecting`, {
cause: e,
})
);
delete this.peers[peer.id];
});
}
@@ -295,7 +295,19 @@ export class SyncManager {
if (!entry || entry.state === "loading") {
if (!entry) {
this.local.coValues[msg.id] = newLoadingState();
await new Promise<void>((resolve) => {
this.local
.loadCoValue(msg.id)
.then(() => resolve())
.catch((e) => {
console.error(
"Error loading coValue in handleLoad",
e
);
resolve();
});
setTimeout(resolve, 1000);
});
}
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
@@ -313,10 +325,7 @@ export class SyncManager {
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
await this.tellUntoldKnownStateIncludingDependencies(
msg.id,
peer
);
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
await this.sendNewContentIncludingDependencies(msg.id, peer);
}
@@ -325,8 +334,7 @@ export class SyncManager {
let entry = this.local.coValues[msg.id];
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
peer.optimisticKnownStates[msg.id] ||
emptyKnownState(msg.id),
peer.optimisticKnownStates[msg.id] || emptyKnownState(msg.id),
knownStateIn(msg)
);
@@ -352,10 +360,7 @@ export class SyncManager {
return [];
}
await this.tellUntoldKnownStateIncludingDependencies(
msg.id,
peer
);
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
await this.sendNewContentIncludingDependencies(msg.id, peer);
}
@@ -370,8 +375,7 @@ export class SyncManager {
let resolveAfterDone: ((coValue: CoValue) => void) | undefined;
const peerOptimisticKnownState =
peer.optimisticKnownStates[msg.id];
const peerOptimisticKnownState = peer.optimisticKnownStates[msg.id];
if (!peerOptimisticKnownState) {
throw new Error(
@@ -453,10 +457,7 @@ export class SyncManager {
}
}
async handleCorrection(
msg: KnownStateMessage,
peer: PeerState
) {
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
const coValue = this.local.expectCoValueLoaded(msg.id);
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
@@ -499,11 +500,7 @@ export class SyncManager {
}
}
function knownStateIn(
msg:
| LoadMessage
| KnownStateMessage
) {
function knownStateIn(msg: LoadMessage | KnownStateMessage) {
return {
id: msg.id,
header: msg.header,

View File

@@ -1,11 +1,11 @@
{
"name": "jazz-browser-auth-local",
"version": "0.1.2",
"version": "0.1.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"jazz-browser": "^0.1.2",
"jazz-browser": "^0.1.4",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-browser",
"version": "0.1.2",
"version": "0.1.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.1.2",
"jazz-storage-indexeddb": "^0.1.2",
"cojson": "^0.1.4",
"jazz-storage-indexeddb": "^0.1.4",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -42,7 +42,7 @@ export async function createBrowserNode({
[await IDBStorage.asPeer({ trace: true }), firstWsPeer]
);
void async function websocketReconnectLoop() {
async function websocketReconnectLoop() {
while (shouldTryToReconnect) {
if (
Object.keys(node.sync.peers).some((peerId) =>
@@ -60,7 +60,9 @@ export async function createBrowserNode({
);
}
}
};
}
void websocketReconnectLoop();
return {
node,
@@ -153,6 +155,8 @@ function websocketReadableStream<T>(ws: WebSocket) {
return new ReadableStream<T>({
start(controller) {
let pingTimeout: ReturnType<typeof setTimeout> | undefined;
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "ping") {
@@ -163,13 +167,25 @@ function websocketReadableStream<T>(ws: WebSocket) {
Date.now() - msg.time,
"ms"
);
if (pingTimeout) {
clearTimeout(pingTimeout);
}
pingTimeout = setTimeout(() => {
console.debug("Ping timeout");
controller.close();
ws.close();
}, 2500);
return;
}
controller.enqueue(msg);
};
ws.onclose = () => controller.close();
ws.onerror = () =>
controller.error(new Error("The WebSocket errored!"));
ws.addEventListener("close", () => controller.close());
ws.addEventListener("error", () =>
controller.error(new Error("The WebSocket errored!"))
);
},
cancel() {
@@ -195,15 +211,15 @@ function createWebSocketPeer(syncAddress: string): Peer {
function websocketWritableStream<T>(ws: WebSocket) {
return new WritableStream<T>({
start(controller) {
ws.onerror = () => {
ws.addEventListener("error", () => {
controller.error(new Error("The WebSocket errored!"));
ws.onclose = null;
};
ws.onclose = () =>
});
ws.addEventListener("close", () => {
controller.error(
new Error("The server closed the connection unexpectedly!")
);
return new Promise((resolve) => (ws.onopen = resolve));
});
return new Promise((resolve) => (ws.addEventListener("open", resolve)));
},
write(chunk) {
@@ -223,13 +239,19 @@ function websocketWritableStream<T>(ws: WebSocket) {
function closeWS(code: number, reasonString?: string) {
return new Promise<void>((resolve, reject) => {
ws.onclose = (e) => {
if (e.wasClean) {
resolve();
} else {
reject(new Error("The connection was not closed cleanly"));
}
};
ws.addEventListener(
"close",
(e) => {
if (e.wasClean) {
resolve();
} else {
reject(
new Error("The connection was not closed cleanly")
);
}
},
{ once: true }
);
ws.close(code, reasonString);
});
}
@@ -277,8 +299,7 @@ export function parseInviteLink(inviteURL: string):
const valueID = url.hash
.split("&")[0]
?.replace(/^#invitedTo=/, "") as CoID<ContentType>;
const inviteSecret = url.hash
.split("&")[1] as InviteSecret;
const inviteSecret = url.hash.split("&")[1] as InviteSecret;
if (!valueID || !inviteSecret) {
return undefined;
}

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-react-auth-local",
"version": "0.1.3",
"version": "0.1.5",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"jazz-browser-auth-local": "^0.1.2",
"jazz-react": "^0.1.3",
"jazz-browser-auth-local": "^0.1.4",
"jazz-react": "^0.1.5",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-react",
"version": "0.1.3",
"version": "0.1.5",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.1.2",
"jazz-browser": "^0.1.2",
"cojson": "^0.1.4",
"jazz-browser": "^0.1.4",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,11 +1,11 @@
{
"name": "jazz-storage-indexeddb",
"version": "0.1.2",
"version": "0.1.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.1.2",
"cojson": "^0.1.4",
"typescript": "^5.1.6"
},
"devDependencies": {

235
yarn.lock
View File

@@ -1513,6 +1513,13 @@
dependencies:
"@babel/types" "^7.20.7"
"@types/better-sqlite3@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@types/better-sqlite3/-/better-sqlite3-7.6.4.tgz#102462611e67aadf950d3ccca10292de91e6f35b"
integrity sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==
dependencies:
"@types/node" "*"
"@types/chai-subset@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
@@ -1604,6 +1611,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/qrcode@^1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.1.tgz#027c2dbfbc8505e1fe2f4033daba920dbd182b44"
integrity sha512-HpSN675K0PmxIDRpjMI3Mc2GiKo3dNu+X/F5SoItiaDS1lVfgC6Wac1c5lQDfKWbTJUSHWiHKzpJpBZG7k9gaA==
dependencies:
"@types/node" "*"
"@types/react-dom@^18.2.7":
version "18.2.7"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
@@ -2243,6 +2257,14 @@ before-after-hook@^2.2.0:
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==
better-sqlite3@^8.5.2:
version "8.5.2"
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-8.5.2.tgz#a1c13e4361125255e39302e8b569a6568c3291e3"
integrity sha512-w/EZ/jwuZF+/47mAVC2+rhR2X/gwkZ+fd1pbX7Y90D5NRaRzDQcxrHY10t6ijGiYIonCVsBSF5v1cay07bP5sg==
dependencies:
bindings "^1.5.0"
prebuild-install "^7.1.0"
big-integer@^1.6.17:
version "1.6.51"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
@@ -2261,6 +2283,13 @@ binary@~0.3.0:
buffers "~0.1.1"
chainsaw "~0.1.0"
bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
dependencies:
file-uri-to-path "1.0.0"
bl@^4.0.3, bl@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
@@ -2433,7 +2462,7 @@ camelcase-keys@^6.2.2:
map-obj "^4.0.0"
quick-lru "^4.0.1"
camelcase@^5.3.1:
camelcase@^5.0.0, camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
@@ -2528,6 +2557,11 @@ chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@@ -2594,6 +2628,15 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
@@ -2927,7 +2970,7 @@ decamelize-keys@^1.1.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
decamelize@^1.1.0:
decamelize@^1.1.0, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
@@ -2961,6 +3004,11 @@ deep-eql@^4.1.2:
dependencies:
type-detect "^4.0.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@@ -3027,6 +3075,11 @@ detect-indent@^5.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==
detect-libc@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d"
integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -3052,6 +3105,11 @@ diff-sequences@^29.4.3:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
dijkstrajs@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -3148,6 +3206,11 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
encode-utf8@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
@@ -3543,6 +3606,11 @@ exit@^0.1.2:
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
expect@^29.0.0, expect@^29.6.2:
version "29.6.2"
resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521"
@@ -3674,6 +3742,11 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
@@ -3871,7 +3944,7 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.5:
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -3980,6 +4053,11 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -4359,7 +4437,7 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ini@^1.3.2, ini@^1.3.8:
ini@^1.3.2, ini@^1.3.8, ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
@@ -5614,7 +5692,7 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -5701,7 +5779,7 @@ mitt@3.0.0:
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
mkdirp-classic@^0.5.2:
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
@@ -5798,6 +5876,11 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -5826,6 +5909,13 @@ nice-napi@^1.0.2:
node-addon-api "^3.0.0"
node-gyp-build "^4.2.2"
node-abi@^3.3.0:
version "3.47.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.47.0.tgz#6cbfa2916805ae25c2b7156ca640131632eb05e8"
integrity sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==
dependencies:
semver "^7.3.5"
node-addon-api@^3.0.0, node-addon-api@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
@@ -6521,6 +6611,11 @@ pkg-types@^1.0.3:
mlly "^1.2.0"
pathe "^1.1.0"
pngjs@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
postcss-import@^15.1.0:
version "15.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
@@ -6574,6 +6669,24 @@ postcss@^8.4.23, postcss@^8.4.27:
picocolors "^1.0.0"
source-map-js "^1.0.2"
prebuild-install@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
dependencies:
detect-libc "^2.0.0"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.3"
mkdirp-classic "^0.5.3"
napi-build-utils "^1.0.1"
node-abi "^3.3.0"
pump "^3.0.0"
rc "^1.2.7"
simple-get "^4.0.0"
tar-fs "^2.0.0"
tunnel-agent "^0.6.0"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -6685,6 +6798,16 @@ pure-rand@^6.0.0:
resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306"
integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==
qrcode@^1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170"
integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==
dependencies:
dijkstrajs "^1.0.1"
encode-utf8 "^1.0.3"
pngjs "^5.0.0"
yargs "^15.3.1"
query-selector-shadow-dom@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz#1c7b0058eff4881ac44f45d8f84ede32e9a2f349"
@@ -6710,6 +6833,16 @@ quick-lru@^5.1.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
dependencies:
deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -6876,6 +7009,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
resolve-alpn@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
@@ -7001,16 +7139,16 @@ safaridriver@^0.1.0:
resolved "https://registry.yarnpkg.com/safaridriver/-/safaridriver-0.1.0.tgz#8ff901e847b003c6a52b534028f57cddc82d6b14"
integrity sha512-azzzIP3gR1TB9bVPv7QO4Zjw0rR1BWEU/s2aFdUMN48gxDjxEB13grAEuXDmkKPgE74cObymDxmAmZnL3clj4w==
safe-buffer@^5.0.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -7109,6 +7247,20 @@ sigstore@^1.3.0, sigstore@^1.4.0:
"@sigstore/tuf" "^1.0.3"
make-fetch-happen "^11.0.1"
simple-concat@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
dependencies:
decompress-response "^6.0.0"
once "^1.3.1"
simple-concat "^1.0.0"
sirv@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446"
@@ -7349,6 +7501,11 @@ strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
strip-literal@^1.0.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.3.0.tgz#db3942c2ec1699e6836ad230090b84bb458e3a07"
@@ -7451,7 +7608,17 @@ tar-fs@3.0.4, tar-fs@^3.0.4:
pump "^3.0.0"
tar-stream "^3.1.5"
tar-stream@^2.2.0, tar-stream@~2.2.0:
tar-fs@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-stream@^2.1.4, tar-stream@^2.2.0, tar-stream@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
@@ -7659,6 +7826,13 @@ tuf-js@^1.1.7:
debug "^4.3.4"
make-fetch-happen "^11.1.1"
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
dependencies:
safe-buffer "^5.0.1"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -8001,6 +8175,11 @@ whatwg-url@^5.0.0:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which-module@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
which@^2.0.1, which@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
@@ -8045,7 +8224,7 @@ wordwrap@^1.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^6.0.1:
wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
@@ -8124,6 +8303,11 @@ xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
@@ -8154,6 +8338,14 @@ yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^20.2.2, yargs-parser@^20.2.3:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
@@ -8185,6 +8377,23 @@ yargs@17.7.1:
y18n "^5.0.5"
yargs-parser "^21.1.1"
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
yargs@^17.3.1, yargs@^17.6.2:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"