@peers-app/peers-sdk 0.18.6 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -1
- package/dist/data/files/file-read-stream.js +7 -0
- package/dist/data/files/file.types.d.ts +6 -0
- package/dist/data/files/file.types.js +18 -0
- package/dist/data/files/files.test.js +50 -7
- package/dist/data/package-version-resolver.test.js +1 -0
- package/dist/data/package-versions.d.ts +3 -0
- package/dist/data/package-versions.js +5 -0
- package/dist/data/packages.d.ts +6 -0
- package/dist/data/packages.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/package-installer/index.d.ts +10 -0
- package/dist/package-installer/index.js +26 -0
- package/dist/package-installer/package-author-signing.d.ts +48 -0
- package/dist/package-installer/package-author-signing.js +73 -0
- package/dist/package-installer/package-author-signing.test.d.ts +1 -0
- package/dist/package-installer/package-author-signing.test.js +189 -0
- package/dist/package-installer/package-cloner.d.ts +16 -0
- package/dist/package-installer/package-cloner.js +115 -0
- package/dist/package-installer/package-cloner.test.d.ts +1 -0
- package/dist/package-installer/package-cloner.test.js +276 -0
- package/dist/package-installer/package-creator.d.ts +22 -0
- package/dist/package-installer/package-creator.js +154 -0
- package/dist/package-installer/package-creator.test.d.ts +1 -0
- package/dist/package-installer/package-creator.test.js +354 -0
- package/dist/package-installer/package-installer.d.ts +32 -0
- package/dist/package-installer/package-installer.js +247 -0
- package/dist/package-installer/package-installer.test.d.ts +1 -0
- package/dist/package-installer/package-installer.test.js +666 -0
- package/dist/package-installer/package-propagation.d.ts +29 -0
- package/dist/package-installer/package-propagation.js +363 -0
- package/dist/package-installer/package-propagation.test.d.ts +1 -0
- package/dist/package-installer/package-propagation.test.js +1145 -0
- package/dist/package-installer/package-publisher.d.ts +50 -0
- package/dist/package-installer/package-publisher.js +67 -0
- package/dist/package-installer/package-publisher.test.d.ts +1 -0
- package/dist/package-installer/package-publisher.test.js +142 -0
- package/dist/package-installer/package-remote-checker.d.ts +54 -0
- package/dist/package-installer/package-remote-checker.js +186 -0
- package/dist/package-installer/package-remote-checker.test.d.ts +1 -0
- package/dist/package-installer/package-remote-checker.test.js +263 -0
- package/dist/package-installer/package-seed-installer.d.ts +45 -0
- package/dist/package-installer/package-seed-installer.js +108 -0
- package/dist/package-installer/package-seed-installer.test.d.ts +1 -0
- package/dist/package-installer/package-seed-installer.test.js +123 -0
- package/dist/package-installer/package-tarball.d.ts +35 -0
- package/dist/package-installer/package-tarball.js +57 -0
- package/dist/package-installer/package-tarball.test.d.ts +1 -0
- package/dist/package-installer/package-tarball.test.js +75 -0
- package/dist/package-installer/types.d.ts +110 -0
- package/dist/package-installer/types.js +2 -0
- package/dist/rpc-types.d.ts +14 -0
- package/dist/rpc-types.js +6 -0
- package/dist/system-ids.d.ts +1 -0
- package/dist/system-ids.js +2 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
# @peers-app/peers-sdk
|
|
2
|
+
|
|
3
|
+
The core SDK for building [Peers](https://peers.app) packages.
|
|
4
|
+
|
|
5
|
+
## What is Peers?
|
|
6
|
+
|
|
7
|
+
Peers is a local-first personal computing platform. Your data lives on your devices, syncs peer-to-peer, and is end-to-end encrypted — no servers in the middle. You own your data and your identity. Build apps with the SDK or use AI coding tools to create them; either way, the platform handles sync, encryption, and persistence automatically.
|
|
8
|
+
|
|
9
|
+
## What this package provides
|
|
10
|
+
|
|
11
|
+
### Package System
|
|
12
|
+
|
|
13
|
+
`definePackage()` and the contract builder API let you declare tables, tools, assistants, screens, and navigation entries. The runtime installs, loads, and hot-reloads your package across all devices.
|
|
14
|
+
|
|
15
|
+
### ORM
|
|
16
|
+
|
|
17
|
+
Table definitions with Zod schemas, data queries with a Mongo-style filter syntax, and async cursors. Tables sync across devices and encrypt automatically — you just define the schema.
|
|
18
|
+
|
|
19
|
+
### Observables and Persistent Variables
|
|
20
|
+
|
|
21
|
+
Lightweight reactive primitives (`observable()`, `computed()`) with `.subscribe()`. Persistent variables (`deviceVar`, `userVar`, `groupVar`) are observables backed by the database — write once, subscribe everywhere, synced across devices.
|
|
22
|
+
|
|
23
|
+
### Types and Schemas
|
|
24
|
+
|
|
25
|
+
Zod schemas and TypeScript types for the entire Peers data model: users, groups, devices, packages, tools, assistants, workflows, messages, channels, and more.
|
|
26
|
+
|
|
27
|
+
### Encryption
|
|
28
|
+
|
|
29
|
+
NaCl-based key management, message signing and verification, box encryption, and deterministic hashing. Identity is a cryptographic key pair — no passwords, no email.
|
|
30
|
+
|
|
31
|
+
### Tools Framework
|
|
32
|
+
|
|
33
|
+
Define AI-callable tools with `ITool` and `IToolInstance`. Tools are registered at runtime and available to built-in and user-defined AI assistants and workflows.
|
|
34
|
+
|
|
35
|
+
### Identity and Groups
|
|
36
|
+
|
|
37
|
+
User, group, and permission management types. Group-scoped data contexts for multi-tenant data isolation. Invite flows and trust levels.
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @peers-app/peers-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The fastest way to build a Peers package is to start from the template:
|
|
46
|
+
|
|
47
|
+
**[peers-package-template](https://github.com/peers-app/peers-package-template)** — scaffold, build, and install a custom package into the Peers runtime. This will be setup automatically when you create a new package in the desktop app.
|
|
48
|
+
|
|
49
|
+
## Key Interfaces
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
| Export | Purpose |
|
|
53
|
+
| ------------------------------------ | ----------------------------------------------------------------------- |
|
|
54
|
+
| `definePackage()` | Entry point for declaring a package's contracts, tools, tables, and nav |
|
|
55
|
+
| `Table` | ORM table with CRUD, Zod validation, and `dataChanged` events |
|
|
56
|
+
| `DataQuery` / `DataFilter` | Mongo-style query filters translated to SQL |
|
|
57
|
+
| `observable()` / `computed()` | Reactive state primitives |
|
|
58
|
+
| `deviceVar` / `userVar` / `groupVar` | Persistent variables — observables backed by the database |
|
|
59
|
+
| `ITool` / `IToolInstance` | AI-callable tool definitions |
|
|
60
|
+
| `IPeersUI` / `IPeersUIRoute` | Screen and route declarations for package UIs |
|
|
61
|
+
| `newid()` | Generate 25-character time-sortable peer IDs |
|
|
62
|
+
| `newKeys()` / `hydrateKeys()` | Cryptographic key pair generation and hydration |
|
|
63
|
+
| `UserContext` / `DataContext` | Multi-group data scoping and package loading |
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Links
|
|
67
|
+
|
|
68
|
+
- [peers.app](https://peers.app) — try Peers in your browser or download the desktop app
|
|
69
|
+
- [Documentation](https://peers-app.github.io) — architecture, package development, and API reference
|
|
70
|
+
- [GitHub](https://github.com/peers-app) — source repositories and package template
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -36,6 +36,13 @@ class FileReadStream {
|
|
|
36
36
|
else {
|
|
37
37
|
throw new Error(`File ${this.fileRecord.fileId} has neither chunkHashes nor indexFileId`);
|
|
38
38
|
}
|
|
39
|
+
if (!this.skipVerification) {
|
|
40
|
+
const computedFileHash = (0, keys_1.hashBytes)(new TextEncoder().encode(JSON.stringify(this.chunkHashes)));
|
|
41
|
+
if (computedFileHash !== this.fileRecord.fileHash) {
|
|
42
|
+
throw new Error(`File integrity check failed for ${this.fileRecord.fileId}: ` +
|
|
43
|
+
`chunk hashes produce ${computedFileHash}, expected ${this.fileRecord.fileHash}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
39
46
|
}
|
|
40
47
|
async loadChunkByIndex(chunkIndex) {
|
|
41
48
|
await this.loadChunkHashes();
|
|
@@ -35,6 +35,12 @@ export declare const FILE_CHUNK_SIZE: number;
|
|
|
35
35
|
export declare const CHUNKS_DIR = "file_chunks";
|
|
36
36
|
export declare let CHUNK_INDEX_THRESHOLD: number;
|
|
37
37
|
export declare function setChunkIndexThreshold(threshold: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* Compute the file-system hash for content without saving it.
|
|
40
|
+
* Mirrors the chunking + hashing logic of FileWriteStream so the result
|
|
41
|
+
* matches `IFile.fileHash` for the same bytes.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeFileHash(content: string | Uint8Array): string;
|
|
38
44
|
export interface FileOps {
|
|
39
45
|
downloadFileChunk(chunkHash: string): Promise<Uint8Array | null>;
|
|
40
46
|
fileExists(path: string): Promise<boolean>;
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CHUNK_INDEX_THRESHOLD = exports.CHUNKS_DIR = exports.FILE_CHUNK_SIZE = exports.filesMetaData = exports.fileSchema = void 0;
|
|
4
4
|
exports.setChunkIndexThreshold = setChunkIndexThreshold;
|
|
5
|
+
exports.computeFileHash = computeFileHash;
|
|
5
6
|
exports.setFileOps = setFileOps;
|
|
6
7
|
exports.getFileOps = getFileOps;
|
|
7
8
|
exports.resetFileOps = resetFileOps;
|
|
8
9
|
const zod_1 = require("zod");
|
|
10
|
+
const keys_1 = require("../../keys");
|
|
9
11
|
const zod_types_1 = require("../../types/zod-types");
|
|
10
12
|
const types_1 = require("../orm/types");
|
|
11
13
|
exports.fileSchema = zod_1.z.object({
|
|
@@ -38,6 +40,22 @@ exports.CHUNK_INDEX_THRESHOLD = 1000; // Use chunk index file for files with >10
|
|
|
38
40
|
function setChunkIndexThreshold(threshold) {
|
|
39
41
|
exports.CHUNK_INDEX_THRESHOLD = threshold;
|
|
40
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Compute the file-system hash for content without saving it.
|
|
45
|
+
* Mirrors the chunking + hashing logic of FileWriteStream so the result
|
|
46
|
+
* matches `IFile.fileHash` for the same bytes.
|
|
47
|
+
*/
|
|
48
|
+
function computeFileHash(content) {
|
|
49
|
+
const data = typeof content === "string" ? new Uint8Array(Buffer.from(content, "utf8")) : content;
|
|
50
|
+
const chunkHashes = [];
|
|
51
|
+
let offset = 0;
|
|
52
|
+
while (offset < data.length) {
|
|
53
|
+
const end = Math.min(offset + exports.FILE_CHUNK_SIZE, data.length);
|
|
54
|
+
chunkHashes.push((0, keys_1.hashBytes)(data.subarray(offset, end)));
|
|
55
|
+
offset = end;
|
|
56
|
+
}
|
|
57
|
+
return (0, keys_1.hashBytes)(new Uint8Array(Buffer.from(JSON.stringify(chunkHashes), "utf8")));
|
|
58
|
+
}
|
|
41
59
|
let fileOps = null;
|
|
42
60
|
let fileOpsReady = null;
|
|
43
61
|
function setFileOps(ops) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const keys_1 = require("../../keys");
|
|
3
4
|
const field_type_1 = require("../../types/field-type");
|
|
4
5
|
const utils_1 = require("../../utils");
|
|
5
6
|
const file_types_1 = require("./file.types");
|
|
@@ -201,7 +202,7 @@ describe("FileTable", () => {
|
|
|
201
202
|
const result = await fileTable.getFileContents("non-existent");
|
|
202
203
|
expect(result).toBeNull();
|
|
203
204
|
});
|
|
204
|
-
it("should
|
|
205
|
+
it("should throw integrity error when fileHash doesn't match chunk hashes", async () => {
|
|
205
206
|
const fileId = (0, utils_1.newid)();
|
|
206
207
|
const metadata = {
|
|
207
208
|
fileId,
|
|
@@ -210,10 +211,9 @@ describe("FileTable", () => {
|
|
|
210
211
|
fileHash: "hash123",
|
|
211
212
|
chunkHashes: ["hash1", "hash2"],
|
|
212
213
|
};
|
|
213
|
-
// Insert metadata directly
|
|
214
|
+
// Insert metadata directly with mismatched fileHash
|
|
214
215
|
await fileTable.dataSource.insert(metadata);
|
|
215
|
-
|
|
216
|
-
expect(result).toBeNull();
|
|
216
|
+
await expect(fileTable.getFileContents(fileId)).rejects.toThrow("File integrity check failed");
|
|
217
217
|
});
|
|
218
218
|
it("should return null when chunk is missing during read", async () => {
|
|
219
219
|
const result = await fileTable.getFileContents("non-existent");
|
|
@@ -674,20 +674,63 @@ describe("FileTable", () => {
|
|
|
674
674
|
});
|
|
675
675
|
it("should return null when chunk is unavailable", async () => {
|
|
676
676
|
const fileId = (0, utils_1.newid)();
|
|
677
|
+
const chunkHashes = ["missing_chunk_hash"];
|
|
678
|
+
const fileHash = (0, keys_1.hashBytes)(new TextEncoder().encode(JSON.stringify(chunkHashes)));
|
|
677
679
|
const metadata = {
|
|
678
680
|
fileId,
|
|
679
681
|
name: "missing-chunk.txt",
|
|
680
682
|
fileSize: 50,
|
|
681
|
-
fileHash
|
|
682
|
-
chunkHashes
|
|
683
|
+
fileHash,
|
|
684
|
+
chunkHashes,
|
|
683
685
|
};
|
|
684
686
|
// Insert file metadata without storing the actual chunk
|
|
685
687
|
await fileTable.dataSource.insert(metadata);
|
|
686
688
|
// This should return null when the chunk can't be found
|
|
687
|
-
// instead of throwing an error or hanging
|
|
688
689
|
const result = await fileTable.getFileContents(fileId);
|
|
689
690
|
expect(result).toBeNull();
|
|
690
691
|
});
|
|
692
|
+
it("should throw when fileHash does not match chunk hashes", async () => {
|
|
693
|
+
const fileId = (0, utils_1.newid)();
|
|
694
|
+
const data = new Uint8Array(Buffer.from("Valid content", "utf8"));
|
|
695
|
+
const metadata = {
|
|
696
|
+
fileId,
|
|
697
|
+
name: "tampered.txt",
|
|
698
|
+
fileSize: data.length,
|
|
699
|
+
mimeType: "text/plain",
|
|
700
|
+
};
|
|
701
|
+
// Save file normally to get valid chunks on disk
|
|
702
|
+
const saved = await fileTable.saveFile(metadata, data);
|
|
703
|
+
// Tamper with the fileHash in the database record
|
|
704
|
+
const tamperedRecord = {
|
|
705
|
+
...saved,
|
|
706
|
+
fileHash: "tampered-hash-value",
|
|
707
|
+
};
|
|
708
|
+
await fileTable.dataSource.save(tamperedRecord);
|
|
709
|
+
// Reading should throw a file integrity error
|
|
710
|
+
await expect(fileTable.getFileContents(fileId)).rejects.toThrow("File integrity check failed");
|
|
711
|
+
});
|
|
712
|
+
it("should skip fileHash verification when skipVerification is true", async () => {
|
|
713
|
+
const fileId = (0, utils_1.newid)();
|
|
714
|
+
const data = new Uint8Array(Buffer.from("Valid content", "utf8"));
|
|
715
|
+
const metadata = {
|
|
716
|
+
fileId,
|
|
717
|
+
name: "skip-verify.txt",
|
|
718
|
+
fileSize: data.length,
|
|
719
|
+
mimeType: "text/plain",
|
|
720
|
+
};
|
|
721
|
+
// Save file normally to get valid chunks on disk
|
|
722
|
+
const saved = await fileTable.saveFile(metadata, data);
|
|
723
|
+
// Tamper with the fileHash in the database record
|
|
724
|
+
const tamperedRecord = {
|
|
725
|
+
...saved,
|
|
726
|
+
fileHash: "tampered-hash-value",
|
|
727
|
+
};
|
|
728
|
+
await fileTable.dataSource.save(tamperedRecord);
|
|
729
|
+
// Reading with skipVerification should succeed
|
|
730
|
+
const result = await fileTable.getFileContents(fileId, { skipVerification: true });
|
|
731
|
+
expect(result).not.toBeNull();
|
|
732
|
+
expect(new Uint8Array(result)).toEqual(data);
|
|
733
|
+
});
|
|
691
734
|
});
|
|
692
735
|
describe("Round-trip streaming", () => {
|
|
693
736
|
it("should write and read back identical data using streams", async () => {
|
|
@@ -30,6 +30,7 @@ declare const schema: z.ZodObject<{
|
|
|
30
30
|
navigationPath: string;
|
|
31
31
|
displayName?: string | undefined;
|
|
32
32
|
}>, "many">>;
|
|
33
|
+
packageAuthorSignature: z.ZodOptional<z.ZodString>;
|
|
33
34
|
history: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
35
|
action: z.ZodString;
|
|
35
36
|
by: z.ZodString;
|
|
@@ -76,6 +77,7 @@ declare const schema: z.ZodObject<{
|
|
|
76
77
|
navigationPath: string;
|
|
77
78
|
displayName?: string | undefined;
|
|
78
79
|
}[] | undefined;
|
|
80
|
+
packageAuthorSignature?: string | undefined;
|
|
79
81
|
}, {
|
|
80
82
|
version: string;
|
|
81
83
|
signature: string;
|
|
@@ -103,6 +105,7 @@ declare const schema: z.ZodObject<{
|
|
|
103
105
|
navigationPath: string;
|
|
104
106
|
displayName?: string | undefined;
|
|
105
107
|
}[] | undefined;
|
|
108
|
+
packageAuthorSignature?: string | undefined;
|
|
106
109
|
}>;
|
|
107
110
|
export type IPackageVersion = z.infer<typeof schema>;
|
|
108
111
|
export declare class PackageVersionsTable extends Table<IPackageVersion> {
|
|
@@ -66,6 +66,11 @@ const schema = zod_1.z.object({
|
|
|
66
66
|
.array()
|
|
67
67
|
.optional()
|
|
68
68
|
.describe("The app navigation items that this version provides"),
|
|
69
|
+
packageAuthorSignature: zod_1.z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Publisher's Ed25519 detached signature over the author-signed payload. " +
|
|
73
|
+
"Enables cross-group trust verification without needing the original source."),
|
|
69
74
|
history: zod_1.z
|
|
70
75
|
.array(zod_1.z.object({
|
|
71
76
|
action: zod_1.z.string().describe("created | promoted:beta | promoted:stable | activated"),
|
package/dist/data/packages.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ declare const schema: z.ZodObject<{
|
|
|
29
29
|
activePackageVersionId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
30
30
|
versionFollowRange: z.ZodOptional<z.ZodEnum<["pinned", "patch", "minor", "latest"]>>;
|
|
31
31
|
followVersionTags: z.ZodOptional<z.ZodString>;
|
|
32
|
+
updateUrl: z.ZodOptional<z.ZodString>;
|
|
33
|
+
publishPublicKey: z.ZodString;
|
|
32
34
|
signature: z.ZodString;
|
|
33
35
|
}, "strip", z.ZodTypeAny, {
|
|
34
36
|
name: string;
|
|
@@ -36,6 +38,7 @@ declare const schema: z.ZodObject<{
|
|
|
36
38
|
signature: string;
|
|
37
39
|
packageId: string;
|
|
38
40
|
createdBy: string;
|
|
41
|
+
publishPublicKey: string;
|
|
39
42
|
disabled?: boolean | undefined;
|
|
40
43
|
appNavs?: {
|
|
41
44
|
name: string;
|
|
@@ -47,12 +50,14 @@ declare const schema: z.ZodObject<{
|
|
|
47
50
|
activePackageVersionId?: string | undefined;
|
|
48
51
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
49
52
|
followVersionTags?: string | undefined;
|
|
53
|
+
updateUrl?: string | undefined;
|
|
50
54
|
}, {
|
|
51
55
|
name: string;
|
|
52
56
|
description: string;
|
|
53
57
|
signature: string;
|
|
54
58
|
packageId: string;
|
|
55
59
|
createdBy: string;
|
|
60
|
+
publishPublicKey: string;
|
|
56
61
|
disabled?: boolean | undefined;
|
|
57
62
|
appNavs?: {
|
|
58
63
|
name: string;
|
|
@@ -64,6 +69,7 @@ declare const schema: z.ZodObject<{
|
|
|
64
69
|
activePackageVersionId?: string | undefined;
|
|
65
70
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
66
71
|
followVersionTags?: string | undefined;
|
|
72
|
+
updateUrl?: string | undefined;
|
|
67
73
|
}>;
|
|
68
74
|
export type IPackage = z.infer<typeof schema>;
|
|
69
75
|
export declare class PackagesTable extends Table<IPackage> {
|
package/dist/data/packages.js
CHANGED
|
@@ -72,6 +72,14 @@ const schema = zod_1.z.object({
|
|
|
72
72
|
.string()
|
|
73
73
|
.optional()
|
|
74
74
|
.describe('Tag policy: undefined="current" (follow active tag), "*"=any tag, or CSV like "stable,prod"'),
|
|
75
|
+
updateUrl: zod_1.z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Base URL for remote package seeding. Admin devices check <updateUrl>/latest-<tag>.json on startup."),
|
|
79
|
+
publishPublicKey: zod_1.z
|
|
80
|
+
.string()
|
|
81
|
+
.describe("Ed25519 public key (base64url) of the package publisher. " +
|
|
82
|
+
"Set at package creation; used as TOFU anchor for verifying packageAuthorSignature on versions."),
|
|
75
83
|
signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
|
|
76
84
|
});
|
|
77
85
|
const metaData = {
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from "./keys";
|
|
|
19
19
|
export * from "./logging";
|
|
20
20
|
export * from "./mentions";
|
|
21
21
|
export * from "./observable";
|
|
22
|
+
export * from "./package-installer";
|
|
22
23
|
export * from "./package-loader";
|
|
23
24
|
export * from "./peers-ui/peers-ui";
|
|
24
25
|
export * from "./peers-ui/peers-ui.types";
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ __exportStar(require("./keys"), exports);
|
|
|
40
40
|
__exportStar(require("./logging"), exports);
|
|
41
41
|
__exportStar(require("./mentions"), exports);
|
|
42
42
|
__exportStar(require("./observable"), exports);
|
|
43
|
+
__exportStar(require("./package-installer"), exports);
|
|
43
44
|
__exportStar(require("./package-loader"), exports);
|
|
44
45
|
__exportStar(require("./peers-ui/peers-ui"), exports);
|
|
45
46
|
__exportStar(require("./peers-ui/peers-ui.types"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./package-author-signing";
|
|
2
|
+
export * from "./package-cloner";
|
|
3
|
+
export * from "./package-creator";
|
|
4
|
+
export * from "./package-installer";
|
|
5
|
+
export * from "./package-propagation";
|
|
6
|
+
export * from "./package-publisher";
|
|
7
|
+
export * from "./package-remote-checker";
|
|
8
|
+
export * from "./package-seed-installer";
|
|
9
|
+
export * from "./package-tarball";
|
|
10
|
+
export * from "./types";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./package-author-signing"), exports);
|
|
18
|
+
__exportStar(require("./package-cloner"), exports);
|
|
19
|
+
__exportStar(require("./package-creator"), exports);
|
|
20
|
+
__exportStar(require("./package-installer"), exports);
|
|
21
|
+
__exportStar(require("./package-propagation"), exports);
|
|
22
|
+
__exportStar(require("./package-publisher"), exports);
|
|
23
|
+
__exportStar(require("./package-remote-checker"), exports);
|
|
24
|
+
__exportStar(require("./package-seed-installer"), exports);
|
|
25
|
+
__exportStar(require("./package-tarball"), exports);
|
|
26
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { IPackageVersion } from "../data/package-versions";
|
|
2
|
+
/** Prefix for the persistent variable name that stores a package's signing secret key. */
|
|
3
|
+
export declare const PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
|
|
4
|
+
/**
|
|
5
|
+
* The subset of PackageVersion fields that the publisher signs.
|
|
6
|
+
* The `publicKey` field is included so verifiers know which key to check against.
|
|
7
|
+
*/
|
|
8
|
+
export interface IAuthorSignedPayload {
|
|
9
|
+
packageId: string;
|
|
10
|
+
packageVersionId: string;
|
|
11
|
+
version: string;
|
|
12
|
+
versionTag: string;
|
|
13
|
+
packageBundleFileHash: string;
|
|
14
|
+
routesBundleFileHash?: string;
|
|
15
|
+
uiBundleFileHash?: string;
|
|
16
|
+
publicKey: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extracts the signable payload from a PackageVersion record.
|
|
20
|
+
* Only includes optional hash fields when they have a value (undefined fields are omitted
|
|
21
|
+
* entirely so `stableStringify` produces a consistent output).
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildAuthorSignedPayload(pv: Pick<IPackageVersion, "packageId" | "packageVersionId" | "version" | "versionTag" | "packageBundleFileHash" | "routesBundleFileHash" | "uiBundleFileHash">, publicKey: string): IAuthorSignedPayload;
|
|
24
|
+
/**
|
|
25
|
+
* Signs a PackageVersion with the publisher's Ed25519 secret key.
|
|
26
|
+
* @returns A base64url-encoded detached Ed25519 signature over `stableStringify(payload)`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function signPackageAuthor(pv: Pick<IPackageVersion, "packageId" | "packageVersionId" | "version" | "versionTag" | "packageBundleFileHash" | "routesBundleFileHash" | "uiBundleFileHash">, secretKey: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Result of verifying a packageAuthorSignature.
|
|
31
|
+
* `publicKey` is the key embedded in the payload (extracted from the signature verification process).
|
|
32
|
+
*/
|
|
33
|
+
export interface IVerifyAuthorSignatureResult {
|
|
34
|
+
valid: boolean;
|
|
35
|
+
publicKey: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Verifies the `packageAuthorSignature` on a PackageVersion record.
|
|
39
|
+
*
|
|
40
|
+
* The signature format is a raw base64url detached Ed25519 signature. The public key
|
|
41
|
+
* is NOT in the signature itself — it must be provided via `expectedPublicKey` (TOFU check)
|
|
42
|
+
* to reconstruct the payload and verify.
|
|
43
|
+
*
|
|
44
|
+
* @param pv - The PackageVersion record with `packageAuthorSignature` set
|
|
45
|
+
* @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
|
|
46
|
+
* @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
|
|
47
|
+
*/
|
|
48
|
+
export declare function verifyPackageAuthorSignature(pv: Pick<IPackageVersion, "packageId" | "packageVersionId" | "version" | "versionTag" | "packageBundleFileHash" | "routesBundleFileHash" | "uiBundleFileHash" | "packageAuthorSignature">, expectedPublicKey: string): IVerifyAuthorSignatureResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PACKAGE_SIGNING_KEY_PREFIX = void 0;
|
|
4
|
+
exports.buildAuthorSignedPayload = buildAuthorSignedPayload;
|
|
5
|
+
exports.signPackageAuthor = signPackageAuthor;
|
|
6
|
+
exports.verifyPackageAuthorSignature = verifyPackageAuthorSignature;
|
|
7
|
+
const nacl = require("tweetnacl");
|
|
8
|
+
const tweetnacl_util_1 = require("tweetnacl-util");
|
|
9
|
+
const keys_1 = require("../keys");
|
|
10
|
+
/** Prefix for the persistent variable name that stores a package's signing secret key. */
|
|
11
|
+
exports.PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the signable payload from a PackageVersion record.
|
|
14
|
+
* Only includes optional hash fields when they have a value (undefined fields are omitted
|
|
15
|
+
* entirely so `stableStringify` produces a consistent output).
|
|
16
|
+
*/
|
|
17
|
+
function buildAuthorSignedPayload(pv, publicKey) {
|
|
18
|
+
const payload = {
|
|
19
|
+
packageId: pv.packageId,
|
|
20
|
+
packageVersionId: pv.packageVersionId,
|
|
21
|
+
version: pv.version,
|
|
22
|
+
versionTag: pv.versionTag ?? "",
|
|
23
|
+
packageBundleFileHash: pv.packageBundleFileHash,
|
|
24
|
+
publicKey,
|
|
25
|
+
};
|
|
26
|
+
if (pv.routesBundleFileHash) {
|
|
27
|
+
payload.routesBundleFileHash = pv.routesBundleFileHash;
|
|
28
|
+
}
|
|
29
|
+
if (pv.uiBundleFileHash) {
|
|
30
|
+
payload.uiBundleFileHash = pv.uiBundleFileHash;
|
|
31
|
+
}
|
|
32
|
+
return payload;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Signs a PackageVersion with the publisher's Ed25519 secret key.
|
|
36
|
+
* @returns A base64url-encoded detached Ed25519 signature over `stableStringify(payload)`.
|
|
37
|
+
*/
|
|
38
|
+
function signPackageAuthor(pv, secretKey) {
|
|
39
|
+
const keys = (0, keys_1.hydrateKeys)(secretKey);
|
|
40
|
+
const payload = buildAuthorSignedPayload(pv, keys.publicKey);
|
|
41
|
+
const message = (0, tweetnacl_util_1.decodeUTF8)((0, keys_1.stableStringify)(payload));
|
|
42
|
+
const sk = (0, keys_1.decodeBase64)(secretKey);
|
|
43
|
+
const signature = nacl.sign.detached(message, sk);
|
|
44
|
+
return (0, keys_1.encodeBase64)(signature);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Verifies the `packageAuthorSignature` on a PackageVersion record.
|
|
48
|
+
*
|
|
49
|
+
* The signature format is a raw base64url detached Ed25519 signature. The public key
|
|
50
|
+
* is NOT in the signature itself — it must be provided via `expectedPublicKey` (TOFU check)
|
|
51
|
+
* to reconstruct the payload and verify.
|
|
52
|
+
*
|
|
53
|
+
* @param pv - The PackageVersion record with `packageAuthorSignature` set
|
|
54
|
+
* @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
|
|
55
|
+
* @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
|
|
56
|
+
*/
|
|
57
|
+
function verifyPackageAuthorSignature(pv, expectedPublicKey) {
|
|
58
|
+
const result = { valid: false, publicKey: expectedPublicKey };
|
|
59
|
+
if (!pv.packageAuthorSignature) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const payload = buildAuthorSignedPayload(pv, expectedPublicKey);
|
|
64
|
+
const message = (0, tweetnacl_util_1.decodeUTF8)((0, keys_1.stableStringify)(payload));
|
|
65
|
+
const signature = (0, keys_1.decodeBase64)(pv.packageAuthorSignature);
|
|
66
|
+
const pk = (0, keys_1.decodeBase64)(expectedPublicKey);
|
|
67
|
+
result.valid = nacl.sign.detached.verify(message, signature, pk);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
result.valid = false;
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|