@peers-app/peers-sdk 0.19.14 → 0.20.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/dist/contracts/__tests__/builder.test.js +24 -0
- package/dist/contracts/__tests__/contract-events.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-events.test.js +199 -0
- package/dist/contracts/__tests__/contract-observables.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-observables.test.js +290 -0
- package/dist/contracts/__tests__/contract-provider-router.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-provider-router.test.js +193 -0
- package/dist/contracts/__tests__/contract-proxy.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-proxy.test.js +424 -0
- package/dist/contracts/__tests__/contract-resolvers.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +148 -0
- package/dist/contracts/__tests__/contract-subscription-lifecycle.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-subscription-lifecycle.test.js +353 -0
- package/dist/contracts/__tests__/contract-table-events.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-table-events.test.js +209 -0
- package/dist/contracts/__tests__/contract-transport.test.d.ts +1 -0
- package/dist/contracts/__tests__/contract-transport.test.js +163 -0
- package/dist/contracts/__tests__/persistent-registry.test.d.ts +1 -0
- package/dist/contracts/__tests__/persistent-registry.test.js +109 -0
- package/dist/contracts/__tests__/playground.test.d.ts +1 -0
- package/dist/contracts/__tests__/playground.test.js +213 -0
- package/dist/contracts/__tests__/validate.test.js +122 -0
- package/dist/contracts/builder.d.ts +9 -1
- package/dist/contracts/builder.js +13 -0
- package/dist/contracts/contract-provider-router.d.ts +72 -0
- package/dist/contracts/contract-provider-router.js +164 -0
- package/dist/contracts/contract-proxy.d.ts +333 -0
- package/dist/contracts/contract-proxy.js +663 -0
- package/dist/contracts/contract-resolvers.d.ts +66 -0
- package/dist/contracts/contract-resolvers.js +138 -0
- package/dist/contracts/contract-transport.d.ts +94 -0
- package/dist/contracts/contract-transport.js +159 -0
- package/dist/contracts/contracts.table.d.ts +1 -1
- package/dist/contracts/contracts.table.js +2 -2
- package/dist/contracts/index.d.ts +6 -1
- package/dist/contracts/index.js +25 -1
- package/dist/contracts/persistent-registry.js +3 -1
- package/dist/contracts/system/logs-contract.d.ts +24 -0
- package/dist/contracts/system/logs-contract.js +60 -0
- package/dist/contracts/types.d.ts +27 -4
- package/dist/contracts/validate.d.ts +2 -2
- package/dist/contracts/validate.js +44 -2
- package/dist/data/orm/decorators.d.ts +6 -0
- package/dist/data/orm/decorators.js +21 -0
- package/dist/data/user-trust-levels.d.ts +2 -0
- package/dist/data/user-trust-levels.js +2 -1
- package/dist/data/user-trust-levels.test.d.ts +1 -0
- package/dist/data/user-trust-levels.test.js +37 -0
- package/dist/device/binary-peer-connection-v2.d.ts +2 -0
- package/dist/device/binary-peer-connection-v2.js +12 -2
- package/dist/device/binary-peer-connection-v2.test.js +23 -0
- package/dist/device/get-trust-level-fn.d.ts +6 -0
- package/dist/device/get-trust-level-fn.js +12 -7
- package/dist/device/get-trust-level-fn.test.js +16 -2
- package/dist/logging/console-logs.table.d.ts +3 -0
- package/dist/logging/console-logs.table.js +2 -1
- package/dist/system-ids.d.ts +11 -0
- package/dist/system-ids.js +12 -1
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LOGS_CONTRACT_VERSION = void 0;
|
|
4
|
+
exports.getLogsContractDefinition = getLogsContractDefinition;
|
|
5
|
+
exports.resolveLogsContract = resolveLogsContract;
|
|
6
|
+
const user_context_singleton_1 = require("../../context/user-context-singleton");
|
|
7
|
+
const types_1 = require("../../data/orm/types");
|
|
8
|
+
const console_logs_table_1 = require("../../logging/console-logs.table");
|
|
9
|
+
const system_ids_1 = require("../../system-ids");
|
|
10
|
+
const contract_resolvers_1 = require("../contract-resolvers");
|
|
11
|
+
/** Version of the built-in System Logs contract. */
|
|
12
|
+
exports.LOGS_CONTRACT_VERSION = 1;
|
|
13
|
+
let _definition;
|
|
14
|
+
/**
|
|
15
|
+
* The first built-in **system contract**: read and write the local device's console logs.
|
|
16
|
+
*
|
|
17
|
+
* It declares only the `ConsoleLogs` table — the table's own methods (`list`, `count`,
|
|
18
|
+
* `deleteOldLogs`, …) are the entire surface we need, so no bespoke tools are required.
|
|
19
|
+
* The table shape is derived from {@link consoleLogSchema} so the contract stays accurate.
|
|
20
|
+
*
|
|
21
|
+
* Built lazily and memoized: reading `consoleLogsMetaData` / `consoleLogSchema` at module
|
|
22
|
+
* load would race the import cycle between the logging table and the SDK barrel, so we defer
|
|
23
|
+
* construction until first use (well after all modules have finished initializing).
|
|
24
|
+
*/
|
|
25
|
+
function getLogsContractDefinition() {
|
|
26
|
+
if (!_definition) {
|
|
27
|
+
_definition = {
|
|
28
|
+
contractId: system_ids_1.logsContractId,
|
|
29
|
+
version: exports.LOGS_CONTRACT_VERSION,
|
|
30
|
+
devTag: "dev",
|
|
31
|
+
name: "System Logs",
|
|
32
|
+
description: "Read and write the local device's console logs.",
|
|
33
|
+
tables: [
|
|
34
|
+
{
|
|
35
|
+
name: console_logs_table_1.consoleLogsMetaData.name,
|
|
36
|
+
description: console_logs_table_1.consoleLogsMetaData.description,
|
|
37
|
+
primaryKeyName: console_logs_table_1.consoleLogsMetaData.primaryKeyName,
|
|
38
|
+
fields: (0, types_1.schemaToFields)(console_logs_table_1.consoleLogSchema),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
tools: [],
|
|
42
|
+
observables: [],
|
|
43
|
+
events: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return _definition;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the logs contract to its live implementation for a data context: the
|
|
50
|
+
* `ConsoleLogs` table backing that context.
|
|
51
|
+
*/
|
|
52
|
+
function resolveLogsContract(ctx) {
|
|
53
|
+
const consoleLogs = (0, user_context_singleton_1.getTableContainer)(ctx.dataContext).getTable(console_logs_table_1.consoleLogsMetaData, console_logs_table_1.consoleLogSchema, console_logs_table_1.ConsoleLogsTable);
|
|
54
|
+
return {
|
|
55
|
+
contractId: system_ids_1.logsContractId,
|
|
56
|
+
version: exports.LOGS_CONTRACT_VERSION,
|
|
57
|
+
tables: { [console_logs_table_1.consoleLogsMetaData.name]: consoleLogs },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
(0, contract_resolvers_1.registerContractResolver)(system_ids_1.logsContractId, exports.LOGS_CONTRACT_VERSION, resolveLogsContract);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Table } from "../data/orm/table";
|
|
2
2
|
import type { ITableDefinition } from "../data/orm/table-definitions.type";
|
|
3
3
|
import type { IToolInstance } from "../data/tools";
|
|
4
|
+
import type { Event } from "../events";
|
|
4
5
|
import type { Observable } from "../observable";
|
|
5
6
|
import type { FieldType, IField } from "../types/field-type";
|
|
6
7
|
/**
|
|
7
|
-
* A versioned interface definition declaring the shape of tables, tools,
|
|
8
|
-
* observables that a package provides. Pure data — no executable code.
|
|
8
|
+
* A versioned interface definition declaring the shape of tables, tools,
|
|
9
|
+
* observables, and events that a package provides. Pure data — no executable code.
|
|
9
10
|
*/
|
|
10
11
|
export interface IContractDefinition {
|
|
11
12
|
contractId: string;
|
|
@@ -17,6 +18,12 @@ export interface IContractDefinition {
|
|
|
17
18
|
tables: IContractTable[];
|
|
18
19
|
tools: IContractTool[];
|
|
19
20
|
observables: IContractObservable[];
|
|
21
|
+
/**
|
|
22
|
+
* Events a provider may emit and consumers may subscribe to. Optional so
|
|
23
|
+
* existing contract definitions stay valid; the {@link ContractBuilder} always
|
|
24
|
+
* emits an array (possibly empty) and consumers read `contract.events ?? []`.
|
|
25
|
+
*/
|
|
26
|
+
events?: IContractEvent[];
|
|
20
27
|
}
|
|
21
28
|
/** Table shape within a contract — derived from ITableMetaData + IField[]. */
|
|
22
29
|
export interface IContractTable {
|
|
@@ -39,6 +46,18 @@ export interface IContractObservable {
|
|
|
39
46
|
valueType: FieldType;
|
|
40
47
|
writable: boolean;
|
|
41
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* A generic event a contract provider may emit. Consumers subscribe by name; the
|
|
51
|
+
* provider only forwards payloads over the transport while at least one consumer
|
|
52
|
+
* has an active subscription (see the provider session). Pure shape — the live
|
|
53
|
+
* {@link Event} is supplied at resolution time in {@link IContractResolution.events}.
|
|
54
|
+
*/
|
|
55
|
+
export interface IContractEvent {
|
|
56
|
+
name: string;
|
|
57
|
+
description: string;
|
|
58
|
+
/** Shape of the payload delivered to subscribers when the event fires. */
|
|
59
|
+
payloadFields: IField[];
|
|
60
|
+
}
|
|
42
61
|
/**
|
|
43
62
|
* A reference to a contract that a package wants to consume.
|
|
44
63
|
* Recorded during `definePackage` and checked at install time.
|
|
@@ -46,6 +65,7 @@ export interface IContractObservable {
|
|
|
46
65
|
export interface IConsumedContract {
|
|
47
66
|
contractId: string;
|
|
48
67
|
version: number;
|
|
68
|
+
optional?: boolean;
|
|
49
69
|
}
|
|
50
70
|
/**
|
|
51
71
|
* Declares that the owning contract's implementation also satisfies another
|
|
@@ -86,15 +106,18 @@ export interface IResolvedContract {
|
|
|
86
106
|
tables: Record<string, Table<any>>;
|
|
87
107
|
tools: Record<string, IToolInstance>;
|
|
88
108
|
observables: Record<string, Observable<any>>;
|
|
109
|
+
/** Live events the provider forwards from, keyed by contract event name. */
|
|
110
|
+
events?: Record<string, Event<any>>;
|
|
89
111
|
}
|
|
90
112
|
/** Outcome of a validation check. */
|
|
91
113
|
export interface IValidationResult {
|
|
92
114
|
valid: boolean;
|
|
93
115
|
errors: IValidationError[];
|
|
94
116
|
}
|
|
117
|
+
/** One incompatibility found while validating a contract definition or provider. */
|
|
95
118
|
export interface IValidationError {
|
|
96
|
-
/** Which element failed
|
|
97
|
-
kind: "table" | "tool" | "observable" | "contract" | "field";
|
|
119
|
+
/** Which contract element failed validation. */
|
|
120
|
+
kind: "table" | "tool" | "observable" | "event" | "contract" | "field";
|
|
98
121
|
/** Name of the element (table name, tool name, etc.) */
|
|
99
122
|
name: string;
|
|
100
123
|
message: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { IAlsoImplementsDeclaration, IContractDefinition, IValidationResult } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Validate that an implementation contract satisfies a target contract shape.
|
|
4
|
-
* The implementation must be a superset: all contract tables, tools,
|
|
5
|
-
* observables must be present with compatible shapes.
|
|
4
|
+
* The implementation must be a superset: all contract tables, tools,
|
|
5
|
+
* observables, and events must be present with compatible shapes.
|
|
6
6
|
*/
|
|
7
7
|
export declare function validateProviderSatisfiesContract(impl: IContractDefinition, contract: IContractDefinition): IValidationResult;
|
|
8
8
|
/**
|
|
@@ -114,10 +114,36 @@ function validateToolFields(contractTool, implTool, direction) {
|
|
|
114
114
|
}
|
|
115
115
|
return errors;
|
|
116
116
|
}
|
|
117
|
+
function validateEventFields(contractEvent, implEvent) {
|
|
118
|
+
const errors = [];
|
|
119
|
+
const implFieldMap = new Map(implEvent.payloadFields.map((field) => [field.name, field]));
|
|
120
|
+
for (const contractField of contractEvent.payloadFields) {
|
|
121
|
+
const implField = implFieldMap.get(contractField.name);
|
|
122
|
+
if (!implField) {
|
|
123
|
+
if (!contractField.optional) {
|
|
124
|
+
errors.push({
|
|
125
|
+
kind: "field",
|
|
126
|
+
name: `${contractEvent.name}.payload.${contractField.name}`,
|
|
127
|
+
message: `Required payload field '${contractField.name}' missing from implementation event '${implEvent.name}'`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const compat = isFieldCompatible(contractField, implField);
|
|
133
|
+
if (compat) {
|
|
134
|
+
errors.push({
|
|
135
|
+
kind: "field",
|
|
136
|
+
name: `${contractEvent.name}.payload.${contractField.name}`,
|
|
137
|
+
message: `Payload field '${contractField.name}' in event '${implEvent.name}': ${compat}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return errors;
|
|
142
|
+
}
|
|
117
143
|
/**
|
|
118
144
|
* Validate that an implementation contract satisfies a target contract shape.
|
|
119
|
-
* The implementation must be a superset: all contract tables, tools,
|
|
120
|
-
* observables must be present with compatible shapes.
|
|
145
|
+
* The implementation must be a superset: all contract tables, tools,
|
|
146
|
+
* observables, and events must be present with compatible shapes.
|
|
121
147
|
*/
|
|
122
148
|
function validateProviderSatisfiesContract(impl, contract) {
|
|
123
149
|
const errors = [];
|
|
@@ -184,6 +210,20 @@ function validateProviderSatisfiesContract(impl, contract) {
|
|
|
184
210
|
});
|
|
185
211
|
}
|
|
186
212
|
}
|
|
213
|
+
// Events
|
|
214
|
+
const implEventMap = new Map((impl.events ?? []).map((event) => [event.name, event]));
|
|
215
|
+
for (const contractEvent of contract.events ?? []) {
|
|
216
|
+
const implEvent = implEventMap.get(contractEvent.name);
|
|
217
|
+
if (!implEvent) {
|
|
218
|
+
errors.push({
|
|
219
|
+
kind: "event",
|
|
220
|
+
name: contractEvent.name,
|
|
221
|
+
message: `Event '${contractEvent.name}' required by contract but not provided by implementation`,
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
errors.push(...validateEventFields(contractEvent, implEvent));
|
|
226
|
+
}
|
|
187
227
|
return errors.length === 0 ? ok() : fail(errors);
|
|
188
228
|
}
|
|
189
229
|
/**
|
|
@@ -212,11 +252,13 @@ function validateImmutability(existing, incoming) {
|
|
|
212
252
|
tables: existing.tables,
|
|
213
253
|
tools: existing.tools,
|
|
214
254
|
observables: existing.observables,
|
|
255
|
+
events: existing.events ?? [],
|
|
215
256
|
});
|
|
216
257
|
const incomingShape = JSON.stringify({
|
|
217
258
|
tables: incoming.tables,
|
|
218
259
|
tools: incoming.tools,
|
|
219
260
|
observables: incoming.observables,
|
|
261
|
+
events: incoming.events ?? [],
|
|
220
262
|
});
|
|
221
263
|
if (existingShape !== incomingShape) {
|
|
222
264
|
return fail([
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return whether a table method was explicitly marked for remote invocation by
|
|
3
|
+
* {@link ProxyClientTableMethodCalls}. Client proxies are recognized through their
|
|
4
|
+
* marked `__original` implementation as well.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isContractRemoteTableMethod(method: unknown): boolean;
|
|
1
7
|
/**
|
|
2
8
|
* Decorator that marks a table method as server-side only.
|
|
3
9
|
* When used on the client side, these methods will be automatically proxied
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isContractRemoteTableMethod = isContractRemoteTableMethod;
|
|
3
4
|
exports.ProxyClientTableMethodCalls = ProxyClientTableMethodCalls;
|
|
4
5
|
const rpc_types_1 = require("../../rpc-types");
|
|
6
|
+
const contractRemoteTableMethod = Symbol("contractRemoteTableMethod");
|
|
7
|
+
function markContractRemoteTableMethod(method) {
|
|
8
|
+
Object.defineProperty(method, contractRemoteTableMethod, { value: true });
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return whether a table method was explicitly marked for remote invocation by
|
|
12
|
+
* {@link ProxyClientTableMethodCalls}. Client proxies are recognized through their
|
|
13
|
+
* marked `__original` implementation as well.
|
|
14
|
+
*/
|
|
15
|
+
function isContractRemoteTableMethod(method) {
|
|
16
|
+
if (typeof method !== "function") {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const candidate = method;
|
|
20
|
+
return Boolean(candidate[contractRemoteTableMethod] || candidate.__original?.[contractRemoteTableMethod]);
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* Decorator that marks a table method as server-side only.
|
|
7
24
|
* When used on the client side, these methods will be automatically proxied
|
|
@@ -13,6 +30,9 @@ const rpc_types_1 = require("../../rpc-types");
|
|
|
13
30
|
*/
|
|
14
31
|
function ProxyClientTableMethodCalls() {
|
|
15
32
|
return (target, context) => {
|
|
33
|
+
if (context.kind === "method") {
|
|
34
|
+
markContractRemoteTableMethod(target);
|
|
35
|
+
}
|
|
16
36
|
if (context.kind === "method" && rpc_types_1.isClient) {
|
|
17
37
|
const methodName = context.name;
|
|
18
38
|
// WARNING: Using typescript's magical `this` param
|
|
@@ -21,6 +41,7 @@ function ProxyClientTableMethodCalls() {
|
|
|
21
41
|
return rpc_types_1.rpcServerCalls.tableMethodCall(dataContextId, this.tableName, methodName, ...args);
|
|
22
42
|
};
|
|
23
43
|
proxyFn.__original = target;
|
|
44
|
+
markContractRemoteTableMethod(proxyFn);
|
|
24
45
|
return proxyFn;
|
|
25
46
|
}
|
|
26
47
|
return target;
|
|
@@ -24,7 +24,9 @@ export declare const userTrustLevelSchema: z.ZodObject<{
|
|
|
24
24
|
assignedAt?: Date | undefined;
|
|
25
25
|
notes?: string | undefined;
|
|
26
26
|
}>;
|
|
27
|
+
/** A trust assignment for one user within a specific data context. */
|
|
27
28
|
export type IUserTrustLevel = z.infer<typeof userTrustLevelSchema>;
|
|
29
|
+
/** Returns the context-scoped table containing explicit user trust assignments. */
|
|
28
30
|
export declare function UserTrustLevels(dataContext?: DataContext): import("./orm").Table<{
|
|
29
31
|
userId: string;
|
|
30
32
|
trustLevel: TrustLevel;
|
|
@@ -33,6 +33,7 @@ const metaData = {
|
|
|
33
33
|
indexes: [{ fields: ["trustLevel"] }],
|
|
34
34
|
};
|
|
35
35
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.userTrustLevelSchema);
|
|
36
|
+
/** Returns the context-scoped table containing explicit user trust assignments. */
|
|
36
37
|
function UserTrustLevels(dataContext) {
|
|
37
38
|
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.userTrustLevelSchema);
|
|
38
39
|
}
|
|
@@ -41,7 +42,7 @@ function UserTrustLevels(dataContext) {
|
|
|
41
42
|
* Returns TrustLevel.Unknown if no trust level has been assigned.
|
|
42
43
|
*/
|
|
43
44
|
async function getUserTrustLevel(dataContext, userId) {
|
|
44
|
-
const userContext =
|
|
45
|
+
const userContext = dataContext.userContext;
|
|
45
46
|
if (dataContext === userContext.userDataContext && userContext.userId === userId) {
|
|
46
47
|
return socket_type_1.TrustLevel.Self;
|
|
47
48
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const socket_type_1 = require("../device/socket.type");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const user_trust_levels_1 = require("./user-trust-levels");
|
|
6
|
+
function makeDataContext(userId, trustRecord) {
|
|
7
|
+
const get = jest.fn().mockResolvedValue(trustRecord);
|
|
8
|
+
const userContext = { userId };
|
|
9
|
+
const dataContext = {
|
|
10
|
+
userContext,
|
|
11
|
+
tableContainer: {
|
|
12
|
+
getTable: jest.fn().mockReturnValue({ get }),
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
Object.assign(userContext, { userDataContext: dataContext });
|
|
16
|
+
return { dataContext, get };
|
|
17
|
+
}
|
|
18
|
+
describe("getUserTrustLevel", () => {
|
|
19
|
+
it("derives Self from the supplied personal context without a global user context", async () => {
|
|
20
|
+
const userId = (0, utils_1.newid)();
|
|
21
|
+
const { dataContext, get } = makeDataContext(userId);
|
|
22
|
+
await expect((0, user_trust_levels_1.getUserTrustLevel)(dataContext, userId)).resolves.toBe(socket_type_1.TrustLevel.Self);
|
|
23
|
+
expect(get).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
it("reads explicit trust from the supplied context", async () => {
|
|
26
|
+
const remoteUserId = (0, utils_1.newid)();
|
|
27
|
+
const { dataContext, get } = makeDataContext((0, utils_1.newid)(), {
|
|
28
|
+
trustLevel: socket_type_1.TrustLevel.Trusted,
|
|
29
|
+
});
|
|
30
|
+
await expect((0, user_trust_levels_1.getUserTrustLevel)(dataContext, remoteUserId)).resolves.toBe(socket_type_1.TrustLevel.Trusted);
|
|
31
|
+
expect(get).toHaveBeenCalledWith(remoteUserId);
|
|
32
|
+
});
|
|
33
|
+
it("returns Unknown when the supplied context has no trust assignment", async () => {
|
|
34
|
+
const { dataContext } = makeDataContext((0, utils_1.newid)());
|
|
35
|
+
await expect((0, user_trust_levels_1.getUserTrustLevel)(dataContext, (0, utils_1.newid)())).resolves.toBe(socket_type_1.TrustLevel.Unknown);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -35,7 +35,9 @@ export interface IBinaryPeer {
|
|
|
35
35
|
*/
|
|
36
36
|
getBufferedAmount?(): number;
|
|
37
37
|
}
|
|
38
|
+
/** Backwards-compatible name for a WebRTC binary peer. */
|
|
38
39
|
export type IWebRTCPeer = IBinaryPeer;
|
|
40
|
+
/** Options for wrapping a binary transport in a verified Peers connection. */
|
|
39
41
|
export interface WrapBinaryPeerOptions {
|
|
40
42
|
/** Protocol identifier used for server address (e.g., 'wrtc' or 'ws') */
|
|
41
43
|
protocol: "wrtc" | "ws";
|
|
@@ -324,9 +324,19 @@ function createBinaryPeerSocket(connectionId, peer, protocol = "binary") {
|
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
326
326
|
if (message.type === "call") {
|
|
327
|
-
const
|
|
327
|
+
const eventName = message.eventName;
|
|
328
|
+
const handler = eventName ? handlers[eventName] : undefined;
|
|
328
329
|
if (!handler) {
|
|
329
|
-
|
|
330
|
+
const error = eventName
|
|
331
|
+
? `No handler registered for event: ${eventName}`
|
|
332
|
+
: "RPC call did not include an event name";
|
|
333
|
+
console.warn(error);
|
|
334
|
+
const responseMessage = {
|
|
335
|
+
type: "callback",
|
|
336
|
+
callbackId: message.callbackId,
|
|
337
|
+
error,
|
|
338
|
+
};
|
|
339
|
+
sendRPC(encodeMessage(responseMessage));
|
|
330
340
|
return;
|
|
331
341
|
}
|
|
332
342
|
try {
|
|
@@ -117,6 +117,29 @@ describe("binary-peer-connection-v2", () => {
|
|
|
117
117
|
iPeer.destroy();
|
|
118
118
|
rPeer.destroy();
|
|
119
119
|
});
|
|
120
|
+
it("should return an error when the remote event has no handler", async () => {
|
|
121
|
+
const { iPeer, rPeer } = await getMockPeerPair();
|
|
122
|
+
const socketA = (0, binary_peer_connection_v2_1.createBinaryPeerSocket)("a", iPeer, "ws");
|
|
123
|
+
(0, binary_peer_connection_v2_1.createBinaryPeerSocket)("b", rPeer, "ws");
|
|
124
|
+
const warning = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
125
|
+
try {
|
|
126
|
+
const response = await new Promise((resolve) => {
|
|
127
|
+
socketA.emit("missing-event", {}, (error, result) => {
|
|
128
|
+
resolve({ error, result });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
expect(response).toEqual({
|
|
132
|
+
error: "No handler registered for event: missing-event",
|
|
133
|
+
result: undefined,
|
|
134
|
+
});
|
|
135
|
+
expect(warning).toHaveBeenCalledWith("No handler registered for event: missing-event");
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
warning.mockRestore();
|
|
139
|
+
iPeer.destroy();
|
|
140
|
+
rPeer.destroy();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
120
143
|
it("should handle Uint8Array args without txEncode", async () => {
|
|
121
144
|
const { iPeer, rPeer } = await getMockPeerPair();
|
|
122
145
|
const socketA = (0, binary_peer_connection_v2_1.createBinaryPeerSocket)("a", iPeer, "ws");
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import { type IDeviceInfo } from "../data";
|
|
2
2
|
import { TrustLevel } from "./socket.type";
|
|
3
|
+
/**
|
|
4
|
+
* Build the connection-handshake trust evaluator for a local user identity.
|
|
5
|
+
*
|
|
6
|
+
* Known user keys are authoritative even for a newly observed device: a claimed identity
|
|
7
|
+
* whose signing or box key differs from the stored user record is rejected as untrusted.
|
|
8
|
+
*/
|
|
3
9
|
export declare function getTrustLevelFn(me: Pick<IDeviceInfo, "userId" | "publicKey" | "publicBoxKey">, serverUrl?: string): (deviceInfo: IDeviceInfo, _registerNew?: boolean) => Promise<TrustLevel>;
|
|
@@ -4,6 +4,12 @@ exports.getTrustLevelFn = getTrustLevelFn;
|
|
|
4
4
|
const context_1 = require("../context");
|
|
5
5
|
const data_1 = require("../data");
|
|
6
6
|
const socket_type_1 = require("./socket.type");
|
|
7
|
+
/**
|
|
8
|
+
* Build the connection-handshake trust evaluator for a local user identity.
|
|
9
|
+
*
|
|
10
|
+
* Known user keys are authoritative even for a newly observed device: a claimed identity
|
|
11
|
+
* whose signing or box key differs from the stored user record is rejected as untrusted.
|
|
12
|
+
*/
|
|
7
13
|
function getTrustLevelFn(me, serverUrl) {
|
|
8
14
|
return async function getTrustLevel(deviceInfo, _registerNew) {
|
|
9
15
|
const userContext = await (0, context_1.getUserContext)();
|
|
@@ -67,12 +73,11 @@ function getTrustLevelFn(me, serverUrl) {
|
|
|
67
73
|
}
|
|
68
74
|
const effectiveUser = user ?? groupUser;
|
|
69
75
|
if (effectiveUser &&
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
deviceInfo.
|
|
73
|
-
deviceInfo.
|
|
74
|
-
|
|
75
|
-
// deviceInfo does not align with local info about user and device, return Untrusted
|
|
76
|
+
(deviceInfo.userId !== effectiveUser.userId ||
|
|
77
|
+
deviceInfo.publicKey !== effectiveUser.publicKey ||
|
|
78
|
+
deviceInfo.publicBoxKey !== effectiveUser.publicBoxKey ||
|
|
79
|
+
(device ? deviceInfo.userId !== device.userId : false))) {
|
|
80
|
+
// deviceInfo does not align with local info about the user or device, return Untrusted
|
|
76
81
|
// TODO check if user has changed their public keys
|
|
77
82
|
return socket_type_1.TrustLevel.Untrusted;
|
|
78
83
|
}
|
|
@@ -151,7 +156,7 @@ function getTrustLevelFn(me, serverUrl) {
|
|
|
151
156
|
// assignedAt: new Date(),
|
|
152
157
|
// }, { restoreIfDeleted: true });
|
|
153
158
|
// }
|
|
154
|
-
if (newUser) {
|
|
159
|
+
if (newUser && user) {
|
|
155
160
|
// TODO: I'm not sure we immediately want to save this user to the my personal db...
|
|
156
161
|
await (0, data_1.Users)(userDataContext).save(user, {
|
|
157
162
|
restoreIfDeleted: true,
|
|
@@ -255,7 +255,7 @@ describe("getTrustLevelFn", () => {
|
|
|
255
255
|
const result = await getTrustLevel(other);
|
|
256
256
|
expect(result).not.toBe(socket_type_1.TrustLevel.Untrusted);
|
|
257
257
|
});
|
|
258
|
-
it("
|
|
258
|
+
it("returns Untrusted for a known user's signing-key mismatch on a new device", async () => {
|
|
259
259
|
const me = makeTestIdentity();
|
|
260
260
|
const other = makeTestIdentity();
|
|
261
261
|
const existingUser = {
|
|
@@ -268,7 +268,21 @@ describe("getTrustLevelFn", () => {
|
|
|
268
268
|
setupMocks(null, existingUser);
|
|
269
269
|
const getTrustLevel = (0, get_trust_level_fn_1.getTrustLevelFn)(me);
|
|
270
270
|
const result = await getTrustLevel(other);
|
|
271
|
-
expect(result).
|
|
271
|
+
expect(result).toBe(socket_type_1.TrustLevel.Untrusted);
|
|
272
|
+
});
|
|
273
|
+
it("returns Untrusted for a known user's box-key mismatch on a new device", async () => {
|
|
274
|
+
const me = makeTestIdentity();
|
|
275
|
+
const other = makeTestIdentity();
|
|
276
|
+
const existingUser = {
|
|
277
|
+
userId: other.userId,
|
|
278
|
+
publicKey: other.publicKey,
|
|
279
|
+
publicBoxKey: `different_pbk_${(0, utils_1.newid)()}`,
|
|
280
|
+
name: "Alice",
|
|
281
|
+
};
|
|
282
|
+
setupMocks(null, existingUser);
|
|
283
|
+
const getTrustLevel = (0, get_trust_level_fn_1.getTrustLevelFn)(me);
|
|
284
|
+
const result = await getTrustLevel(other);
|
|
285
|
+
expect(result).toBe(socket_type_1.TrustLevel.Untrusted);
|
|
272
286
|
});
|
|
273
287
|
});
|
|
274
288
|
// ─── Trusted fast path ──────────────────────────────────────────────────────
|
|
@@ -35,6 +35,7 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
35
35
|
export type IConsoleLog = z.infer<typeof consoleLogSchema>;
|
|
36
36
|
/** Cutoff within this many ms of `Date.now()` is treated as "clear all" (manual clear). */
|
|
37
37
|
export declare const CLEAR_ALL_CUTOFF_SLACK_MS = 5000;
|
|
38
|
+
declare const metaData: ITableMetaData;
|
|
38
39
|
/**
|
|
39
40
|
* Returns whether a cutoff timestamp should trigger a full-table clear (drop + recreate)
|
|
40
41
|
* rather than a conditional delete. Used when the UI calls `deleteOldLogs(Date.now())`.
|
|
@@ -65,3 +66,5 @@ export declare class ConsoleLogsTable extends Table<IConsoleLog> {
|
|
|
65
66
|
* Returns the user's personal {@link ConsoleLogsTable} (logs are not synced across devices).
|
|
66
67
|
*/
|
|
67
68
|
export declare function ConsoleLogs(): Promise<ConsoleLogsTable>;
|
|
69
|
+
/** Table metadata for the {@link ConsoleLogsTable} (name, primary key, fields). */
|
|
70
|
+
export { metaData as consoleLogsMetaData };
|
|
@@ -34,7 +34,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
34
34
|
done = true;
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.ConsoleLogsTable = exports.CLEAR_ALL_CUTOFF_SLACK_MS = exports.consoleLogSchema = void 0;
|
|
37
|
+
exports.consoleLogsMetaData = exports.ConsoleLogsTable = exports.CLEAR_ALL_CUTOFF_SLACK_MS = exports.consoleLogSchema = void 0;
|
|
38
38
|
exports.isClearAllCutoff = isClearAllCutoff;
|
|
39
39
|
exports.ConsoleLogs = ConsoleLogs;
|
|
40
40
|
const zod_1 = require("zod");
|
|
@@ -73,6 +73,7 @@ const metaData = {
|
|
|
73
73
|
{ fields: ["processInstanceId"] },
|
|
74
74
|
],
|
|
75
75
|
};
|
|
76
|
+
exports.consoleLogsMetaData = metaData;
|
|
76
77
|
/**
|
|
77
78
|
* Returns whether a cutoff timestamp should trigger a full-table clear (drop + recreate)
|
|
78
79
|
* rather than a conditional delete. Used when the UI calls `deleteOldLogs(Date.now())`.
|
package/dist/system-ids.d.ts
CHANGED
|
@@ -7,3 +7,14 @@ export declare const runWorkflowToolId = "000peers0tool0runworkflow";
|
|
|
7
7
|
export declare const peersCorePackageId = "00mh0wlipjixk2gqmurbwee0o";
|
|
8
8
|
export declare const peersCorePackageRepoUrl = "https://github.com/peers-app/peers-core";
|
|
9
9
|
export declare const peersCorePublishPublicKey = "S7cQqnvVz-FVDO6Lk5zC7epSvcP_-NyBVZz9gdcj73c";
|
|
10
|
+
/**
|
|
11
|
+
* Stable id of the peers **system** itself, used as the `providerPackageId` for
|
|
12
|
+
* built-in system contracts (contracts the platform provides rather than a package).
|
|
13
|
+
*/
|
|
14
|
+
export declare const peersSystemPackageId = "000peers0system0000000001";
|
|
15
|
+
/**
|
|
16
|
+
* Contract id for the built-in **System Logs** contract (read/write the local device's
|
|
17
|
+
* console logs). This is the first minimal system contract and the proof-of-concept for
|
|
18
|
+
* contract-consumer-based remote RPC. See `contracts/system/logs-contract.ts`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const logsContractId = "000peers0logs000000000001";
|
package/dist/system-ids.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
|
|
3
|
+
exports.logsContractId = exports.peersSystemPackageId = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
|
|
4
4
|
exports.peersRootUserId = "000peers0user000000000001";
|
|
5
5
|
exports.defaultAssistantId = "000peers0bot00000000shell";
|
|
6
6
|
// export const defaultAssistantId = '000peers0bot000000000bot1';
|
|
@@ -11,3 +11,14 @@ exports.runWorkflowToolId = "000peers0tool0runworkflow";
|
|
|
11
11
|
exports.peersCorePackageId = "00mh0wlipjixk2gqmurbwee0o";
|
|
12
12
|
exports.peersCorePackageRepoUrl = "https://github.com/peers-app/peers-core";
|
|
13
13
|
exports.peersCorePublishPublicKey = "S7cQqnvVz-FVDO6Lk5zC7epSvcP_-NyBVZz9gdcj73c";
|
|
14
|
+
/**
|
|
15
|
+
* Stable id of the peers **system** itself, used as the `providerPackageId` for
|
|
16
|
+
* built-in system contracts (contracts the platform provides rather than a package).
|
|
17
|
+
*/
|
|
18
|
+
exports.peersSystemPackageId = "000peers0system0000000001";
|
|
19
|
+
/**
|
|
20
|
+
* Contract id for the built-in **System Logs** contract (read/write the local device's
|
|
21
|
+
* console logs). This is the first minimal system contract and the proof-of-concept for
|
|
22
|
+
* contract-consumer-based remote RPC. See `contracts/system/logs-contract.ts`.
|
|
23
|
+
*/
|
|
24
|
+
exports.logsContractId = "000peers0logs000000000001";
|