@peers-app/peers-sdk 0.26.4 → 0.26.6
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/dist/contracts/__tests__/contract-resolvers.test.js +24 -0
- package/dist/contracts/__tests__/legacy-removal.test.js +38 -0
- package/dist/contracts/__tests__/remote-contract-transport.test.js +23 -32
- package/dist/contracts/__tests__/system-contracts.test.js +80 -0
- package/dist/contracts/contract-resolvers.d.ts +1 -1
- package/dist/contracts/contract-resolvers.js +3 -2
- package/dist/contracts/create-local-consumer.d.ts +2 -1
- package/dist/contracts/create-local-consumer.js +1 -1
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/remote-contract-transport.d.ts +3 -3
- package/dist/contracts/remote-contract-transport.js +19 -17
- package/dist/contracts/system/host-shell-contract.d.ts +191 -0
- package/dist/contracts/system/host-shell-contract.js +190 -0
- package/dist/contracts/system/index.d.ts +4 -0
- package/dist/contracts/system/index.js +4 -0
- package/dist/contracts/system/packages-contract.d.ts +257 -0
- package/dist/contracts/system/packages-contract.js +213 -0
- package/dist/data/create-group.d.ts +1 -1
- package/dist/data/create-group.js +1 -1
- package/dist/data/group-members.d.ts +6 -1
- package/dist/data/group-members.js +12 -4
- package/dist/data/groups.d.ts +6 -1
- package/dist/data/groups.js +12 -4
- package/dist/data/package-version-resolver.d.ts +8 -2
- package/dist/data/package-version-resolver.js +28 -2
- package/dist/data/package-version-resolver.test.js +42 -0
- package/dist/data/package-versions.d.ts +5 -1
- package/dist/data/package-versions.js +11 -4
- package/dist/data/packages.d.ts +7 -1
- package/dist/data/packages.js +13 -4
- package/dist/data/table-signing.test.d.ts +1 -0
- package/dist/data/table-signing.test.js +118 -0
- package/dist/data/users.d.ts +1 -2
- package/dist/data/users.js +8 -5
- package/dist/package-installer/package-author-signer.d.ts +19 -0
- package/dist/package-installer/package-author-signer.js +24 -0
- package/dist/package-installer/package-creator.d.ts +1 -7
- package/dist/package-installer/package-creator.js +0 -12
- package/dist/package-installer/package-creator.test.js +0 -16
- package/dist/package-installer/package-installer.d.ts +5 -0
- package/dist/package-installer/package-installer.js +60 -37
- package/dist/package-installer/package-installer.test.js +129 -0
- package/dist/package-installer/types.d.ts +5 -11
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-runtime.d.ts +18 -0
- package/dist/package-loader/package-loader.d.ts +67 -4
- package/dist/package-loader/package-loader.js +300 -52
- package/dist/package-loader/package-loader.test.js +413 -1
- package/dist/package-loader/package-runtime-state.d.ts +112 -0
- package/dist/package-loader/package-runtime-state.js +18 -0
- package/dist/package-loader/resolve-consumed-contracts.js +5 -4
- package/dist/package-loader/resolve-consumed-contracts.test.js +1 -1
- package/dist/rpc-types.d.ts +0 -121
- package/dist/rpc-types.js +0 -39
- package/dist/system-ids.d.ts +13 -0
- package/dist/system-ids.js +14 -1
- package/package.json +1 -1
|
@@ -92,6 +92,10 @@ const metaData = {
|
|
|
92
92
|
fields: (0, types_1.schemaToFields)(schema),
|
|
93
93
|
indexes: [{ fields: ["packageId"] }],
|
|
94
94
|
};
|
|
95
|
+
// Symbol.for keeps signer registration shared by separately installed SDK copies.
|
|
96
|
+
const signingKey = Symbol.for("@peers-app/peers-sdk/package-version-signing");
|
|
97
|
+
const signingGlobals = globalThis;
|
|
98
|
+
/** Package version table with signature and group promotion permission checks. */
|
|
95
99
|
let PackageVersionsTable = (() => {
|
|
96
100
|
let _classSuper = table_1.Table;
|
|
97
101
|
let _instanceExtraInitializers = [];
|
|
@@ -122,15 +126,16 @@ let PackageVersionsTable = (() => {
|
|
|
122
126
|
return super.save(packageVersion, opts);
|
|
123
127
|
}
|
|
124
128
|
async signAndSave(packageVersion, opts) {
|
|
125
|
-
|
|
129
|
+
const sign = signingGlobals[signingKey];
|
|
130
|
+
if (!sign) {
|
|
126
131
|
throw new Error("Package version signing is not enabled. Call PackageVersionsTable.enablePackageVersionSigning(fn) to enable it.");
|
|
127
132
|
}
|
|
128
|
-
packageVersion = await
|
|
133
|
+
packageVersion = await sign.call(PackageVersionsTable, packageVersion);
|
|
129
134
|
return this.save(packageVersion, opts);
|
|
130
135
|
}
|
|
131
|
-
|
|
136
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
132
137
|
static enablePackageVersionSigning(fn) {
|
|
133
|
-
|
|
138
|
+
signingGlobals[signingKey] = fn;
|
|
134
139
|
}
|
|
135
140
|
constructor() {
|
|
136
141
|
super(...arguments);
|
|
@@ -140,6 +145,7 @@ let PackageVersionsTable = (() => {
|
|
|
140
145
|
})();
|
|
141
146
|
exports.PackageVersionsTable = PackageVersionsTable;
|
|
142
147
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, schema, PackageVersionsTable);
|
|
148
|
+
/** Returns the PackageVersions table for the given context or the current default context. */
|
|
143
149
|
function PackageVersions(dataContext) {
|
|
144
150
|
return (0, context_1.getTableContainer)(dataContext).getTable(metaData, schema, PackageVersionsTable);
|
|
145
151
|
}
|
|
@@ -161,6 +167,7 @@ function computePackageVersionHash(version,
|
|
|
161
167
|
_versionTag, packageBundleFileHash, routesBundleFileHash, uiBundleFileHash) {
|
|
162
168
|
return (0, keys_1.hashValue)([version, packageBundleFileHash, routesBundleFileHash ?? "", uiBundleFileHash ?? ""].join(":"));
|
|
163
169
|
}
|
|
170
|
+
/** Checks whether an incoming version falls within the selected automatic update range. */
|
|
164
171
|
function isVersionInRange(activeVersion, incomingVersion, range) {
|
|
165
172
|
if (range === "pinned")
|
|
166
173
|
return false;
|
package/dist/data/packages.d.ts
CHANGED
|
@@ -42,19 +42,25 @@ declare const schema: z.ZodObject<{
|
|
|
42
42
|
followVersionTags?: string | undefined;
|
|
43
43
|
updateUrl?: string | undefined;
|
|
44
44
|
}>;
|
|
45
|
+
/** A signed package record with publisher identity and version selection policy. */
|
|
45
46
|
export type IPackage = z.infer<typeof schema>;
|
|
47
|
+
/** Package table with signature enforcement outside personal contexts. */
|
|
46
48
|
export declare class PackagesTable extends Table<IPackage> {
|
|
47
49
|
static isPassthrough: boolean;
|
|
48
50
|
insert(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
|
|
49
51
|
update(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
|
|
50
52
|
save(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
|
|
51
53
|
signAndSave(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
|
|
52
|
-
|
|
54
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
53
55
|
static enablePackageSigning(fn: (packageObj: IPackage) => IPackage): void;
|
|
54
56
|
delete(packageId: string): Promise<void>;
|
|
55
57
|
}
|
|
58
|
+
/** Returns the Packages table for the given context or the current default context. */
|
|
56
59
|
export declare function Packages(dataContext?: DataContext): PackagesTable;
|
|
60
|
+
/** Default directory for locally stored package files. */
|
|
57
61
|
export declare const packagesRootDir = "~/peers/packages";
|
|
62
|
+
/** Device preference controlling package reloads when the UI refreshes. */
|
|
58
63
|
export declare const reloadPackagesOnPageRefresh: import("./persistent-vars").PersistentVar<boolean>;
|
|
64
|
+
/** Device preference enabling automatic peers-core updates. */
|
|
59
65
|
export declare const autoUpdatePeersCore: import("./persistent-vars").PersistentVar<boolean>;
|
|
60
66
|
export {};
|
package/dist/data/packages.js
CHANGED
|
@@ -83,6 +83,10 @@ const metaData = {
|
|
|
83
83
|
iconClassName: "bi bi-box-fill",
|
|
84
84
|
fields: (0, types_1.schemaToFields)(schema),
|
|
85
85
|
};
|
|
86
|
+
// Symbol.for keeps signer registration shared by separately installed SDK copies.
|
|
87
|
+
const signingKey = Symbol.for("@peers-app/peers-sdk/package-signing");
|
|
88
|
+
const signingGlobals = globalThis;
|
|
89
|
+
/** Package table with signature enforcement outside personal contexts. */
|
|
86
90
|
let PackagesTable = (() => {
|
|
87
91
|
let _classSuper = table_1.Table;
|
|
88
92
|
let _instanceExtraInitializers = [];
|
|
@@ -113,15 +117,16 @@ let PackagesTable = (() => {
|
|
|
113
117
|
return super.save(packageObj, opts);
|
|
114
118
|
}
|
|
115
119
|
async signAndSave(packageObj, opts) {
|
|
116
|
-
|
|
120
|
+
const sign = signingGlobals[signingKey];
|
|
121
|
+
if (!sign) {
|
|
117
122
|
throw new Error("Package signing is not enabled. Call PackagesTable.enablePackageSigning(fn) to enable it.");
|
|
118
123
|
}
|
|
119
|
-
packageObj = await
|
|
124
|
+
packageObj = await sign.call(PackagesTable, packageObj);
|
|
120
125
|
return this.save(packageObj, opts);
|
|
121
126
|
}
|
|
122
|
-
|
|
127
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
123
128
|
static enablePackageSigning(fn) {
|
|
124
|
-
|
|
129
|
+
signingGlobals[signingKey] = fn;
|
|
125
130
|
}
|
|
126
131
|
async delete(packageId) {
|
|
127
132
|
// anyone (with write permissions) can delete packages since that isn't a security risk (unless something else is relying on a package to be present?)
|
|
@@ -141,11 +146,15 @@ let PackagesTable = (() => {
|
|
|
141
146
|
})();
|
|
142
147
|
exports.PackagesTable = PackagesTable;
|
|
143
148
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, schema, PackagesTable);
|
|
149
|
+
/** Returns the Packages table for the given context or the current default context. */
|
|
144
150
|
function Packages(dataContext) {
|
|
145
151
|
return (0, context_1.getTableContainer)(dataContext).getTable(metaData, schema, PackagesTable);
|
|
146
152
|
}
|
|
153
|
+
/** Default directory for locally stored package files. */
|
|
147
154
|
exports.packagesRootDir = "~/peers/packages";
|
|
155
|
+
/** Device preference controlling package reloads when the UI refreshes. */
|
|
148
156
|
exports.reloadPackagesOnPageRefresh = (0, persistent_vars_1.deviceVar)("reloadPackagesOnPageRefresh", {
|
|
149
157
|
defaultValue: true,
|
|
150
158
|
});
|
|
159
|
+
/** Device preference enabling automatic peers-core updates. */
|
|
151
160
|
exports.autoUpdatePeersCore = (0, persistent_vars_1.deviceVar)("autoUpdatePeersCore", { defaultValue: true });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tableNames = ["package-version", "package", "group", "group-member", "user"];
|
|
4
|
+
// These key strings are the contract between installed SDK versions; do not rename them.
|
|
5
|
+
const signingKey = (name) => Symbol.for(`@peers-app/peers-sdk/${name}-signing`);
|
|
6
|
+
// Restore process-global registrations so these tests never leave test signers installed.
|
|
7
|
+
const priorSigners = tableNames.map((name) => {
|
|
8
|
+
const key = signingKey(name);
|
|
9
|
+
return { key, descriptor: Object.getOwnPropertyDescriptor(globalThis, key) };
|
|
10
|
+
});
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
for (const { key } of priorSigners) {
|
|
13
|
+
Reflect.deleteProperty(globalThis, key);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
for (const { key, descriptor } of priorSigners) {
|
|
18
|
+
if (descriptor) {
|
|
19
|
+
Object.defineProperty(globalThis, key, descriptor);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
Reflect.deleteProperty(globalThis, key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
function loadSigningTables() {
|
|
27
|
+
let tables;
|
|
28
|
+
jest.isolateModules(() => {
|
|
29
|
+
const { PackageVersionsTable } = require("./package-versions");
|
|
30
|
+
const { PackagesTable } = require("./packages");
|
|
31
|
+
const { GroupsTable } = require("./groups");
|
|
32
|
+
const { GroupMembersTable } = require("./group-members");
|
|
33
|
+
const { UsersTable } = require("./users");
|
|
34
|
+
tables = {
|
|
35
|
+
"package-version": {
|
|
36
|
+
prototype: PackageVersionsTable.prototype,
|
|
37
|
+
enable: PackageVersionsTable.enablePackageVersionSigning,
|
|
38
|
+
},
|
|
39
|
+
package: { prototype: PackagesTable.prototype, enable: PackagesTable.enablePackageSigning },
|
|
40
|
+
group: { prototype: GroupsTable.prototype, enable: GroupsTable.enableGroupSigning },
|
|
41
|
+
"group-member": {
|
|
42
|
+
prototype: GroupMembersTable.prototype,
|
|
43
|
+
enable: GroupMembersTable.enableGroupMemberSigning,
|
|
44
|
+
},
|
|
45
|
+
user: { prototype: UsersTable.prototype, enable: UsersTable.enableUserSigning },
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
return tables;
|
|
49
|
+
}
|
|
50
|
+
describe("table signing across SDK module instances", () => {
|
|
51
|
+
it.each(tableNames)("shares and replaces the %s signer across copies", async (name) => {
|
|
52
|
+
const first = loadSigningTables()[name];
|
|
53
|
+
const second = loadSigningTables()[name];
|
|
54
|
+
expect(first.prototype).not.toBe(second.prototype);
|
|
55
|
+
// Stub persistence only: exercise the real signAndSave and registration methods.
|
|
56
|
+
const receiver = { save: jest.fn(async (record) => record) };
|
|
57
|
+
const row = { name: "Example", signature: "" };
|
|
58
|
+
const opts = { saveAsSnapshot: false };
|
|
59
|
+
await expect(second.prototype.signAndSave.call(receiver, row, opts)).rejects.toThrow(/signing.*enabled/i);
|
|
60
|
+
expect(receiver.save).not.toHaveBeenCalled();
|
|
61
|
+
const sign = jest.fn((record) => ({ ...record, signature: "first" }));
|
|
62
|
+
first.enable(sign);
|
|
63
|
+
expect(Reflect.get(globalThis, signingKey(name))).toBe(sign);
|
|
64
|
+
await expect(second.prototype.signAndSave.call(receiver, row, opts)).resolves.toEqual({
|
|
65
|
+
...row,
|
|
66
|
+
signature: "first",
|
|
67
|
+
});
|
|
68
|
+
expect(sign).toHaveBeenCalledWith(row);
|
|
69
|
+
expect(sign.mock.contexts.at(-1)).toBe(second.prototype.constructor);
|
|
70
|
+
expect(receiver.save).toHaveBeenLastCalledWith({ ...row, signature: "first" }, name === "group-member" ? { ...opts, restoreIfDeleted: true } : opts);
|
|
71
|
+
// Loading another copy must preserve a signer that is already registered.
|
|
72
|
+
const third = loadSigningTables()[name];
|
|
73
|
+
expect(third.prototype).not.toBe(first.prototype);
|
|
74
|
+
await expect(third.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
|
|
75
|
+
...row,
|
|
76
|
+
signature: "first",
|
|
77
|
+
});
|
|
78
|
+
expect(sign.mock.contexts.at(-1)).toBe(third.prototype.constructor);
|
|
79
|
+
second.enable((record) => ({ ...record, signature: "replacement" }));
|
|
80
|
+
for (const copy of [first, third]) {
|
|
81
|
+
await expect(copy.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
|
|
82
|
+
...row,
|
|
83
|
+
signature: "replacement",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
receiver.save.mockClear();
|
|
87
|
+
const failsOnce = jest
|
|
88
|
+
.fn((record) => ({ ...record, signature: "recovered" }))
|
|
89
|
+
.mockImplementationOnce(() => {
|
|
90
|
+
throw new Error("signing failed");
|
|
91
|
+
});
|
|
92
|
+
first.enable(failsOnce);
|
|
93
|
+
await expect(second.prototype.signAndSave.call(receiver, row)).rejects.toThrow("signing failed");
|
|
94
|
+
expect(receiver.save).not.toHaveBeenCalled();
|
|
95
|
+
await expect(third.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
|
|
96
|
+
...row,
|
|
97
|
+
signature: "recovered",
|
|
98
|
+
});
|
|
99
|
+
expect(failsOnce).toHaveBeenCalledTimes(2);
|
|
100
|
+
receiver.save.mockRejectedValueOnce(new Error("save failed"));
|
|
101
|
+
await expect(second.prototype.signAndSave.call(receiver, row)).rejects.toThrow("save failed");
|
|
102
|
+
await expect(first.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
|
|
103
|
+
...row,
|
|
104
|
+
signature: "recovered",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it("keeps the five signer registrations independent", async () => {
|
|
108
|
+
const first = loadSigningTables();
|
|
109
|
+
const second = loadSigningTables();
|
|
110
|
+
for (const name of tableNames) {
|
|
111
|
+
first[name].enable((record) => ({ ...record, signature: name }));
|
|
112
|
+
}
|
|
113
|
+
const receiver = { save: jest.fn(async (record) => record) };
|
|
114
|
+
for (const name of tableNames) {
|
|
115
|
+
await expect(second[name].prototype.signAndSave.call(receiver, { name: "Example", signature: "" })).resolves.toEqual({ name: "Example", signature: name });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
package/dist/data/users.d.ts
CHANGED
|
@@ -84,8 +84,7 @@ export declare class UsersTable extends Table<IUser> {
|
|
|
84
84
|
*/
|
|
85
85
|
save(user: IUser, opts?: ISaveOptions): Promise<IUser>;
|
|
86
86
|
signAndSave(user: IUser, opts?: ISaveOptions): Promise<IUser>;
|
|
87
|
-
|
|
88
|
-
/** Enables signing for proxied `signAndSave` calls. */
|
|
87
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
89
88
|
static enableUserSigning(fn: (user: IUser) => IUser): void;
|
|
90
89
|
/** @deprecated Forbidden on UsersTable; use save() */
|
|
91
90
|
insert(..._args: Parameters<Table<IUser>["insert"]>): never;
|
package/dist/data/users.js
CHANGED
|
@@ -102,6 +102,9 @@ let userRowChangeGuard = refuseIdentityChanges;
|
|
|
102
102
|
function setUserRowChangeGuard(guard) {
|
|
103
103
|
userRowChangeGuard = guard ?? refuseIdentityChanges;
|
|
104
104
|
}
|
|
105
|
+
// Symbol.for keeps signer registration shared by separately installed SDK copies.
|
|
106
|
+
const signingKey = Symbol.for("@peers-app/peers-sdk/user-signing");
|
|
107
|
+
const signingGlobals = globalThis;
|
|
105
108
|
/** User table with owner-signature enforcement outside the personal context. */
|
|
106
109
|
let UsersTable = (() => {
|
|
107
110
|
let _classSuper = table_1.Table;
|
|
@@ -170,20 +173,20 @@ let UsersTable = (() => {
|
|
|
170
173
|
return super.save(verdict.row, opts);
|
|
171
174
|
}
|
|
172
175
|
async signAndSave(user, opts) {
|
|
173
|
-
|
|
176
|
+
const sign = signingGlobals[signingKey];
|
|
177
|
+
if (!sign) {
|
|
174
178
|
throw new Error("User signing must be enabled to sign and save users. Call UsersTable.enableUserSigning(fn) to enable it.");
|
|
175
179
|
}
|
|
176
180
|
user = {
|
|
177
181
|
...user,
|
|
178
182
|
name: user.name.slice(0, devices_1.DISPLAY_NAME_MAX_LENGTH),
|
|
179
183
|
};
|
|
180
|
-
user =
|
|
184
|
+
user = sign.call(UsersTable, user);
|
|
181
185
|
return this.save(user, opts);
|
|
182
186
|
}
|
|
183
|
-
|
|
184
|
-
/** Enables signing for proxied `signAndSave` calls. */
|
|
187
|
+
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
185
188
|
static enableUserSigning(fn) {
|
|
186
|
-
|
|
189
|
+
signingGlobals[signingKey] = fn;
|
|
187
190
|
}
|
|
188
191
|
/** @deprecated Forbidden on UsersTable; use save() */
|
|
189
192
|
insert(..._args) {
|
|
@@ -23,3 +23,22 @@ export declare function loadPackageSigningKey(packageId: string, getUserSecretKe
|
|
|
23
23
|
* @param getUserSecretKey - Platform callback that returns the signed-in user's secret key
|
|
24
24
|
*/
|
|
25
25
|
export declare function trySignPackageAuthorVersion(pvFields: AuthorSignableFields, getUserSecretKey: () => Promise<string>): Promise<string | undefined>;
|
|
26
|
+
/** Host-installed signer for PackageVersion author signatures. */
|
|
27
|
+
export type PackageAuthorVersionSigner = (pvFields: AuthorSignableFields) => Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Install (or clear) the host's PackageVersion author signer.
|
|
30
|
+
*
|
|
31
|
+
* `peers-device` installs one at startup over the signed-in user's secret key, so Runtime
|
|
32
|
+
* code (the installer, System Packages) can re-sign versions without a host RPC.
|
|
33
|
+
*
|
|
34
|
+
* @param signer - Signer to use, or `undefined` to clear it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function setPackageAuthorVersionSigner(signer: PackageAuthorVersionSigner | undefined): void;
|
|
37
|
+
/**
|
|
38
|
+
* Sign PackageVersion fields with the installed host signer.
|
|
39
|
+
*
|
|
40
|
+
* @param pvFields - Author-signable PackageVersion fields.
|
|
41
|
+
* @returns The detached signature, or `undefined` when no signer is installed, the package
|
|
42
|
+
* signing key is unavailable, or signing fails.
|
|
43
|
+
*/
|
|
44
|
+
export declare function signPackageAuthorVersionOnHost(pvFields: AuthorSignableFields): Promise<string | undefined>;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.loadPackageSigningKey = loadPackageSigningKey;
|
|
4
4
|
exports.trySignPackageAuthorVersion = trySignPackageAuthorVersion;
|
|
5
|
+
exports.setPackageAuthorVersionSigner = setPackageAuthorVersionSigner;
|
|
6
|
+
exports.signPackageAuthorVersionOnHost = signPackageAuthorVersionOnHost;
|
|
5
7
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
6
8
|
const persistent_vars_1 = require("../data/persistent-vars");
|
|
7
9
|
const keys_1 = require("../keys");
|
|
@@ -55,3 +57,25 @@ async function trySignPackageAuthorVersion(pvFields, getUserSecretKey) {
|
|
|
55
57
|
return undefined;
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
let installedSigner;
|
|
61
|
+
/**
|
|
62
|
+
* Install (or clear) the host's PackageVersion author signer.
|
|
63
|
+
*
|
|
64
|
+
* `peers-device` installs one at startup over the signed-in user's secret key, so Runtime
|
|
65
|
+
* code (the installer, System Packages) can re-sign versions without a host RPC.
|
|
66
|
+
*
|
|
67
|
+
* @param signer - Signer to use, or `undefined` to clear it.
|
|
68
|
+
*/
|
|
69
|
+
function setPackageAuthorVersionSigner(signer) {
|
|
70
|
+
installedSigner = signer;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sign PackageVersion fields with the installed host signer.
|
|
74
|
+
*
|
|
75
|
+
* @param pvFields - Author-signable PackageVersion fields.
|
|
76
|
+
* @returns The detached signature, or `undefined` when no signer is installed, the package
|
|
77
|
+
* signing key is unavailable, or signing fails.
|
|
78
|
+
*/
|
|
79
|
+
async function signPackageAuthorVersionOnHost(pvFields) {
|
|
80
|
+
return installedSigner?.(pvFields);
|
|
81
|
+
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { DataContext } from "../context/data-context";
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
* Normalize `addOrUpdatePackage` RPC results.
|
|
5
|
-
* Older hosts return a bare packageId string; current hosts return
|
|
6
|
-
* {@link IAddOrUpdatePackageResult}.
|
|
7
|
-
*/
|
|
8
|
-
export declare function unwrapAddOrUpdatePackageResult(result: string | IAddOrUpdatePackageResult): IAddOrUpdatePackageResult;
|
|
2
|
+
import type { ICreatePackageOpts, ICreatePackageResult, IPackageInstallerDeps, ITemplateCacheResult, TemplateCacheRefreshFailure } from "./types";
|
|
9
3
|
/**
|
|
10
4
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
11
5
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.unwrapAddOrUpdatePackageResult = unwrapAddOrUpdatePackageResult;
|
|
4
3
|
exports.nameToFolderSlug = nameToFolderSlug;
|
|
5
4
|
exports.classifyTemplatePullFailure = classifyTemplatePullFailure;
|
|
6
5
|
exports.ensureTemplateCache = ensureTemplateCache;
|
|
@@ -24,17 +23,6 @@ function patchTemplateTokens(contents, values) {
|
|
|
24
23
|
.replaceAll("<app-screen-id>", values.appScreenId)
|
|
25
24
|
.replaceAll("<contract-id>", values.contractId);
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Normalize `addOrUpdatePackage` RPC results.
|
|
29
|
-
* Older hosts return a bare packageId string; current hosts return
|
|
30
|
-
* {@link IAddOrUpdatePackageResult}.
|
|
31
|
-
*/
|
|
32
|
-
function unwrapAddOrUpdatePackageResult(result) {
|
|
33
|
-
if (typeof result === "string") {
|
|
34
|
-
return { packageId: result };
|
|
35
|
-
}
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
26
|
/**
|
|
39
27
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
40
28
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
@@ -87,22 +87,6 @@ beforeEach(() => {
|
|
|
87
87
|
mockNewKeys.mockReturnValue(generatedKeys);
|
|
88
88
|
mockPersistentVarsSave.mockReset();
|
|
89
89
|
});
|
|
90
|
-
describe("unwrapAddOrUpdatePackageResult", () => {
|
|
91
|
-
it("accepts a legacy packageId string", () => {
|
|
92
|
-
expect((0, package_creator_1.unwrapAddOrUpdatePackageResult)("00mnkoyo7mizav6tseubjzr3a")).toEqual({
|
|
93
|
-
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
it("passes through a structured result including a template warning", () => {
|
|
97
|
-
expect((0, package_creator_1.unwrapAddOrUpdatePackageResult)({
|
|
98
|
-
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
99
|
-
warning: "stale template",
|
|
100
|
-
})).toEqual({
|
|
101
|
-
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
102
|
-
warning: "stale template",
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
90
|
describe("nameToFolderSlug", () => {
|
|
107
91
|
it("converts a simple name", () => {
|
|
108
92
|
expect((0, package_creator_1.nameToFolderSlug)("My Widget")).toBe("my-widget");
|
|
@@ -8,6 +8,11 @@ import type { IInstallOpts, IInstallResult, IPackageInfo, IPackageInstallerDeps
|
|
|
8
8
|
* bundles from `dist/`, hashes them for deduplication, saves File records
|
|
9
9
|
* only when content has changed, upserts the PackageVersion, and optionally
|
|
10
10
|
* triggers the package loader and device version resolution.
|
|
11
|
+
*
|
|
12
|
+
* When the bundle hash matches the existing PackageVersion, no files or PV are
|
|
13
|
+
* rewritten. The Packages row still gets a missing `activePackageVersionId`
|
|
14
|
+
* (and description), and when the id was missing the version is activated on
|
|
15
|
+
* this device unless `skipResolve`.
|
|
11
16
|
*/
|
|
12
17
|
export declare function installPackageFromBundles(dataContext: DataContext, deps: IPackageInstallerDeps, packageId: string, opts?: IInstallOpts): Promise<IInstallResult>;
|
|
13
18
|
/**
|
|
@@ -12,8 +12,8 @@ const package_version_resolver_1 = require("../data/package-version-resolver");
|
|
|
12
12
|
const package_versions_1 = require("../data/package-versions");
|
|
13
13
|
const packages_1 = require("../data/packages");
|
|
14
14
|
const persistent_vars_1 = require("../data/persistent-vars");
|
|
15
|
-
const rpc_types_1 = require("../rpc-types");
|
|
16
15
|
const utils_1 = require("../utils");
|
|
16
|
+
const package_author_signer_1 = require("./package-author-signer");
|
|
17
17
|
/**
|
|
18
18
|
* Install or update a dev PackageVersion from locally-built bundle files.
|
|
19
19
|
*
|
|
@@ -21,6 +21,11 @@ const utils_1 = require("../utils");
|
|
|
21
21
|
* bundles from `dist/`, hashes them for deduplication, saves File records
|
|
22
22
|
* only when content has changed, upserts the PackageVersion, and optionally
|
|
23
23
|
* triggers the package loader and device version resolution.
|
|
24
|
+
*
|
|
25
|
+
* When the bundle hash matches the existing PackageVersion, no files or PV are
|
|
26
|
+
* rewritten. The Packages row still gets a missing `activePackageVersionId`
|
|
27
|
+
* (and description), and when the id was missing the version is activated on
|
|
28
|
+
* this device unless `skipResolve`.
|
|
24
29
|
*/
|
|
25
30
|
async function installPackageFromBundles(dataContext, deps, packageId, opts) {
|
|
26
31
|
const versionTag = opts?.versionTag ?? "dev";
|
|
@@ -53,23 +58,24 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
|
|
|
53
58
|
const uiHash = uiBundle ? (0, file_types_1.computeFileHash)(uiBundle) : undefined;
|
|
54
59
|
// 5. Compute PV hash
|
|
55
60
|
const pvHash = (0, package_versions_1.computePackageVersionHash)(version, "", packageHash, routesHash, uiHash);
|
|
56
|
-
// 6. Early exit if unchanged
|
|
61
|
+
// 6. Early exit if unchanged. Still repair missing appNavs (see syncDevBundles
|
|
62
|
+
// skipLoad), and still fill an empty activePackageVersionId (e.g. reimport after
|
|
63
|
+
// the Packages row was deleted). If it was empty, activate on the device unless
|
|
64
|
+
// skipResolve.
|
|
57
65
|
const pvTable = (0, package_versions_1.PackageVersions)(dataContext);
|
|
58
66
|
const existingPv = await pvTable.findOne({ packageId, versionTag });
|
|
59
67
|
if (existingPv && existingPv.packageVersionHash === pvHash) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
68
|
+
let loaded;
|
|
69
|
+
let unchanged = true;
|
|
70
|
+
if (!existingPv.appNavs?.length) {
|
|
71
|
+
loaded = await loadAndPersistAppNavs(dataContext, packageId, existingPv, localPath);
|
|
72
|
+
unchanged = !loaded?.appNavs?.length;
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
74
|
+
await activateInstalledPackageVersion(dataContext, packageId, existingPv, info, localPath, {
|
|
75
|
+
skipResolve: opts?.skipResolve,
|
|
76
|
+
resolveOnlyWhenActivated: true,
|
|
77
|
+
});
|
|
78
|
+
return { packageVersion: existingPv, loaded, unchanged };
|
|
73
79
|
}
|
|
74
80
|
// 7. Save bundle files (deduped by hash)
|
|
75
81
|
const packageFile = await saveBundleFile(dataContext, packageBundle, `${packageId}-package.bundle.js`, packageId);
|
|
@@ -110,31 +116,48 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
|
|
|
110
116
|
if (!opts?.skipLoad || !savedPv.appNavs?.length) {
|
|
111
117
|
loaded = await loadAndPersistAppNavs(dataContext, packageId, savedPv, localPath);
|
|
112
118
|
}
|
|
113
|
-
// 10. Update package record
|
|
119
|
+
// 10–11. Update package record and activate on device (unless skipResolve)
|
|
120
|
+
await activateInstalledPackageVersion(dataContext, packageId, savedPv, info, localPath, {
|
|
121
|
+
skipResolve: opts?.skipResolve,
|
|
122
|
+
});
|
|
123
|
+
return { packageVersion: savedPv, loaded, unchanged: false };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Sync the Packages row with an installed PackageVersion, then activate it on
|
|
127
|
+
* this device.
|
|
128
|
+
*
|
|
129
|
+
* Updates the description when it differs, and sets `activePackageVersionId`
|
|
130
|
+
* when it is empty (first install, or reimport after the row was deleted).
|
|
131
|
+
* Unless `skipResolve`, writes device prefs and force-resolves the version.
|
|
132
|
+
* With `resolveOnlyWhenActivated`, the device step runs only when this call
|
|
133
|
+
* filled an empty `activePackageVersionId`.
|
|
134
|
+
*/
|
|
135
|
+
async function activateInstalledPackageVersion(dataContext, packageId, packageVersion, info, localPath, opts) {
|
|
136
|
+
// Update package record if needed (description, first-install activation)
|
|
114
137
|
const pkg = await (0, packages_1.Packages)(dataContext).get(packageId);
|
|
115
|
-
if (pkg)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!pkg.activePackageVersionId) {
|
|
122
|
-
pkg.activePackageVersionId = savedPv.packageVersionId;
|
|
123
|
-
needsSave = true;
|
|
124
|
-
}
|
|
125
|
-
if (needsSave) {
|
|
126
|
-
await (0, packages_1.Packages)(dataContext).signAndSave(pkg);
|
|
127
|
-
}
|
|
138
|
+
if (!pkg)
|
|
139
|
+
return;
|
|
140
|
+
let needsSave = false;
|
|
141
|
+
if (info.description && info.description !== pkg.description) {
|
|
142
|
+
pkg.description = info.description;
|
|
143
|
+
needsSave = true;
|
|
128
144
|
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
force: true,
|
|
134
|
-
localPath,
|
|
135
|
-
});
|
|
145
|
+
const activated = !pkg.activePackageVersionId;
|
|
146
|
+
if (activated) {
|
|
147
|
+
pkg.activePackageVersionId = packageVersion.packageVersionId;
|
|
148
|
+
needsSave = true;
|
|
136
149
|
}
|
|
137
|
-
|
|
150
|
+
if (needsSave) {
|
|
151
|
+
await (0, packages_1.Packages)(dataContext).signAndSave(pkg);
|
|
152
|
+
}
|
|
153
|
+
// Activate on device
|
|
154
|
+
if (opts.skipResolve || (opts.resolveOnlyWhenActivated && !activated))
|
|
155
|
+
return;
|
|
156
|
+
await (0, package_version_resolver_1.updatePackagePrefs)(packageId, { activePackageVersionId: packageVersion.packageVersionId }, dataContext);
|
|
157
|
+
await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dataContext, {
|
|
158
|
+
force: true,
|
|
159
|
+
localPath,
|
|
160
|
+
});
|
|
138
161
|
}
|
|
139
162
|
/**
|
|
140
163
|
* Load a package bundle and persist `appNavs` onto the PackageVersion when the
|
|
@@ -159,7 +182,7 @@ async function loadAndPersistAppNavs(dataContext, packageId, pv, localPath) {
|
|
|
159
182
|
return loaded;
|
|
160
183
|
pv.appNavs = loaded.appNavs;
|
|
161
184
|
if (pv.packageAuthorSignature) {
|
|
162
|
-
const authorSig = await
|
|
185
|
+
const authorSig = await (0, package_author_signer_1.signPackageAuthorVersionOnHost)({
|
|
163
186
|
packageId: pv.packageId,
|
|
164
187
|
packageVersionId: pv.packageVersionId,
|
|
165
188
|
version: pv.version,
|