@naylence/runtime 0.3.12 → 0.3.13

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.
Files changed (94) hide show
  1. package/dist/browser/index.cjs +1479 -926
  2. package/dist/browser/index.mjs +1472 -927
  3. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +1 -1
  4. package/dist/cjs/naylence/fame/factory-manifest.js +6 -0
  5. package/dist/cjs/naylence/fame/grants/grant-materializer.js +59 -0
  6. package/dist/cjs/naylence/fame/node/admission/admission-profile-factory.js +4 -2
  7. package/dist/cjs/naylence/fame/node/admission/direct-admission-client-factory.js +3 -1
  8. package/dist/cjs/naylence/fame/node/admission/direct-admission-client.js +12 -9
  9. package/dist/cjs/naylence/fame/node/default-node-identity-policy-factory.js +21 -0
  10. package/dist/cjs/naylence/fame/node/default-node-identity-policy.js +60 -0
  11. package/dist/cjs/naylence/fame/node/factory-commons.js +31 -7
  12. package/dist/cjs/naylence/fame/node/index.js +11 -1
  13. package/dist/cjs/naylence/fame/node/node-config.js +4 -0
  14. package/dist/cjs/naylence/fame/node/node-identity-policy-factory.js +22 -0
  15. package/dist/cjs/naylence/fame/node/node-identity-policy-profile-factory.js +67 -0
  16. package/dist/cjs/naylence/fame/node/node-identity-policy.js +2 -0
  17. package/dist/cjs/naylence/fame/node/node.js +45 -9
  18. package/dist/cjs/naylence/fame/node/root-session-manager.js +1 -11
  19. package/dist/cjs/naylence/fame/node/rpc-client-manager.js +10 -3
  20. package/dist/cjs/naylence/fame/node/token-subject-node-identity-policy-factory.js +55 -0
  21. package/dist/cjs/naylence/fame/node/token-subject-node-identity-policy.js +84 -0
  22. package/dist/cjs/naylence/fame/node/upstream-session-manager.js +87 -9
  23. package/dist/cjs/naylence/fame/security/auth/auth-identity.js +2 -0
  24. package/dist/cjs/naylence/fame/security/auth/materializable-token-provider.js +9 -0
  25. package/dist/cjs/naylence/fame/security/auth/oauth2-pkce-token-provider.js +9 -0
  26. package/dist/cjs/naylence/fame/security/auth/static-token-provider.js +44 -0
  27. package/dist/cjs/naylence/fame/security/auth/token-provider.js +6 -0
  28. package/dist/cjs/naylence/fame/security/default-security-manager.js +4 -2
  29. package/dist/cjs/naylence/fame/security/index.js +1 -0
  30. package/dist/cjs/naylence/fame/security/keys/default-key-manager.js +1 -1
  31. package/dist/cjs/naylence/fame/util/task-spawner.js +8 -0
  32. package/dist/cjs/version.js +2 -2
  33. package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +1 -1
  34. package/dist/esm/naylence/fame/factory-manifest.js +6 -0
  35. package/dist/esm/naylence/fame/grants/grant-materializer.js +55 -0
  36. package/dist/esm/naylence/fame/node/admission/admission-profile-factory.js +4 -2
  37. package/dist/esm/naylence/fame/node/admission/direct-admission-client-factory.js +3 -1
  38. package/dist/esm/naylence/fame/node/admission/direct-admission-client.js +13 -10
  39. package/dist/esm/naylence/fame/node/default-node-identity-policy-factory.js +17 -0
  40. package/dist/esm/naylence/fame/node/default-node-identity-policy.js +56 -0
  41. package/dist/esm/naylence/fame/node/factory-commons.js +31 -7
  42. package/dist/esm/naylence/fame/node/index.js +7 -0
  43. package/dist/esm/naylence/fame/node/node-config.js +4 -0
  44. package/dist/esm/naylence/fame/node/node-identity-policy-factory.js +18 -0
  45. package/dist/esm/naylence/fame/node/node-identity-policy-profile-factory.js +63 -0
  46. package/dist/esm/naylence/fame/node/node-identity-policy.js +1 -0
  47. package/dist/esm/naylence/fame/node/node.js +45 -9
  48. package/dist/esm/naylence/fame/node/root-session-manager.js +1 -11
  49. package/dist/esm/naylence/fame/node/rpc-client-manager.js +10 -3
  50. package/dist/esm/naylence/fame/node/token-subject-node-identity-policy-factory.js +18 -0
  51. package/dist/esm/naylence/fame/node/token-subject-node-identity-policy.js +80 -0
  52. package/dist/esm/naylence/fame/node/upstream-session-manager.js +87 -9
  53. package/dist/esm/naylence/fame/security/auth/auth-identity.js +1 -0
  54. package/dist/esm/naylence/fame/security/auth/materializable-token-provider.js +6 -0
  55. package/dist/esm/naylence/fame/security/auth/oauth2-pkce-token-provider.js +9 -0
  56. package/dist/esm/naylence/fame/security/auth/static-token-provider.js +44 -0
  57. package/dist/esm/naylence/fame/security/auth/token-provider.js +5 -0
  58. package/dist/esm/naylence/fame/security/default-security-manager.js +4 -2
  59. package/dist/esm/naylence/fame/security/index.js +1 -0
  60. package/dist/esm/naylence/fame/security/keys/default-key-manager.js +1 -1
  61. package/dist/esm/naylence/fame/util/task-spawner.js +8 -0
  62. package/dist/esm/version.js +2 -2
  63. package/dist/node/index.cjs +1432 -879
  64. package/dist/node/index.mjs +1425 -880
  65. package/dist/node/node.cjs +1560 -1007
  66. package/dist/node/node.mjs +1553 -1008
  67. package/dist/types/naylence/fame/factory-manifest.d.ts +1 -1
  68. package/dist/types/naylence/fame/grants/grant-materializer.d.ts +4 -0
  69. package/dist/types/naylence/fame/node/admission/admission-profile-factory.d.ts +1 -1
  70. package/dist/types/naylence/fame/node/admission/direct-admission-client-factory.d.ts +1 -1
  71. package/dist/types/naylence/fame/node/admission/direct-admission-client.d.ts +3 -0
  72. package/dist/types/naylence/fame/node/default-node-identity-policy-factory.d.ts +15 -0
  73. package/dist/types/naylence/fame/node/default-node-identity-policy.d.ts +5 -0
  74. package/dist/types/naylence/fame/node/factory-commons.d.ts +2 -0
  75. package/dist/types/naylence/fame/node/index.d.ts +7 -0
  76. package/dist/types/naylence/fame/node/node-config.d.ts +2 -0
  77. package/dist/types/naylence/fame/node/node-identity-policy-factory.d.ts +12 -0
  78. package/dist/types/naylence/fame/node/node-identity-policy-profile-factory.d.ts +15 -0
  79. package/dist/types/naylence/fame/node/node-identity-policy.d.ts +26 -0
  80. package/dist/types/naylence/fame/node/node-like.d.ts +3 -1
  81. package/dist/types/naylence/fame/node/node.d.ts +4 -1
  82. package/dist/types/naylence/fame/node/root-session-manager.d.ts +0 -1
  83. package/dist/types/naylence/fame/node/rpc-client-manager.d.ts +2 -0
  84. package/dist/types/naylence/fame/node/token-subject-node-identity-policy-factory.d.ts +14 -0
  85. package/dist/types/naylence/fame/node/token-subject-node-identity-policy.d.ts +5 -0
  86. package/dist/types/naylence/fame/node/upstream-session-manager.d.ts +4 -0
  87. package/dist/types/naylence/fame/security/auth/auth-identity.d.ts +6 -0
  88. package/dist/types/naylence/fame/security/auth/materializable-token-provider.d.ts +12 -0
  89. package/dist/types/naylence/fame/security/auth/oauth2-pkce-token-provider.d.ts +4 -2
  90. package/dist/types/naylence/fame/security/auth/static-token-provider.d.ts +4 -2
  91. package/dist/types/naylence/fame/security/auth/token-provider.d.ts +5 -0
  92. package/dist/types/naylence/fame/security/index.d.ts +1 -0
  93. package/dist/types/version.d.ts +1 -1
  94. package/package.json +1 -1
