@peers-app/peers-sdk 0.7.3 → 0.7.5

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.
@@ -55,7 +55,7 @@ function getTableContainer(dataContext) {
55
55
  }
56
56
  function setDefaultClientUserContext(userId) {
57
57
  const clientProxyDataSourceFactory = (metaData, schema, groupId) => {
58
- return new client_proxy_data_source_1.ClientProxyDataSource(metaData, schema, groupId ?? '');
58
+ return new client_proxy_data_source_1.ClientProxyDataSource(metaData, schema, groupId || userId);
59
59
  };
60
60
  const userContext = new user_context_1.UserContext(userId, clientProxyDataSourceFactory);
61
61
  (async () => {
@@ -88,6 +88,12 @@ if (!rpc_types_1.isClient) {
88
88
  }
89
89
  // TODO: add check that client has permission to make this call
90
90
  rpc_types_1.rpcServerCalls.tableMethodCall = async (dataContextId, tableName, methodName, ...args) => {
91
+ if (!tableName) {
92
+ throw new Error('tableName is required for tableMethodCall');
93
+ }
94
+ if (!dataContextId) {
95
+ console.warn('dataContextId not provided to tableMethodCall - defaulting to user context. This is discouraged. If userDataContext was intended then pass it in explicitly.');
96
+ }
91
97
  const table = await getTable(dataContextId, tableName);
92
98
  const method = table[methodName];
93
99
  if (typeof method !== 'function') {
@@ -8,7 +8,6 @@ export declare class UserContext {
8
8
  readonly dataSourceFactory: DataSourceFactory;
9
9
  readonly deviceId: Observable<string>;
10
10
  readonly currentlyActiveGroupId: Observable<string | undefined>;
11
- readonly packagesRootDir: Observable<string>;
12
11
  readonly reloadPackagesOnPageRefresh: Observable<boolean>;
13
12
  readonly groupIds: Observable<string[]>;
14
13
  readonly userDataContext: DataContext;
@@ -40,7 +39,7 @@ export declare class UserContext {
40
39
  syncUserAndGroupObjects(userId: string, keys: IPublicPrivateKeys, me?: IUser): Promise<void>;
41
40
  subscribeToDataChangedAcrossAllGroups<T extends {
42
41
  [key: string]: any;
43
- }>(table: Table<T>, handler: (evt: ICrossGroupSubscriptionHandlerArgs<T>) => any): import("../events").ISubscriptionResult;
42
+ }>(table: string | Table<T>, handler: (evt: ICrossGroupSubscriptionHandlerArgs<T>) => any): import("../events").ISubscriptionResult;
44
43
  }
45
44
  export interface ICrossGroupSubscriptionHandlerArgs<T> {
46
45
  name: string;
@@ -13,7 +13,6 @@ class UserContext {
13
13
  dataSourceFactory;
14
14
  deviceId = (0, observable_1.observable)('');
15
15
  currentlyActiveGroupId = (0, observable_1.observable)();
16
- packagesRootDir = (0, observable_1.observable)('');
17
16
  reloadPackagesOnPageRefresh = (0, observable_1.observable)(false);
18
17
  groupIds = (0, observable_1.observable)([]);
19
18
  userDataContext;
@@ -29,7 +28,12 @@ class UserContext {
29
28
  this.loadingPromise = this.init();
30
29
  }
31
30
  async init() {
32
- await this.loadUserContextObservablesFromDB();
31
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
32
+ console.log(`not setting up user-context pvars during tests because they will interfere with other UserContext instances`);
33
+ }
34
+ else {
35
+ await this.loadUserContextObservablesFromDB();
36
+ }
33
37
  await this.loadGroupContexts();
34
38
  await this.loadAllPackages();
35
39
  return this;
@@ -106,8 +110,9 @@ class UserContext {
106
110
  * for the user context to be fully loaded before proceeding with standard data access
107
111
  */
108
112
  async loadUserContextObservablesFromDB() {
109
- const persistentVars = await this.userDataContext.tableContainer.getTableByName(data_1.persistentVarsMetaData.name);
110
- const vars = await persistentVars.list({ name: { $in: ['myUserId', 'thisDeviceId', 'currentlyActiveGroupId', 'packagesRootDir', 'reloadPackagesOnPageRefresh'] } });
113
+ // const persistentVars: PersistentVarsTable = await this.userDataContext.tableContainer.getTableByName(persistentVarsMetaData.name) as PersistentVarsTable;
114
+ const persistentVars = (0, data_1.PersistentVars)(this.userDataContext);
115
+ const vars = await persistentVars.list({ name: { $in: ['thisDeviceId', 'currentlyActiveGroupId', 'reloadPackagesOnPageRefresh'] } });
111
116
  const varDbValues = vars.reduce((acc, curr) => {
112
117
  acc[curr.name] = curr.value?.value;
113
118
  return acc;
@@ -119,15 +124,11 @@ class UserContext {
119
124
  const currentlyActiveGroupIdPVar = (0, data_1.deviceVar)('currentlyActiveGroupId', { dbValue: varDbValues['currentlyActiveGroupId'], defaultValue: '', userContext: this });
120
125
  this.currentlyActiveGroupId(currentlyActiveGroupIdPVar());
121
126
  (0, observable_1.linkObservables)(currentlyActiveGroupIdPVar, this.currentlyActiveGroupId);
122
- const packagesRootDirPVar = (0, data_1.deviceVar)('packagesRootDir', { defaultValue: '~/peers-packages', dbValue: varDbValues['packagesRootDir'], userContext: this });
123
- this.packagesRootDir(packagesRootDirPVar());
124
- (0, observable_1.linkObservables)(packagesRootDirPVar, this.packagesRootDir);
125
127
  const reloadPackagesOnPageRefreshPVar = (0, data_1.deviceVar)('reloadPackagesOnPageRefresh', { defaultValue: false, dbValue: varDbValues['reloadPackagesOnPageRefresh'], userContext: this });
126
128
  this.reloadPackagesOnPageRefresh(reloadPackagesOnPageRefreshPVar());
127
129
  (0, observable_1.linkObservables)(reloadPackagesOnPageRefreshPVar, this.reloadPackagesOnPageRefresh);
128
130
  await Promise.all([
129
131
  deviceIdPVar.loadingPromise,
130
- packagesRootDirPVar.loadingPromise,
131
132
  reloadPackagesOnPageRefreshPVar.loadingPromise,
132
133
  ]);
133
134
  }
@@ -185,7 +186,8 @@ class UserContext {
185
186
  }
186
187
  }
187
188
  subscribeToDataChangedAcrossAllGroups(table, handler) {
188
- const tableEventPrefix = `${table.tableName}_DataChanged_`;
189
+ const tableName = typeof table === 'string' ? table : table.tableName;
190
+ const tableEventPrefix = `${tableName}_DataChanged_`;
189
191
  const subscription = (0, events_1.subscribe)(evt => evt.name.startsWith(tableEventPrefix), async (evt) => {
190
192
  const dataContextId = evt.name.endsWith('_') ? this.userId : evt.name.split('_').pop();
191
193
  const dataContext = this.getDataContext(dataContextId);
@@ -90,6 +90,9 @@ class MockPeerDevice {
90
90
  timestampLastApplied: Date.now()
91
91
  }));
92
92
  }
93
+ sendDeviceMessage(message) {
94
+ throw new Error('Method not implemented.');
95
+ }
93
96
  }
94
97
  describe('data-locks.ts', () => {
95
98
  const fiveMinutesMs = 5 * 60 * 1000;
@@ -11,9 +11,10 @@ function ProxyClientTableMethodCalls() {
11
11
  return function (target, context) {
12
12
  if (context.kind === 'method' && rpc_types_1.isClient) {
13
13
  const methodName = context.name;
14
- return function (table, ...args) {
15
- const dataContextId = table.groupId || '';
16
- return rpc_types_1.rpcServerCalls.tableMethodCall(dataContextId, table.tableName, methodName, ...args);
14
+ // WARNING: Using typescript's magical `this` param
15
+ return function (...args) {
16
+ const dataContextId = this.dataSource.dataContextId || this.groupId || '';
17
+ return rpc_types_1.rpcServerCalls.tableMethodCall(dataContextId, this.tableName, methodName, ...args);
17
18
  };
18
19
  }
19
20
  return target;
@@ -136,145 +136,150 @@ function persistentVarFactory(name, opts) {
136
136
  let isSecret = opts.isSecret;
137
137
  let rec = undefined;
138
138
  persistentVar.loadingPromise = new Promise(async (resolve) => {
139
- const userContext = opts?.userContext || await (0, user_context_singleton_1.getUserContext)();
140
- function getDataContext() {
141
- if (opts?.dataContext) {
142
- return opts.dataContext;
143
- }
144
- if (scope === 'group') {
145
- return userContext.defaultDataContext();
146
- }
147
- else {
148
- return userContext.userDataContext;
149
- }
150
- }
151
- function getVarNameInDb() {
152
- if (scope === 'groupDevice' || scope === 'groupUser') {
153
- const dc = opts?.dataContext || userContext.defaultDataContext();
154
- // if it's a group var saved in the user's personal db, it's postfixed with the dataContextId to make it unique
155
- return name + `_` + dc.dataContextId;
156
- }
157
- return name;
158
- }
159
- async function loadRecFromDb() {
160
- const dc = getDataContext();
161
- const table = PersistentVars(dc);
162
- const name = getVarNameInDb();
163
- let dbRec = await table.findOne({ name });
164
- if (dbRec) {
165
- if (dbRec.scope !== scope) {
166
- console.warn(`${name}: deleting old persistent var record because scopes don't match. If this is unexpected there could be two different persistent variables using the same name`);
167
- await table.delete(dbRec);
168
- dbRec = undefined;
139
+ try {
140
+ const userContext = opts?.userContext || await (0, user_context_singleton_1.getUserContext)();
141
+ function getDataContext() {
142
+ if (opts?.dataContext) {
143
+ return opts.dataContext;
144
+ }
145
+ if (scope === 'group') {
146
+ return userContext.defaultDataContext();
147
+ }
148
+ else {
149
+ return userContext.userDataContext;
169
150
  }
170
151
  }
171
- if (!dbRec) {
172
- dbRec = {
173
- persistentVarId: '',
174
- name,
175
- scope,
176
- isSecret,
177
- value: { value: defaultValue },
178
- };
179
- }
180
- // if the db says it's secret but the caller didn't specify, assume it's secret
181
- if (dbRec.isSecret !== undefined && opts.isSecret === undefined) {
182
- isSecret = dbRec.isSecret;
183
- }
184
- return dbRec;
185
- }
186
- async function reactToValueChanged(value) {
187
- if (name === 'colorModePreference')
188
- console.log(`colorModePreferences pvar value set: ${value}`);
189
- if (!rec?.persistentVarId && value === defaultValue) {
190
- return;
191
- }
192
- if (!rec?.persistentVarId) {
193
- rec = await loadRecFromDb();
194
- }
195
- const oldValue = rec.value.value;
196
- if ((0, lodash_1.isEqual)(value, oldValue)) {
197
- return;
152
+ function getVarNameInDb() {
153
+ if (scope === 'groupDevice' || scope === 'groupUser') {
154
+ const dc = opts?.dataContext || userContext.defaultDataContext();
155
+ // if it's a group var saved in the user's personal db, it's postfixed with the dataContextId to make it unique
156
+ return name + `_` + dc.dataContextId;
157
+ }
158
+ return name;
198
159
  }
199
- rec.value.value = value;
200
- const dc = getDataContext();
201
- const table = PersistentVars(dc);
202
- // delete if value equals default value
203
- if ((0, lodash_1.isEqual)(rec.value.value, defaultValue)) {
204
- if (rec.persistentVarId) {
205
- if (name === 'colorModePreference') {
206
- console.log(`deleted persistent var ${name} from db:`, rec.value.value);
160
+ async function loadRecFromDb() {
161
+ const dc = getDataContext();
162
+ const table = PersistentVars(dc);
163
+ const name = getVarNameInDb();
164
+ let dbRec = await table.findOne({ name });
165
+ if (dbRec) {
166
+ if (dbRec.scope !== scope) {
167
+ console.warn(`${name}: deleting old persistent var record because scopes don't match. If this is unexpected there could be two different persistent variables using the same name`);
168
+ await table.delete(dbRec);
169
+ dbRec = undefined;
207
170
  }
208
- await table.delete(rec);
209
171
  }
210
- rec = undefined;
172
+ if (!dbRec) {
173
+ dbRec = {
174
+ persistentVarId: '',
175
+ name,
176
+ scope,
177
+ isSecret,
178
+ value: { value: defaultValue },
179
+ };
180
+ }
181
+ // if the db says it's secret but the caller didn't specify, assume it's secret
182
+ if (dbRec.isSecret !== undefined && opts.isSecret === undefined) {
183
+ isSecret = dbRec.isSecret;
184
+ }
185
+ return dbRec;
211
186
  }
212
- else {
213
- try {
214
- await table.save(rec);
215
- if (name === 'colorModePreference') {
216
- console.log(`Saved var ${name} to db:`, rec.value.value);
187
+ async function reactToValueChanged(value) {
188
+ if (name === 'colorModePreference')
189
+ console.log(`colorModePreferences pvar value set: ${value}`);
190
+ if (!rec?.persistentVarId && value === defaultValue) {
191
+ return;
192
+ }
193
+ if (!rec?.persistentVarId) {
194
+ rec = await loadRecFromDb();
195
+ }
196
+ const oldValue = rec.value.value;
197
+ if ((0, lodash_1.isEqual)(value, oldValue)) {
198
+ return;
199
+ }
200
+ rec.value.value = value;
201
+ const dc = getDataContext();
202
+ const table = PersistentVars(dc);
203
+ // delete if value equals default value
204
+ if ((0, lodash_1.isEqual)(rec.value.value, defaultValue)) {
205
+ if (rec.persistentVarId) {
206
+ if (name === 'colorModePreference') {
207
+ console.log(`deleted persistent var ${name} from db:`, rec.value.value);
208
+ }
209
+ await table.delete(rec);
217
210
  }
211
+ rec = undefined;
218
212
  }
219
- catch (err) {
220
- const errMsg = err?.message || String(err) || '';
221
- if (errMsg.includes('UNIQUE constraint failed')) {
222
- console.warn(`Detected UNIQUE constraint failed error when saving persistent var, reloading and retrying: ${name}`);
223
- rec = await loadRecFromDb();
224
- persistentVar(rec.value.value);
213
+ else {
214
+ try {
215
+ await table.save(rec);
216
+ if (name === 'colorModePreference') {
217
+ console.log(`Saved var ${name} to db:`, rec.value.value);
218
+ }
225
219
  }
226
- else {
227
- console.error('Error saving persistent var', { name, value, rec, err });
228
- throw err;
220
+ catch (err) {
221
+ const errMsg = err?.message || String(err) || '';
222
+ if (errMsg.includes('UNIQUE constraint failed')) {
223
+ console.warn(`Detected UNIQUE constraint failed error when saving persistent var, reloading and retrying: ${name}`);
224
+ rec = await loadRecFromDb();
225
+ persistentVar(rec.value.value);
226
+ }
227
+ else {
228
+ console.error('Error saving persistent var', { name, value, rec, err });
229
+ throw err;
230
+ }
229
231
  }
230
232
  }
231
233
  }
232
- }
233
- // subscribe to db changes
234
- userContext.subscribeToDataChangedAcrossAllGroups(PersistentVars(), async (evt) => {
235
- const dbRec = evt.data.dataObject;
236
- const dbName = getVarNameInDb();
237
- if (!rec?.persistentVarId && dbRec.name === dbName) {
238
- rec = dbRec;
239
- }
240
- if (dbRec.persistentVarId === rec?.persistentVarId) {
241
- if (evt.data.op === 'delete') {
242
- rec = undefined;
243
- if (defaultValue !== undefined) {
244
- persistentVar(defaultValue);
245
- }
246
- }
247
- else {
234
+ // subscribe to db changes
235
+ userContext.subscribeToDataChangedAcrossAllGroups(exports.persistentVarsMetaData.name, async (evt) => {
236
+ const dbRec = evt.data.dataObject;
237
+ const dbName = getVarNameInDb();
238
+ if (!rec?.persistentVarId && dbRec.name === dbName) {
248
239
  rec = dbRec;
249
- if (!(0, lodash_1.isEqual)(persistentVar(), rec.value.value)) {
250
- persistentVar(rec.value.value);
240
+ }
241
+ if (dbRec.persistentVarId === rec?.persistentVarId) {
242
+ if (evt.data.op === 'delete') {
243
+ rec = undefined;
244
+ if (defaultValue !== undefined) {
245
+ persistentVar(defaultValue);
246
+ }
247
+ }
248
+ else {
249
+ rec = dbRec;
250
+ if (!(0, lodash_1.isEqual)(persistentVar(), rec.value.value)) {
251
+ persistentVar(rec.value.value);
252
+ }
251
253
  }
252
254
  }
255
+ });
256
+ // update group-dependent variables if group context changes and it hasn't been pinned to a specific data context
257
+ const groupDependentScopes = ['group', 'groupDevice', 'groupUser'];
258
+ if (!opts?.dataContext && groupDependentScopes.includes(scope)) {
259
+ userContext.defaultDataContext.subscribe(async () => {
260
+ rec = await loadRecFromDb();
261
+ persistentVar(rec.value.value);
262
+ });
253
263
  }
254
- });
255
- // update group-dependent variables if group context changes and it hasn't been pinned to a specific data context
256
- const groupDependentScopes = ['group', 'groupDevice', 'groupUser'];
257
- if (!opts?.dataContext && groupDependentScopes.includes(scope)) {
258
- userContext.defaultDataContext.subscribe(async () => {
264
+ if (!setWithDbValue) {
259
265
  rec = await loadRecFromDb();
260
- persistentVar(rec.value.value);
261
- });
262
- }
263
- if (!setWithDbValue) {
264
- rec = await loadRecFromDb();
265
- if (!(0, lodash_1.isEqual)(rec.value.value, persistentVar())) {
266
- if (name === 'colorModePreference') {
267
- console.log(`Loaded persistent var ${name} from db:`, rec.value.value);
266
+ if (!(0, lodash_1.isEqual)(rec.value.value, persistentVar())) {
267
+ if (name === 'colorModePreference') {
268
+ console.log(`Loaded persistent var ${name} from db:`, rec.value.value);
269
+ }
270
+ persistentVar(rec.value.value);
268
271
  }
269
- persistentVar(rec.value.value);
270
272
  }
271
- }
272
- persistentVar.subscribe(newValue => {
273
- persistentVar.loadingPromise = persistentVar.loadingPromise.then(async () => {
274
- await reactToValueChanged(newValue);
275
- return persistentVar;
273
+ persistentVar.subscribe(newValue => {
274
+ persistentVar.loadingPromise = persistentVar.loadingPromise.then(async () => {
275
+ await reactToValueChanged(newValue);
276
+ return persistentVar;
277
+ });
276
278
  });
277
- });
279
+ }
280
+ catch (err) {
281
+ console.error(`an unexpected occurred while loading a persistent var: ${name}`, err);
282
+ }
278
283
  resolve(persistentVar);
279
284
  });
280
285
  return persistentVar;
@@ -201,7 +201,7 @@ class Connection {
201
201
  if (this.trustLevel < socket_type_1.TrustLevel.Unknown) {
202
202
  throw new Error('Untrusted connection');
203
203
  }
204
- console.log(new Date().toISOString(), `Connection ${this.connectionId} verified on server side with trust level ${this.trustLevel}`);
204
+ console.log(`Connection ${this.connectionId} verified on server side with trust level ${this.trustLevel}`);
205
205
  this._verified = true;
206
206
  if (this.onHandshakeComplete) {
207
207
  setTimeout(() => {
@@ -239,7 +239,7 @@ class Connection {
239
239
  this.emit('reset');
240
240
  throw new Error('Untrusted connection');
241
241
  }
242
- console.log(new Date().toISOString(), `Connection ${this.connectionId} (${remoteAddress}) verified on client side with trust level ${this.trustLevel}`);
242
+ console.log(`Connection ${this.connectionId} (${remoteAddress}) verified on client side with trust level ${this.trustLevel}`);
243
243
  return handshakeResponse;
244
244
  }
245
245
  }
@@ -64,7 +64,7 @@ function getTrustLevelFn(me, serverUrl) {
64
64
  // console.error(new Date().toISOString(), 'Error getting trust level from server', serverUrl, err);
65
65
  // return TrustLevel.Unknown;
66
66
  // });
67
- // console.log(new Date().toISOString(), 'Remote trust level', { _remoteTrustLevel, serverUrl, remoteUrlBeingChecked: serverUrl });
67
+ // console.log('Remote trust level', { _remoteTrustLevel, serverUrl, remoteUrlBeingChecked: serverUrl });
68
68
  // if (_remoteTrustLevel < TrustLevel.Unknown) {
69
69
  // console.error(new Date().toISOString(), 'Unverified user failed trust check', { user, remoteTrustLevel });
70
70
  // device.trustLevel = _remoteTrustLevel;
package/dist/index.d.ts CHANGED
@@ -31,3 +31,4 @@ export * from "./observable";
31
31
  export * from "./rpc-types";
32
32
  export * from "./serial-json";
33
33
  export * from "./utils";
34
+ export * from "./logging";
package/dist/index.js CHANGED
@@ -48,3 +48,4 @@ __exportStar(require("./observable"), exports);
48
48
  __exportStar(require("./rpc-types"), exports);
49
49
  __exportStar(require("./serial-json"), exports);
50
50
  __exportStar(require("./utils"), exports);
51
+ __exportStar(require("./logging"), exports);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Setup console proxy to capture all console output and write to ConsoleLogs table.
3
+ * Also subscribes to dataChanged events to output logs from other process instances.
4
+ * @param processName - The name of the process (e.g., 'main', 'renderer')
5
+ */
6
+ export declare function setupConsoleProxy(processName: string): Promise<void>;
7
+ /**
8
+ * Restore original console methods
9
+ */
10
+ export declare function restoreConsole(): void;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupConsoleProxy = setupConsoleProxy;
4
+ exports.restoreConsole = restoreConsole;
5
+ const utils_1 = require("../utils");
6
+ const console_logs_table_1 = require("./console-logs.table");
7
+ // Store original console methods
8
+ const originalConsole = {
9
+ debug: console.debug,
10
+ info: console.info,
11
+ log: console.log,
12
+ warn: console.warn,
13
+ error: console.error,
14
+ };
15
+ let isProxySetup = false;
16
+ let currentProcessName = 'unknown';
17
+ let currentProcessInstanceId = '';
18
+ /**
19
+ * Setup console proxy to capture all console output and write to ConsoleLogs table.
20
+ * Also subscribes to dataChanged events to output logs from other process instances.
21
+ * @param processName - The name of the process (e.g., 'main', 'renderer')
22
+ */
23
+ async function setupConsoleProxy(processName) {
24
+ if (isProxySetup) {
25
+ console.warn('Console proxy already setup, skipping...');
26
+ return;
27
+ }
28
+ isProxySetup = true;
29
+ currentProcessName = processName;
30
+ currentProcessInstanceId = (0, utils_1.newid)(); // Unique ID for this process instance
31
+ const levels = ['debug', 'info', 'log', 'warn', 'error'];
32
+ // Monkey-patch console methods to capture local logs
33
+ levels.forEach((level) => {
34
+ const original = originalConsole[level];
35
+ console[level] = function (...args) {
36
+ // Always call original console method first (preserve terminal output)
37
+ original.apply(console, args);
38
+ // Asynchronously write to database (don't block console output)
39
+ writeLogToDatabase(level, args).catch((err) => {
40
+ // Use original console to avoid infinite recursion
41
+ originalConsole.error('Failed to write log to database:', err);
42
+ });
43
+ };
44
+ });
45
+ // Subscribe to dataChanged events to output logs from OTHER process instances
46
+ try {
47
+ const consoleLogsTable = await (0, console_logs_table_1.ConsoleLogs)();
48
+ consoleLogsTable.dataChanged.subscribe((evt) => {
49
+ const log = evt.dataObject;
50
+ // Skip if this is our own log
51
+ if (log.processInstanceId === currentProcessInstanceId) {
52
+ return;
53
+ }
54
+ // Output log from another process with prefix
55
+ const colorCode = log.process === 'renderer' ? '\x1b[36m' : '\x1b[35m'; // cyan for renderer, magenta for main
56
+ const resetCode = '\x1b[0m';
57
+ const prefix = `${colorCode}[${log.process}]${resetCode}`;
58
+ const logLevel = log.level;
59
+ const logArgs = [prefix, log.message];
60
+ if (log.context) {
61
+ logArgs.push(log.context);
62
+ }
63
+ if (originalConsole[logLevel]) {
64
+ originalConsole[logLevel](...logArgs);
65
+ }
66
+ else {
67
+ originalConsole.log(...logArgs);
68
+ }
69
+ // Also output stack trace for errors
70
+ if (log.stackTrace) {
71
+ originalConsole.error(log.stackTrace);
72
+ }
73
+ });
74
+ }
75
+ catch (err) {
76
+ originalConsole.error('Failed to subscribe to console logs dataChanged:', err);
77
+ }
78
+ console.log(`Console proxy initialized for process: ${processName} (instance: ${currentProcessInstanceId})`);
79
+ }
80
+ /**
81
+ * Restore original console methods
82
+ */
83
+ function restoreConsole() {
84
+ if (!isProxySetup) {
85
+ return;
86
+ }
87
+ console.debug = originalConsole.debug;
88
+ console.info = originalConsole.info;
89
+ console.log = originalConsole.log;
90
+ console.warn = originalConsole.warn;
91
+ console.error = originalConsole.error;
92
+ isProxySetup = false;
93
+ originalConsole.log('Console proxy removed, original console restored');
94
+ }
95
+ /**
96
+ * Write a log entry to the ConsoleLogs table
97
+ */
98
+ async function writeLogToDatabase(level, args) {
99
+ try {
100
+ // Format message from arguments
101
+ const message = formatLogMessage(args);
102
+ // Extract context objects from arguments
103
+ const context = extractContext(args);
104
+ // Get stack trace for errors
105
+ const stackTrace = level === 'error' ? getStackTrace() : undefined;
106
+ // Extract source from stack trace
107
+ const source = extractSource(stackTrace);
108
+ const logRecord = {
109
+ logId: (0, utils_1.newid)(),
110
+ timestamp: (0, utils_1.getTimestamp)(),
111
+ level,
112
+ process: currentProcessName,
113
+ processInstanceId: currentProcessInstanceId,
114
+ source,
115
+ message,
116
+ context,
117
+ stackTrace,
118
+ };
119
+ const consoleLogsTable = await (0, console_logs_table_1.ConsoleLogs)();
120
+ await consoleLogsTable.insert(logRecord);
121
+ }
122
+ catch (err) {
123
+ // Silently fail if table not available (e.g., during initialization)
124
+ // Don't use console here to avoid infinite recursion
125
+ }
126
+ }
127
+ /**
128
+ * Format log arguments into a single message string
129
+ */
130
+ function formatLogMessage(args) {
131
+ return args.map((arg) => {
132
+ if (typeof arg === 'string') {
133
+ return arg;
134
+ }
135
+ if (arg instanceof Error) {
136
+ return `${arg.name}: ${arg.message}`;
137
+ }
138
+ if (typeof arg === 'object') {
139
+ try {
140
+ return JSON.stringify(arg);
141
+ }
142
+ catch {
143
+ return String(arg);
144
+ }
145
+ }
146
+ return String(arg);
147
+ }).join(' ');
148
+ }
149
+ /**
150
+ * Extract structured context objects from log arguments
151
+ */
152
+ function extractContext(args) {
153
+ const objects = args.filter((arg) => arg && typeof arg === 'object' && !(arg instanceof Error) && !(arg instanceof Date));
154
+ if (objects.length === 0) {
155
+ return undefined;
156
+ }
157
+ if (objects.length === 1) {
158
+ return objects[0];
159
+ }
160
+ // Merge multiple objects
161
+ return Object.assign({}, ...objects);
162
+ }
163
+ /**
164
+ * Get current stack trace
165
+ */
166
+ function getStackTrace() {
167
+ try {
168
+ const stack = new Error().stack;
169
+ if (!stack)
170
+ return undefined;
171
+ // Remove first few lines (Error message and this function's frames)
172
+ const lines = stack.split('\n');
173
+ return lines.slice(4).join('\n');
174
+ }
175
+ catch {
176
+ return undefined;
177
+ }
178
+ }
179
+ /**
180
+ * Extract source file/module from stack trace
181
+ */
182
+ function extractSource(stackTrace) {
183
+ if (!stackTrace)
184
+ return undefined;
185
+ try {
186
+ const lines = stackTrace.split('\n');
187
+ if (lines.length === 0)
188
+ return undefined;
189
+ // Get first line of stack (caller's location)
190
+ const firstLine = lines[0];
191
+ // Extract file path from patterns like:
192
+ // "at function (file:///path/to/file.ts:123:45)"
193
+ // "at file:///path/to/file.ts:123:45"
194
+ const match = firstLine.match(/\((.*?):\d+:\d+\)|at (.*?):\d+:\d+/);
195
+ if (match) {
196
+ const path = match[1] || match[2];
197
+ // Extract just the filename
198
+ const filename = path.split('/').pop()?.split('?')[0];
199
+ return filename;
200
+ }
201
+ }
202
+ catch {
203
+ // Ignore parsing errors
204
+ }
205
+ return undefined;
206
+ }
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { ITableMetaData } from "../data/orm/types";
3
+ import { ITableDependencies, Table } from "../data/orm";
4
+ export declare const consoleLogSchema: z.ZodObject<{
5
+ logId: z.ZodEffects<z.ZodString, string, string>;
6
+ timestamp: z.ZodDefault<z.ZodNumber>;
7
+ level: z.ZodEnum<["debug", "info", "log", "warn", "error"]>;
8
+ process: z.ZodString;
9
+ processInstanceId: z.ZodString;
10
+ source: z.ZodOptional<z.ZodString>;
11
+ message: z.ZodString;
12
+ context: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodAny, z.objectOutputType<{}, z.ZodAny, "strip">, z.objectInputType<{}, z.ZodAny, "strip">>>;
13
+ stackTrace: z.ZodOptional<z.ZodString>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ message: string;
16
+ timestamp: number;
17
+ logId: string;
18
+ level: "error" | "debug" | "info" | "log" | "warn";
19
+ process: string;
20
+ processInstanceId: string;
21
+ source?: string | undefined;
22
+ context?: z.objectOutputType<{}, z.ZodAny, "strip"> | undefined;
23
+ stackTrace?: string | undefined;
24
+ }, {
25
+ message: string;
26
+ logId: string;
27
+ level: "error" | "debug" | "info" | "log" | "warn";
28
+ process: string;
29
+ processInstanceId: string;
30
+ source?: string | undefined;
31
+ timestamp?: number | undefined;
32
+ context?: z.objectInputType<{}, z.ZodAny, "strip"> | undefined;
33
+ stackTrace?: string | undefined;
34
+ }>;
35
+ export type IConsoleLog = z.infer<typeof consoleLogSchema>;
36
+ export declare class ConsoleLogsTable extends Table<IConsoleLog> {
37
+ readonly LOG_TTL: number;
38
+ readonly LOG_CLEANUP_INTERVAL: number;
39
+ constructor(metaData: ITableMetaData, deps: ITableDependencies);
40
+ private logCleanupInitialized;
41
+ initializeLogCleanup(): Promise<void>;
42
+ deleteOldLogs(cutoffTimestamp?: number): Promise<void>;
43
+ }
44
+ /**
45
+ * We always use the user's personal data context so all logs are written to the same place. This requires
46
+ * that the table be accessed in an asynchronous way
47
+ */
48
+ export declare function ConsoleLogs(): Promise<ConsoleLogsTable>;
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.ConsoleLogsTable = exports.consoleLogSchema = void 0;
38
+ exports.ConsoleLogs = ConsoleLogs;
39
+ const zod_1 = require("zod");
40
+ const zod_types_1 = require("../types/zod-types");
41
+ const types_1 = require("../data/orm/types");
42
+ const user_context_singleton_1 = require("../context/user-context-singleton");
43
+ const table_definitions_system_1 = require("../data/orm/table-definitions.system");
44
+ const utils_1 = require("../utils");
45
+ const orm_1 = require("../data/orm");
46
+ exports.consoleLogSchema = zod_1.z.object({
47
+ logId: zod_types_1.zodPeerId,
48
+ timestamp: zod_1.z.number().default(() => (0, utils_1.getTimestamp)()).describe('The timestamp the log was created created'),
49
+ level: zod_1.z.enum(['debug', 'info', 'log', 'warn', 'error']).describe('The log level'),
50
+ process: zod_1.z.string().describe('The process that generated the log (e.g., main, renderer, worker)'),
51
+ processInstanceId: zod_1.z.string().describe('Unique ID for this process instance to filter own logs'),
52
+ source: zod_1.z.string().optional().describe('The source file or module that generated the log'),
53
+ message: zod_1.z.string().describe('The log message'),
54
+ context: zod_types_1.zodAnyObject.optional().describe('Additional structured context data'),
55
+ stackTrace: zod_1.z.string().optional().describe('Stack trace for errors'),
56
+ });
57
+ const metaData = {
58
+ name: 'ConsoleLogs',
59
+ description: 'System-wide console log entries with cross-process visibility.',
60
+ primaryKeyName: 'logId',
61
+ fields: (0, types_1.schemaToFields)(exports.consoleLogSchema),
62
+ localOnly: true, // Don't sync logs between devices
63
+ indexes: [
64
+ { fields: ['timestamp'] },
65
+ { fields: ['level'] },
66
+ { fields: ['process'] },
67
+ { fields: ['processInstanceId'] },
68
+ ],
69
+ };
70
+ let ConsoleLogsTable = (() => {
71
+ let _classSuper = orm_1.Table;
72
+ let _instanceExtraInitializers = [];
73
+ let _initializeLogCleanup_decorators;
74
+ let _deleteOldLogs_decorators;
75
+ return class ConsoleLogsTable extends _classSuper {
76
+ static {
77
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
78
+ _initializeLogCleanup_decorators = [(0, orm_1.ProxyClientTableMethodCalls)()];
79
+ _deleteOldLogs_decorators = [(0, orm_1.ProxyClientTableMethodCalls)()];
80
+ __esDecorate(this, null, _initializeLogCleanup_decorators, { kind: "method", name: "initializeLogCleanup", static: false, private: false, access: { has: obj => "initializeLogCleanup" in obj, get: obj => obj.initializeLogCleanup }, metadata: _metadata }, null, _instanceExtraInitializers);
81
+ __esDecorate(this, null, _deleteOldLogs_decorators, { kind: "method", name: "deleteOldLogs", static: false, private: false, access: { has: obj => "deleteOldLogs" in obj, get: obj => obj.deleteOldLogs }, metadata: _metadata }, null, _instanceExtraInitializers);
82
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
83
+ }
84
+ LOG_TTL = (__runInitializers(this, _instanceExtraInitializers), 7 * 24 * 60 * 60 * 1000); // 7 days
85
+ LOG_CLEANUP_INTERVAL = 24 * 60 * 60 * 1000; // 1 day
86
+ constructor(metaData, deps) {
87
+ super(metaData, deps);
88
+ if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {
89
+ this.initializeLogCleanup();
90
+ }
91
+ }
92
+ logCleanupInitialized = false;
93
+ async initializeLogCleanup() {
94
+ if (this.logCleanupInitialized) {
95
+ return;
96
+ }
97
+ this.logCleanupInitialized = true;
98
+ // always cleanup old logs on startup - randomly spread out of the first 60 seconds to not slam the db
99
+ setTimeout(() => this.deleteOldLogs(), 60_000 * Math.random());
100
+ // regularly clean up logs
101
+ // TODO - consider doing this as a workflow
102
+ setInterval(() => this.deleteOldLogs(), this.LOG_CLEANUP_INTERVAL);
103
+ }
104
+ async deleteOldLogs(cutoffTimestamp = Date.now() - 7 * 24 * 60 * 60 * 1000) {
105
+ const count = await this.count({ timestamp: { $lt: cutoffTimestamp } });
106
+ if (count === 0) {
107
+ console.log(`No old logs to clean up older than ${new Date(cutoffTimestamp).toISOString()}`);
108
+ return;
109
+ }
110
+ let ds = this.dataSource;
111
+ while (!(ds instanceof orm_1.SQLDataSource) && ds.dataSource) {
112
+ ds = ds.dataSource;
113
+ }
114
+ if (ds instanceof orm_1.SQLDataSource) {
115
+ const db = ds.db;
116
+ await db.exec(`delete from ConsoleLogs where timestamp < $timestamp`, { timestamp: cutoffTimestamp });
117
+ console.log(`bulk deleted ${count} logs`);
118
+ }
119
+ else {
120
+ const cursor = this.cursor({ timestamp: { $lt: cutoffTimestamp } }, { sortBy: ['-timestamp'] });
121
+ let count2 = 0;
122
+ for await (const log of cursor) {
123
+ this.delete(log.logId);
124
+ }
125
+ console.log(`cursor deleted ${count2} logs`);
126
+ }
127
+ console.log(`cleaned up console logs`, { expectedCount: count });
128
+ }
129
+ };
130
+ })();
131
+ exports.ConsoleLogsTable = ConsoleLogsTable;
132
+ (0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.consoleLogSchema, ConsoleLogsTable);
133
+ /**
134
+ * We always use the user's personal data context so all logs are written to the same place. This requires
135
+ * that the table be accessed in an asynchronous way
136
+ */
137
+ async function ConsoleLogs() {
138
+ const userContext = await (0, user_context_singleton_1.getUserContext)();
139
+ return (0, user_context_singleton_1.getTableContainer)(userContext.userDataContext).getTable(metaData, exports.consoleLogSchema, ConsoleLogsTable);
140
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./console-logger";
2
+ export * from "./console-logs.table";
@@ -0,0 +1,18 @@
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("./console-logger"), exports);
18
+ __exportStar(require("./console-logs.table"), exports);
package/dist/rpc-types.js CHANGED
@@ -16,7 +16,6 @@ exports.rpcServerCalls = {
16
16
  setUserIdAndSecretKey: rpcStub('setUserIdAndSecretKey'),
17
17
  getUserId: rpcStub('getUserId'),
18
18
  encryptData: rpcStub('encryptData'),
19
- // TODO collapse these all down to just tableMethodCall
20
19
  tableMethodCall: rpcStub('tableMethodCall'),
21
20
  // TODO lock this down so not all code can get any file contents
22
21
  getFileContents: rpcStub('getFileContents'),
@@ -7,6 +7,16 @@ export interface IPeerDevice {
7
7
  listChanges(filter?: DataFilter<IChange>, opts?: IDataQueryParams<IChange>): Promise<IChange[]>;
8
8
  getNetworkInfo(): Promise<INetworkInfo>;
9
9
  notifyOfChanges(deviceId: string, timestampLastApplied: number): Promise<void>;
10
+ sendDeviceMessage(message: IDeviceMessage): Promise<any>;
11
+ }
12
+ export interface IDeviceMessage {
13
+ deviceMessageId: string;
14
+ fromDeviceId: string;
15
+ toDeviceId: string;
16
+ dataContextId: string;
17
+ ttl: number;
18
+ payload: any;
19
+ signature: string;
10
20
  }
11
21
  export interface INetworkInfo {
12
22
  deviceId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"