Rename quack to quak
German for 'quack', matching the Ente (German for 'duck') naming. All references updated: package name, CLI binary, X-Client-Package header, test descriptions, temp dir prefixes, README, Makefile docker tag.
This commit is contained in:
2
Makefile
2
Makefile
@@ -33,7 +33,7 @@ clean:
|
|||||||
@rm -rf dist coverage .vitest-cache *.tsbuildinfo
|
@rm -rf dist coverage .vitest-cache *.tsbuildinfo
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t quack .
|
docker build -t quak .
|
||||||
|
|
||||||
hooks:
|
hooks:
|
||||||
@printf '#!/bin/sh\nset -e\nmake lint\nmake fmt-check\n' > .git/hooks/pre-commit
|
@printf '#!/bin/sh\nset -e\nmake lint\nmake fmt-check\n' > .git/hooks/pre-commit
|
||||||
|
|||||||
60
README.md
60
README.md
@@ -1,6 +1,6 @@
|
|||||||
# quack
|
# quak
|
||||||
|
|
||||||
quack is a WTFPL-licensed TypeScript client library and CLI by
|
quak is a WTFPL-licensed TypeScript client library and CLI by
|
||||||
[@sneak](https://sneak.berlin) for the [Ente](https://ente.io) end-to-end
|
[@sneak](https://sneak.berlin) for the [Ente](https://ente.io) end-to-end
|
||||||
encrypted photo hosting service. It logs in, enumerates collections and files,
|
encrypted photo hosting service. It logs in, enumerates collections and files,
|
||||||
and downloads individual images while decrypting them on the way to disk.
|
and downloads individual images while decrypting them on the way to disk.
|
||||||
@@ -8,29 +8,29 @@ and downloads individual images while decrypting them on the way to disk.
|
|||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.eeqj.de/sneak/quack.git
|
git clone https://git.eeqj.de/sneak/quak.git
|
||||||
cd quack
|
cd quak
|
||||||
yarn install
|
yarn install
|
||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
# Log in (prompts for email, password, and OTP/TOTP if required).
|
# Log in (prompts for email, password, and OTP/TOTP if required).
|
||||||
# Stores an encrypted session under $XDG_CONFIG_HOME/quack/.
|
# Stores an encrypted session under $XDG_CONFIG_HOME/quak/.
|
||||||
yarn quack login
|
yarn quak login
|
||||||
|
|
||||||
# List the user's collections (albums).
|
# List the user's collections (albums).
|
||||||
yarn quack collections
|
yarn quak collections
|
||||||
|
|
||||||
# List files in a collection.
|
# List files in a collection.
|
||||||
yarn quack files --collection 12345
|
yarn quak files --collection 12345
|
||||||
|
|
||||||
# Download and decrypt a single file to ./out/.
|
# Download and decrypt a single file to ./out/.
|
||||||
yarn quack get 67890 --out ./out/
|
yarn quak get 67890 --out ./out/
|
||||||
```
|
```
|
||||||
|
|
||||||
For library use:
|
For library use:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Client } from "quack";
|
import { Client } from "quak";
|
||||||
|
|
||||||
const client = await Client.fromSavedSession();
|
const client = await Client.fromSavedSession();
|
||||||
for (const c of await client.listCollections()) {
|
for (const c of await client.listCollections()) {
|
||||||
@@ -49,7 +49,7 @@ Flutter app fails to sync reliably. The web app is heavy. The desktop app is the
|
|||||||
web app inside a slow Electron wrapper. The Go CLI is the closest thing to a
|
web app inside a slow Electron wrapper. The Go CLI is the closest thing to a
|
||||||
usable tool, but it is awkward to integrate from anything that is not a shell.
|
usable tool, but it is awkward to integrate from anything that is not a shell.
|
||||||
|
|
||||||
quack is the first step in fixing that. This repo ships a small, correct,
|
quak is the first step in fixing that. This repo ships a small, correct,
|
||||||
well-tested implementation of Ente's cryptographic protocol and its read-only
|
well-tested implementation of Ente's cryptographic protocol and its read-only
|
||||||
API surface, plus a CLI that proves the library is enough to do real work
|
API surface, plus a CLI that proves the library is enough to do real work
|
||||||
without a UI.
|
without a UI.
|
||||||
@@ -67,7 +67,7 @@ requires the protocol layer to be correct first.
|
|||||||
|
|
||||||
## Development workflow
|
## Development workflow
|
||||||
|
|
||||||
All work on quack is test-driven. No exceptions.
|
All work on quak is test-driven. No exceptions.
|
||||||
|
|
||||||
1. Every change starts on a feature branch off `main`.
|
1. Every change starts on a feature branch off `main`.
|
||||||
2. The first commit on the branch is the test suite for what is being added or
|
2. The first commit on the branch is the test suite for what is being added or
|
||||||
@@ -79,7 +79,7 @@ All work on quack is test-driven. No exceptions.
|
|||||||
`main` is always green. The Dockerfile runs `make check`, so a red branch
|
`main` is always green. The Dockerfile runs `make check`, so a red branch
|
||||||
cannot pass CI.
|
cannot pass CI.
|
||||||
5. Tests are the canonical API documentation for this library. Every test file
|
5. Tests are the canonical API documentation for this library. Every test file
|
||||||
is commented thoroughly enough that a reader who has never seen quack can
|
is commented thoroughly enough that a reader who has never seen quak can
|
||||||
learn how to use it from the tests alone. Comments explain why a behavior
|
learn how to use it from the tests alone. Comments explain why a behavior
|
||||||
matters, not just what the assertion checks.
|
matters, not just what the assertion checks.
|
||||||
6. Test fixtures (cryptographic vectors, recorded HTTP responses, sample files)
|
6. Test fixtures (cryptographic vectors, recorded HTTP responses, sample files)
|
||||||
@@ -98,13 +98,13 @@ All work on quack is test-driven. No exceptions.
|
|||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
quack is a TypeScript library with a thin CLI wrapper. The library does the
|
quak is a TypeScript library with a thin CLI wrapper. The library does the work;
|
||||||
work; the CLI is for humans.
|
the CLI is for humans.
|
||||||
|
|
||||||
### Layout
|
### Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
quack/
|
quak/
|
||||||
src/
|
src/
|
||||||
crypto/ libsodium primitives (boxes, secretstreams, KDF, SRP)
|
crypto/ libsodium primitives (boxes, secretstreams, KDF, SRP)
|
||||||
api/ HTTP client + typed endpoint wrappers
|
api/ HTTP client + typed endpoint wrappers
|
||||||
@@ -114,7 +114,7 @@ quack/
|
|||||||
client.ts high-level Client class assembled from the above
|
client.ts high-level Client class assembled from the above
|
||||||
index.ts public library exports
|
index.ts public library exports
|
||||||
bin/
|
bin/
|
||||||
quack.ts CLI entrypoint (commander.js)
|
quak.ts CLI entrypoint (commander.js)
|
||||||
test/ unit + integration tests (vitest)
|
test/ unit + integration tests (vitest)
|
||||||
Makefile
|
Makefile
|
||||||
Dockerfile
|
Dockerfile
|
||||||
@@ -169,7 +169,7 @@ A custom API endpoint is configurable for self-hosted servers via the
|
|||||||
Required request headers on every authenticated call:
|
Required request headers on every authenticated call:
|
||||||
|
|
||||||
- `X-Auth-Token`: the decrypted auth token from login.
|
- `X-Auth-Token`: the decrypted auth token from login.
|
||||||
- `X-Client-Package`: identifies the client. quack uses `berlin.sneak.quack`.
|
- `X-Client-Package`: identifies the client. quak uses `berlin.sneak.quak`.
|
||||||
|
|
||||||
Endpoints used:
|
Endpoints used:
|
||||||
|
|
||||||
@@ -187,8 +187,8 @@ Endpoints used:
|
|||||||
|
|
||||||
### Session persistence
|
### Session persistence
|
||||||
|
|
||||||
After login, quack writes an encrypted session blob to
|
After login, quak writes an encrypted session blob to
|
||||||
`$XDG_CONFIG_HOME/quack/session.json` (default `~/.config/quack/session.json`)
|
`$XDG_CONFIG_HOME/quak/session.json` (default `~/.config/quak/session.json`)
|
||||||
containing the auth token, the user's master key, the user's secret key, and the
|
containing the auth token, the user's master key, the user's secret key, and the
|
||||||
user's email. The session file is itself encrypted with a key derived from a
|
user's email. The session file is itself encrypted with a key derived from a
|
||||||
per-machine random value stored in the OS keychain when available, falling back
|
per-machine random value stored in the OS keychain when available, falling back
|
||||||
@@ -197,14 +197,14 @@ secret key are never written to disk in cleartext.
|
|||||||
|
|
||||||
### CLI surface
|
### CLI surface
|
||||||
|
|
||||||
- `quack login`: interactive login, writes session.
|
- `quak login`: interactive login, writes session.
|
||||||
- `quack logout`: deletes the session.
|
- `quak logout`: deletes the session.
|
||||||
- `quack whoami`: prints the logged-in email.
|
- `quak whoami`: prints the logged-in email.
|
||||||
- `quack collections`: list collections (id, name, type, file count).
|
- `quak collections`: list collections (id, name, type, file count).
|
||||||
- `quack files --collection <id>`: list files in a collection (id, name, type,
|
- `quak files --collection <id>`: list files in a collection (id, name, type,
|
||||||
creation time, size).
|
creation time, size).
|
||||||
- `quack get <fileID> --out <dir>`: download and decrypt a file.
|
- `quak get <fileID> --out <dir>`: download and decrypt a file.
|
||||||
- `quack get-thumb <fileID> --out <dir>`: download and decrypt a thumbnail.
|
- `quak get-thumb <fileID> --out <dir>`: download and decrypt a thumbnail.
|
||||||
|
|
||||||
All commands accept `--json` for machine-readable output.
|
All commands accept `--json` for machine-readable output.
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ export interface ApiClientOptions {
|
|||||||
thumbsOrigin?: string; // default https://thumbnails.ente.io
|
thumbsOrigin?: string; // default https://thumbnails.ente.io
|
||||||
authToken?: string;
|
authToken?: string;
|
||||||
fetch?: typeof fetch; // injectable for tests
|
fetch?: typeof fetch; // injectable for tests
|
||||||
userAgent?: string; // default "quack/<version>"
|
userAgent?: string; // default "quak/<version>"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
@@ -481,8 +481,8 @@ export interface Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionStoreOptions {
|
export interface SessionStoreOptions {
|
||||||
path?: string; // default $XDG_CONFIG_HOME/quack/session.json
|
path?: string; // default $XDG_CONFIG_HOME/quak/session.json
|
||||||
keychainService?: string; // default "berlin.sneak.quack"
|
keychainService?: string; // default "berlin.sneak.quak"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SessionStore {
|
export class SessionStore {
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "quack",
|
"name": "quak",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"description": "TypeScript client library and CLI for the Ente end-to-end encrypted photo service",
|
"description": "TypeScript client library and CLI for the Ente end-to-end encrypted photo service",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"author": "@sneak <https://sneak.berlin>",
|
"author": "@sneak <https://sneak.berlin>",
|
||||||
"homepage": "https://git.eeqj.de/sneak/quack",
|
"homepage": "https://git.eeqj.de/sneak/quak",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.eeqj.de/sneak/quack.git"
|
"url": "https://git.eeqj.de/sneak/quak.git"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"bin": {
|
"bin": {
|
||||||
"quack": "./dist/bin/quack.js"
|
"quak": "./dist/bin/quak.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/",
|
"dist/",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const DEFAULT_API_ORIGIN = "https://api.ente.io";
|
const DEFAULT_API_ORIGIN = "https://api.ente.io";
|
||||||
const DEFAULT_FILES_ORIGIN = "https://files.ente.io";
|
const DEFAULT_FILES_ORIGIN = "https://files.ente.io";
|
||||||
const DEFAULT_THUMBS_ORIGIN = "https://thumbnails.ente.io";
|
const DEFAULT_THUMBS_ORIGIN = "https://thumbnails.ente.io";
|
||||||
const CLIENT_PACKAGE = "berlin.sneak.quack";
|
const CLIENT_PACKAGE = "berlin.sneak.quak";
|
||||||
|
|
||||||
export interface ApiClientOptions {
|
export interface ApiClientOptions {
|
||||||
apiOrigin?: string;
|
apiOrigin?: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
export type Base64 = string;
|
export type Base64 = string;
|
||||||
|
|
||||||
// The blob the server returns alongside the auth token after a successful
|
// The blob the server returns alongside the auth token after a successful
|
||||||
// login. Together with the user's password, this is everything quack needs
|
// login. Together with the user's password, this is everything quak needs
|
||||||
// to derive the master key, secret key, and auth token.
|
// to derive the master key, secret key, and auth token.
|
||||||
export interface KeyAttributes {
|
export interface KeyAttributes {
|
||||||
kekSalt: Base64;
|
kekSalt: Base64;
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class Client {
|
|||||||
const code = await opts.emailOTP();
|
const code = await opts.emailOTP();
|
||||||
response = await submitEmailOTP(api, opts.email, code);
|
response = await submitEmailOTP(api, opts.email, code);
|
||||||
} else if (challenge.kind === "passkey") {
|
} else if (challenge.kind === "passkey") {
|
||||||
throw new Error("Passkey authentication is not supported by quack");
|
throw new Error("Passkey authentication is not supported by quak");
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown login challenge kind`);
|
throw new Error(`Unknown login challenge kind`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import sodium from "libsodium-wrappers-sumo";
|
import sodium from "libsodium-wrappers-sumo";
|
||||||
|
|
||||||
// Argon2id over (password, salt, opsLimit, memLimit) producing a 32-byte
|
// Argon2id over (password, salt, opsLimit, memLimit) producing a 32-byte
|
||||||
// Key Encryption Key. Parameters come from the server; quack passes them
|
// Key Encryption Key. Parameters come from the server; quak passes them
|
||||||
// straight through. memLimit is in bytes, opsLimit is the iteration count.
|
// straight through. memLimit is in bytes, opsLimit is the iteration count.
|
||||||
export const deriveKEK = async (
|
export const deriveKEK = async (
|
||||||
password: string,
|
password: string,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Tests for `ApiClient`.
|
* Tests for `ApiClient`.
|
||||||
*
|
*
|
||||||
* `ApiClient` is the HTTP layer that every other module in quack calls to
|
* `ApiClient` is the HTTP layer that every other module in quak calls to
|
||||||
* reach the Ente server. It handles:
|
* reach the Ente server. It handles:
|
||||||
*
|
*
|
||||||
* - Base URL resolution. Production uses `https://api.ente.io` for the
|
* - Base URL resolution. Production uses `https://api.ente.io` for the
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* dedicated CDN hosts.
|
* dedicated CDN hosts.
|
||||||
*
|
*
|
||||||
* - Required headers. Every request carries `X-Client-Package`
|
* - Required headers. Every request carries `X-Client-Package`
|
||||||
* (`berlin.sneak.quack`). Authenticated requests also carry
|
* (`berlin.sneak.quak`). Authenticated requests also carry
|
||||||
* `X-Auth-Token` with the token recovered by `unwrapAuth`.
|
* `X-Auth-Token` with the token recovered by `unwrapAuth`.
|
||||||
*
|
*
|
||||||
* - JSON serialization. `getJSON` and `postJSON` handle Accept /
|
* - JSON serialization. `getJSON` and `postJSON` handle Accept /
|
||||||
@@ -121,7 +121,7 @@ describe("ApiClient defaults", () => {
|
|||||||
await client.getJSON("/ping");
|
await client.getJSON("/ping");
|
||||||
|
|
||||||
const headers = new Headers(calls[0]!.init?.headers as HeadersInit);
|
const headers = new Headers(calls[0]!.init?.headers as HeadersInit);
|
||||||
expect(headers.get("X-Client-Package")).toBe("berlin.sneak.quack");
|
expect(headers.get("X-Client-Package")).toBe("berlin.sneak.quak");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* # Using the quack Client
|
* # Using the quak Client
|
||||||
*
|
*
|
||||||
* This test file is a tutorial. It walks through every operation the
|
* This test file is a tutorial. It walks through every operation the
|
||||||
* library supports, in the order you would use them in a real program.
|
* library supports, in the order you would use them in a real program.
|
||||||
* Each `it()` block is a self-contained example with commentary
|
* Each `it()` block is a self-contained example with commentary
|
||||||
* explaining what is happening and why. If you are reading the quack
|
* explaining what is happening and why. If you are reading the quak
|
||||||
* source for the first time, start here.
|
* source for the first time, start here.
|
||||||
*
|
*
|
||||||
* The tests run against a mock Ente server built from the same SRP and
|
* The tests run against a mock Ente server built from the same SRP and
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
* All you need is the `Client` class:
|
* All you need is the `Client` class:
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* import { Client } from "quack";
|
* import { Client } from "quak";
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The Client wraps every lower-level module (crypto, auth, api, model,
|
* The Client wraps every lower-level module (crypto, auth, api, model,
|
||||||
@@ -332,7 +332,7 @@ beforeAll(async () => {
|
|||||||
await init();
|
await init();
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
server = await buildServer();
|
server = await buildServer();
|
||||||
testDir = mkdtempSync(join(tmpdir(), "quack-usage-test-"));
|
testDir = mkdtempSync(join(tmpdir(), "quak-usage-test-"));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -344,7 +344,7 @@ afterAll(() => {
|
|||||||
// The tutorial
|
// The tutorial
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
describe("quack Client usage guide", () => {
|
describe("quak Client usage guide", () => {
|
||||||
/**
|
/**
|
||||||
* ## 1. Logging in
|
* ## 1. Logging in
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Tests for `crypto.decryptBox` and `crypto.decryptSealed`.
|
* Tests for `crypto.decryptBox` and `crypto.decryptSealed`.
|
||||||
*
|
*
|
||||||
* These cover the two asymmetric-and-symmetric "box" primitives quack uses
|
* These cover the two asymmetric-and-symmetric "box" primitives quak uses
|
||||||
* to unwrap key material from Ente:
|
* to unwrap key material from Ente:
|
||||||
*
|
*
|
||||||
* - `decryptBox`: secretbox decryption. Used everywhere a small payload
|
* - `decryptBox`: secretbox decryption. Used everywhere a small payload
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
*
|
*
|
||||||
* authToken = decryptSealed(encryptedToken, publicKey, secretKey)
|
* authToken = decryptSealed(encryptedToken, publicKey, secretKey)
|
||||||
*
|
*
|
||||||
* Encryption is server-side; quack only ever decrypts.
|
* Encryption is server-side; quak only ever decrypts.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import sodium from "libsodium-wrappers-sumo";
|
import sodium from "libsodium-wrappers-sumo";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Ente delivers most binary fields as standard base64 strings (with `+`,
|
* Ente delivers most binary fields as standard base64 strings (with `+`,
|
||||||
* `/`, and `=` padding). A few fields, notably the auth token returned by
|
* `/`, and `=` padding). A few fields, notably the auth token returned by
|
||||||
* the login flow, are URL-safe base64 (with `-` and `_` instead of `+` and
|
* the login flow, are URL-safe base64 (with `-` and `_` instead of `+` and
|
||||||
* `/`, and stripped padding). quack must accept both forms on input and
|
* `/`, and stripped padding). quak must accept both forms on input and
|
||||||
* produce the right form on output.
|
* produce the right form on output.
|
||||||
*
|
*
|
||||||
* These tests pin the contract:
|
* These tests pin the contract:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* libsodium ships as WebAssembly. The bindings (`libsodium-wrappers-sumo`)
|
* libsodium ships as WebAssembly. The bindings (`libsodium-wrappers-sumo`)
|
||||||
* load asynchronously: the runtime must `await sodium.ready` once before any
|
* load asynchronously: the runtime must `await sodium.ready` once before any
|
||||||
* crypto call is safe. quack hides that detail behind a single
|
* crypto call is safe. quak hides that detail behind a single
|
||||||
* `init()` function.
|
* `init()` function.
|
||||||
*
|
*
|
||||||
* Every other test in `test/crypto/**` calls `init()` in `beforeAll`. New
|
* Every other test in `test/crypto/**` calls `init()` in `beforeAll`. New
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ let testDir: string;
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await init();
|
await init();
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
testDir = mkdtempSync(join(tmpdir(), "quack-test-"));
|
testDir = mkdtempSync(join(tmpdir(), "quak-test-"));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -97,7 +97,7 @@ const mockFetchForBody = (body: Uint8Array) => {
|
|||||||
describe("downloadFile", () => {
|
describe("downloadFile", () => {
|
||||||
it("downloads, decrypts, and writes a single-chunk file", async () => {
|
it("downloads, decrypts, and writes a single-chunk file", async () => {
|
||||||
const plaintext = new TextEncoder().encode(
|
const plaintext = new TextEncoder().encode(
|
||||||
"Hello from quack! This is a test photo payload.",
|
"Hello from quak! This is a test photo payload.",
|
||||||
);
|
);
|
||||||
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
||||||
const { header, ciphertext } = encryptFileBody(plaintext, key);
|
const { header, ciphertext } = encryptFileBody(plaintext, key);
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ const main = async () => {
|
|||||||
const { mkdtempSync, statSync } = await import("node:fs");
|
const { mkdtempSync, statSync } = await import("node:fs");
|
||||||
const { join } = await import("node:path");
|
const { join } = await import("node:path");
|
||||||
const { tmpdir } = await import("node:os");
|
const { tmpdir } = await import("node:os");
|
||||||
const outDir = mkdtempSync(join(tmpdir(), "quack-live-test-"));
|
const outDir = mkdtempSync(join(tmpdir(), "quak-live-test-"));
|
||||||
const outPath = `${outDir}/${first.metadata.title}`;
|
const outPath = `${outDir}/${first.metadata.title}`;
|
||||||
|
|
||||||
console.log(`\n Downloading "${first.metadata.title}"...`);
|
console.log(`\n Downloading "${first.metadata.title}"...`);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* These two functions turn the raw encrypted JSON blobs the Ente server
|
* These two functions turn the raw encrypted JSON blobs the Ente server
|
||||||
* returns into the decrypted Collection and EnteFile objects that the
|
* returns into the decrypted Collection and EnteFile objects that the
|
||||||
* rest of quack works with.
|
* rest of quak works with.
|
||||||
*
|
*
|
||||||
* ## Collection decryption
|
* ## Collection decryption
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { VERSION } from "../src/index.js";
|
import { VERSION } from "../src/index.js";
|
||||||
|
|
||||||
describe("quack", () => {
|
describe("quak", () => {
|
||||||
it("exports a version string", () => {
|
it("exports a version string", () => {
|
||||||
expect(typeof VERSION).toBe("string");
|
expect(typeof VERSION).toBe("string");
|
||||||
expect(VERSION.length).toBeGreaterThan(0);
|
expect(VERSION.length).toBeGreaterThan(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user