@@ -100,6 +100,7 @@ export class UpstreamSessionManager extends TaskSpawner {
100
100
  super();
101
101
  this.readyEvent = new AsyncEvent();
102
102
  this.stopEvent = new AsyncEvent();
103
+ this.wakeEvent = new AsyncEvent();
103
104
  this.queueEvent = new AsyncEvent();
104
105
  this.currentStopSubtasks = null;
105
106
  this.messageQueue = [];
@@ -111,6 +112,7 @@ export class UpstreamSessionManager extends TaskSpawner {
111
112
  this.hadSuccessfulAttach = false;
112
113
  this.lastConnectorState = null;
113
114
  this.connectEpoch = 0;
115
+ this._visibilityHandler = null;
114
116
  const options = normalizeOptions(optionsInput);
115
117
  this.node = options.node;
116
118
  this.attachClient = options.attachClient;
@@ -130,6 +132,34 @@ export class UpstreamSessionManager extends TaskSpawner {
130
132
  get systemId() {
131
133
  return this.targetSystemId;
132
134
  }
135
+ setupVisibilityListener() {
136
+ logger.debug('setup_visibility_listener_called', {
137
+ has_document: typeof document !== 'undefined',
138
+ });
139
+ if (typeof document !== 'undefined' && document.addEventListener) {
140
+ this._visibilityHandler = () => {
141
+ logger.debug('visibility_change_event_fired', {
142
+ state: document.visibilityState,
143
+ });
144
+ if (document.visibilityState === 'visible') {
145
+ logger.debug('visibility_change_detected_waking_up');
146
+ this.wakeEvent.set();
147
+ }
148
+ };
149
+ document.addEventListener('visibilitychange', this._visibilityHandler);
150
+ }
151
+ else {
152
+ logger.debug('setup_visibility_listener_skipped_no_document');
153
+ }
154
+ }
155
+ teardownVisibilityListener() {
156
+ if (this._visibilityHandler &&
157
+ typeof document !== 'undefined' &&
158
+ document.removeEventListener) {
159
+ document.removeEventListener('visibilitychange', this._visibilityHandler);
160
+ this._visibilityHandler = null;
161
+ }
162
+ }
133
163
  async start(options = {}) {
134
164
  const { waitUntilReady = true } = options;
135
165
  if (this.fsmTask) {
@@ -137,6 +167,8 @@ export class UpstreamSessionManager extends TaskSpawner {
137
167
  }
138
168
  this.stopEvent.clear();
139
169
  this.readyEvent.clear();
170
+ this.wakeEvent.clear();
171
+ this.setupVisibilityListener();
140
172
  const taskName = `upstream-fsm-${this.connectEpoch}`;
141
173
  this.fsmTask = this.spawn(() => this.fsmLoop(), { name: taskName });
142
174
  if (!waitUntilReady) {
@@ -157,6 +189,7 @@ export class UpstreamSessionManager extends TaskSpawner {
157
189
  }
158
190
  async stop() {
159
191
  logger.debug('upstream_session_manager_stopping');
192
+ this.teardownVisibilityListener();
160
193
  this.stopEvent.set();
161
194
  this.currentStopSubtasks?.set();
162
195
  if (this.fsmTask) {
@@ -210,11 +243,16 @@ export class UpstreamSessionManager extends TaskSpawner {
210
243
  async fsmLoop() {
211
244
  let delay = UpstreamSessionManager.BACKOFF_INITIAL;
212
245
  while (!this.stopEvent.isSet()) {
246
+ const startTime = Date.now();
213
247
  try {
214
248
  await this.connectCycle();
215
249
  delay = UpstreamSessionManager.BACKOFF_INITIAL;
216
250
  }
217
251
  catch (error) {
252
+ // Reset backoff if the connection was alive for more than 10 seconds
253
+ if (Date.now() - startTime > 10000) {
254
+ delay = UpstreamSessionManager.BACKOFF_INITIAL;
255
+ }
218
256
  if (error instanceof TaskCancelledError) {
219
257
  throw error;
220
258
  }
@@ -229,11 +267,20 @@ export class UpstreamSessionManager extends TaskSpawner {
229
267
  }
230
268
  }
231
269
  else {
232
- logger.warning('upstream_link_closed', {
233
- error: error.message,
234
- will_retry: true,
235
- exc_info: true,
236
- });
270
+ const err = error;
271
+ if (err.name === 'OAuth2PkceRedirectInitiatedError') {
272
+ logger.info('upstream_link_redirecting', {
273
+ error: err.message,
274
+ will_retry: true,
275
+ });
276
+ }
277
+ else {
278
+ logger.warning('upstream_link_closed', {
279
+ error: err.message,
280
+ will_retry: true,
281
+ exc_info: true,
282
+ });
283
+ }
237
284
  if (!this.hadSuccessfulAttach) {
238
285
  throw error;
239
286
  }
@@ -251,14 +298,40 @@ export class UpstreamSessionManager extends TaskSpawner {
251
298
  if (delaySeconds <= 0) {
252
299
  return;
253
300
  }
301
+ // If the document is visible, cap the backoff delay to improve UX
302
+ // This ensures that if the user is watching, we retry quickly (e.g. 1s)
303
+ // instead of waiting for the full exponential backoff (up to 30s).
304
+ let effectiveDelay = delaySeconds;
305
+ if (typeof document !== 'undefined' &&
306
+ document.visibilityState === 'visible') {
307
+ effectiveDelay = Math.min(delaySeconds, 1.0);
308
+ if (effectiveDelay < delaySeconds) {
309
+ logger.debug('sleep_reduced_document_visible', {
310
+ original: delaySeconds,
311
+ new: effectiveDelay,
312
+ });
313
+ }
314
+ }
315
+ if (this.wakeEvent.isSet()) {
316
+ this.wakeEvent.clear();
317
+ return;
318
+ }
254
319
  let timeout;
255
320
  const sleepPromise = new Promise((resolve) => {
256
321
  timeout = setTimeout(() => {
257
322
  timeout = undefined;
258
323
  resolve();
259
- }, delaySeconds * 1000);
324
+ }, effectiveDelay * 1000);
260
325
  });
261
- await Promise.race([sleepPromise, this.stopEvent.wait()]);
326
+ await Promise.race([
327
+ sleepPromise,
328
+ this.stopEvent.wait(),
329
+ this.wakeEvent.wait(),
330
+ ]);
331
+ if (this.wakeEvent.isSet()) {
332
+ logger.debug('sleep_interrupted_by_wake_event');
333
+ this.wakeEvent.clear();
334
+ }
262
335
  if (timeout !== undefined) {
263
336
  clearTimeout(timeout);
264
337
  }
@@ -278,7 +351,7 @@ export class UpstreamSessionManager extends TaskSpawner {
278
351
  return signal ? event.wait({ signal }) : event.wait();
279
352
  }
280
353
  _getLocalNodeId() {
281
- const normalized = this._normalizeNodeId(this.node.id);
354
+ const normalized = this._normalizeNodeId(this.node.provisionalId);
282
355
  if (!normalized) {
283
356
  throw new Error('UpstreamSessionManager requires node with a stable identifier');
284
357
  }
@@ -296,7 +369,7 @@ export class UpstreamSessionManager extends TaskSpawner {
296
369
  throw new FameConnectError('Admission client is required to attach upstream');
297
370
  }
298
371
  this.connectEpoch += 1;
299
- const welcome = await this.admissionClient.hello(this.node.id, generateId(), this.requestedLogicals);
372
+ const welcome = await this.admissionClient.hello(this.node.provisionalId, generateId(), this.requestedLogicals);
300
373
  const connectionGrants = welcome.frame.connectionGrants;
301
374
  if (!connectionGrants?.length) {
302
375
  throw new Error('Welcome frame missing connection grants');
@@ -338,6 +411,11 @@ export class UpstreamSessionManager extends TaskSpawner {
338
411
  Array.isArray(welcome.frame.connectionGrants)) {
339
412
  for (const grant of welcome.frame.connectionGrants) {
340
413
  if (grant && typeof grant === 'object') {
414
+ const grantType = grant.type;
415
+ if (grantType === 'WebSocketConnectionGrant' ||
416
+ grantType === 'HttpConnectionGrant') {
417
+ continue;
418
+ }
341
419
  // Avoid duplicates by checking if grant already exists
342
420
  const isDuplicate = callbackGrants.some((existing) => JSON.stringify(existing) === JSON.stringify(grant));
343
421
  if (!isDuplicate) {
@@ -0,0 +1,6 @@
1
+ export function isMaterializableTokenProvider(candidate) {
2
+ return (typeof candidate === 'object' &&
3
+ candidate !== null &&
4
+ typeof candidate.materialize ===
5
+ 'function');
6
+ }
@@ -271,6 +271,15 @@ export class OAuth2PkceTokenProvider {
271
271
  constructor(rawOptions) {
272
272
  this.options = normalizeOptions(rawOptions);
273
273
  }
274
+ async materialize() {
275
+ const token = await this.getToken();
276
+ return {
277
+ type: 'StaticTokenProvider',
278
+ token: token.value,
279
+ expiresAt: token.expiresAt,
280
+ expires_at: token.expiresAt,
281
+ };
282
+ }
274
283
  async getToken() {
275
284
  if (!isBrowserEnvironment()) {
276
285
  throw new Error('OAuth2PkceTokenProvider requires a browser environment with sessionStorage support');
@@ -41,6 +41,50 @@ export class StaticTokenProvider {
41
41
  async getToken() {
42
42
  return { ...this.token };
43
43
  }
44
+ async getIdentity() {
45
+ const tokenValue = this.token.value;
46
+ const parts = tokenValue.split('.');
47
+ if (parts.length !== 3) {
48
+ return undefined;
49
+ }
50
+ try {
51
+ const payloadSegment = parts[1];
52
+ // Fix padding for base64url
53
+ const padding = '='.repeat((4 - (payloadSegment.length % 4)) % 4);
54
+ const base64 = (payloadSegment + padding)
55
+ .replace(/-/g, '+')
56
+ .replace(/_/g, '/');
57
+ let jsonString;
58
+ if (typeof Buffer !== 'undefined') {
59
+ jsonString = Buffer.from(base64, 'base64').toString('utf-8');
60
+ }
61
+ else if (typeof atob === 'function') {
62
+ jsonString = atob(base64);
63
+ try {
64
+ jsonString = decodeURIComponent(jsonString
65
+ .split('')
66
+ .map(function (c) {
67
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
68
+ })
69
+ .join(''));
70
+ }
71
+ catch {
72
+ // ignore
73
+ }
74
+ }
75
+ else {
76
+ return undefined;
77
+ }
78
+ const payload = JSON.parse(jsonString);
79
+ if (payload && typeof payload.sub === 'string') {
80
+ return { subject: payload.sub, claims: payload };
81
+ }
82
+ }
83
+ catch {
84
+ // ignore decoding errors
85
+ }
86
+ return undefined;
87
+ }
44
88
  }
45
89
  function normalizeOptions(input) {
46
90
  if (input === null || input === undefined) {
@@ -3,3 +3,8 @@ export function isTokenProvider(candidate) {
3
3
  candidate !== null &&
4
4
  typeof candidate.getToken === 'function');
5
5
  }
6
+ export function isIdentityExposingTokenProvider(candidate) {
7
+ return (isTokenProvider(candidate) &&
8
+ typeof candidate.getIdentity ===
9
+ 'function');
10
+ }
@@ -377,7 +377,7 @@ export class DefaultSecurityManager {
377
377
  async onNodeInitialized(node) {
378
378
  this._node = node;
379
379
  logger.debug('security_manager_node_initialized', {
380
- node_id: node.id,
380
+ node_id: node.provisionalId,
381
381
  has_node_crypto_provider: Boolean(node.cryptoProvider),
382
382
  provider_type: node.cryptoProvider
383
383
  ? (node.cryptoProvider.constructor?.name ?? 'unknown')
@@ -401,7 +401,9 @@ export class DefaultSecurityManager {
401
401
  if (encryption && hasNodeListenerMethod(encryption, 'onNodeInitialized')) {
402
402
  await encryption.onNodeInitialized(node);
403
403
  }
404
- logger.debug('node_security_initialization_complete', { node_id: node.id });
404
+ logger.debug('node_security_initialization_complete', {
405
+ node_id: node.provisionalId,
406
+ });
405
407
  }
406
408
  async onNodeAttachToPeer(node, attachInfo, connector) {
407
409
  const attachRecord = attachInfo;
@@ -1,4 +1,5 @@
1
1
  export * from './auth/authorizer.js';
2
+ export * from './auth/auth-identity.js';
2
3
  export { AUTHORIZER_FACTORY_BASE_TYPE, AuthorizerFactory, } from './auth/authorizer-factory.js';
3
4
  export * from './auth/auth-injection-strategy.js';
4
5
  export { AUTH_INJECTION_STRATEGY_FACTORY_BASE_TYPE, AuthInjectionStrategyFactory, } from './auth/auth-injection-strategy-factory.js';
@@ -92,7 +92,7 @@ export class DefaultKeyManager {
92
92
  this.node = node;
93
93
  this.routingNode = isRoutingNode(node) ? node : null;
94
94
  logger.debug('key_manager_started', {
95
- node_id: this.nodeId,
95
+ node_id: node.provisionalId,
96
96
  physical_path: this.physicalPath,
97
97
  has_upstream: this.hasUpstream,
98
98
  });
@@ -275,6 +275,14 @@ export class TaskSpawner {
275
275
  });
276
276
  return;
277
277
  }
278
+ // Handle PKCE redirect "errors" as info
279
+ if (error.name === 'OAuth2PkceRedirectInitiatedError') {
280
+ logger.debug('background_task_redirecting', {
281
+ task_name: taskName,
282
+ note: 'Task interrupted for PKCE redirect',
283
+ });
284
+ return;
285
+ }
278
286
  // Check if this is a retriable connection error (will be logged and retried by FSM)
279
287
  const isRetriableError = error.name === 'FameConnectError' ||
280
288
  error.message.includes('missed heartbeat') ||
@@ -1,7 +1,7 @@
1
1
  // This file is auto-generated during build - do not edit manually
2
- // Generated from package.json version: 0.3.12
2
+ // Generated from package.json version: 0.3.13
3
3
  /**
4
4
  * The package version, injected at build time.
5
5
  * @internal
6
6
  */
7
- export const VERSION = '0.3.12';
7
+ export const VERSION = '0.3.13';