@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
@@ -102,6 +102,8 @@ class RPCClientManager {
102
102
  this.rpcBound = false;
103
103
  this.trackerEventHandler = null;
104
104
  this.trackerWithEvents = null;
105
+ this.boundPhysicalPath = null;
106
+ this.rpcRecipient = null;
105
107
  this.setupTrackerEventHandler();
106
108
  }
107
109
  setupTrackerEventHandler() {
@@ -253,6 +255,8 @@ class RPCClientManager {
253
255
  this.rpcBound = false;
254
256
  this.rpcReplyAddress = null;
255
257
  this.rpcListenerAddress = null;
258
+ this.boundPhysicalPath = null;
259
+ this.rpcRecipient = null;
256
260
  for (const [requestId, pending] of Array.from(this.pending.entries())) {
257
261
  if (pending.timer) {
258
262
  clearTimeout(pending.timer);
@@ -270,17 +274,20 @@ class RPCClientManager {
270
274
  this.pendingByEnvelopeId.clear();
271
275
  }
272
276
  async ensureReplyListener() {
273
- if (this.rpcBound) {
277
+ const currentPhysicalPath = this.getPhysicalPath();
278
+ if (this.rpcBound && this.boundPhysicalPath === currentPhysicalPath) {
274
279
  return;
275
280
  }
276
- const recipient = `__rpc__${(0, core_1.generateId)()}`;
277
- this.rpcReplyAddress = (0, core_1.formatAddress)(recipient, this.getPhysicalPath());
281
+ const recipient = this.rpcRecipient || `__rpc__${(0, core_1.generateId)()}`;
282
+ this.rpcRecipient = recipient;
283
+ this.rpcReplyAddress = (0, core_1.formatAddress)(recipient, currentPhysicalPath);
278
284
  const handler = async (envelope, _context) => {
279
285
  await this.handleReplyEnvelope(envelope);
280
286
  return null;
281
287
  };
282
288
  this.rpcListenerAddress = await this.listenCallback(recipient, handler);
283
289
  this.rpcBound = true;
290
+ this.boundPhysicalPath = currentPhysicalPath;
284
291
  logger.debug('rpc_reply_listener_bound', {
285
292
  reply_recipient: recipient,
286
293
  reply_address: this.rpcReplyAddress?.toString(),
@@ -0,0 +1,55 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TokenSubjectNodeIdentityPolicyFactory = exports.FACTORY_META = void 0;
37
+ const node_identity_policy_factory_js_1 = require("./node-identity-policy-factory.js");
38
+ exports.FACTORY_META = {
39
+ base: node_identity_policy_factory_js_1.NODE_IDENTITY_POLICY_FACTORY_BASE_TYPE,
40
+ key: 'TokenSubjectNodeIdentityPolicy',
41
+ };
42
+ class TokenSubjectNodeIdentityPolicyFactory extends node_identity_policy_factory_js_1.NodeIdentityPolicyFactory {
43
+ constructor() {
44
+ super(...arguments);
45
+ this.type = 'TokenSubjectNodeIdentityPolicy';
46
+ }
47
+ async create(
48
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
49
+ _config) {
50
+ const { TokenSubjectNodeIdentityPolicy } = await Promise.resolve().then(() => __importStar(require('./token-subject-node-identity-policy.js')));
51
+ return new TokenSubjectNodeIdentityPolicy();
52
+ }
53
+ }
54
+ exports.TokenSubjectNodeIdentityPolicyFactory = TokenSubjectNodeIdentityPolicyFactory;
55
+ exports.default = TokenSubjectNodeIdentityPolicyFactory;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenSubjectNodeIdentityPolicy = void 0;
4
+ const core_1 = require("@naylence/core");
5
+ const token_provider_factory_js_1 = require("../security/auth/token-provider-factory.js");
6
+ const token_provider_js_1 = require("../security/auth/token-provider.js");
7
+ const logging_js_1 = require("../util/logging.js");
8
+ const logger = (0, logging_js_1.getLogger)('naylence.fame.node.token_subject_node_identity_policy');
9
+ class TokenSubjectNodeIdentityPolicy {
10
+ async resolveInitialNodeId(context) {
11
+ if (context.configuredId) {
12
+ return context.configuredId;
13
+ }
14
+ if (context.persistedId) {
15
+ return context.persistedId;
16
+ }
17
+ return (0, core_1.generateIdAsync)();
18
+ }
19
+ async resolveAdmissionNodeId(context) {
20
+ logger.debug('resolve_admission_node_id_start', {
21
+ grantsCount: context.grants?.length ?? 0,
22
+ currentNodeId: context.currentNodeId,
23
+ });
24
+ if (context.grants && context.grants.length > 0) {
25
+ for (const grant of context.grants) {
26
+ try {
27
+ const auth = grant.auth;
28
+ if (!auth) {
29
+ logger.debug('skipping_grant_no_auth', { grantType: grant.type });
30
+ continue;
31
+ }
32
+ const tokenProviderConfig = (auth.tokenProvider ??
33
+ auth.token_provider);
34
+ if (!tokenProviderConfig ||
35
+ typeof tokenProviderConfig.type !== 'string') {
36
+ logger.debug('skipping_grant_invalid_token_provider_config', {
37
+ grantType: grant.type,
38
+ config: tokenProviderConfig,
39
+ });
40
+ continue;
41
+ }
42
+ logger.debug('creating_token_provider', {
43
+ type: tokenProviderConfig.type,
44
+ });
45
+ const provider = await token_provider_factory_js_1.TokenProviderFactory.createTokenProvider(tokenProviderConfig);
46
+ const isExposing = (0, token_provider_js_1.isIdentityExposingTokenProvider)(provider);
47
+ logger.debug('token_provider_created', {
48
+ type: tokenProviderConfig.type,
49
+ isIdentityExposing: isExposing,
50
+ });
51
+ if (isExposing) {
52
+ const identity = await provider.getIdentity();
53
+ logger.debug('retrieved_identity', { identity });
54
+ if (identity && identity.subject) {
55
+ const hashedSubject = await (0, core_1.generateIdAsync)({
56
+ mode: 'fingerprint',
57
+ material: identity.subject,
58
+ length: 8,
59
+ });
60
+ const newNodeId = `${hashedSubject}-${context.currentNodeId}`;
61
+ logger.info('resolved_identity_from_token', {
62
+ subject: identity.subject,
63
+ hashedSubject,
64
+ newNodeId,
65
+ });
66
+ return newNodeId;
67
+ }
68
+ else {
69
+ logger.debug('identity_missing_subject', { identity });
70
+ }
71
+ }
72
+ }
73
+ catch (err) {
74
+ logger.warning('failed_to_extract_identity_from_grant', { error: err });
75
+ }
76
+ }
77
+ }
78
+ else {
79
+ logger.debug('no_grants_available');
80
+ }
81
+ return context.currentNodeId;
82
+ }
83
+ }
84
+ exports.TokenSubjectNodeIdentityPolicy = TokenSubjectNodeIdentityPolicy;
@@ -103,6 +103,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
103
103
  super();
104
104
  this.readyEvent = new async_event_js_1.AsyncEvent();
105
105
  this.stopEvent = new async_event_js_1.AsyncEvent();
106
+ this.wakeEvent = new async_event_js_1.AsyncEvent();
106
107
  this.queueEvent = new async_event_js_1.AsyncEvent();
107
108
  this.currentStopSubtasks = null;
108
109
  this.messageQueue = [];
@@ -114,6 +115,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
114
115
  this.hadSuccessfulAttach = false;
115
116
  this.lastConnectorState = null;
116
117
  this.connectEpoch = 0;
118
+ this._visibilityHandler = null;
117
119
  const options = normalizeOptions(optionsInput);
118
120
  this.node = options.node;
119
121
  this.attachClient = options.attachClient;
@@ -133,6 +135,34 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
133
135
  get systemId() {
134
136
  return this.targetSystemId;
135
137
  }
138
+ setupVisibilityListener() {
139
+ logger.debug('setup_visibility_listener_called', {
140
+ has_document: typeof document !== 'undefined',
141
+ });
142
+ if (typeof document !== 'undefined' && document.addEventListener) {
143
+ this._visibilityHandler = () => {
144
+ logger.debug('visibility_change_event_fired', {
145
+ state: document.visibilityState,
146
+ });
147
+ if (document.visibilityState === 'visible') {
148
+ logger.debug('visibility_change_detected_waking_up');
149
+ this.wakeEvent.set();
150
+ }
151
+ };
152
+ document.addEventListener('visibilitychange', this._visibilityHandler);
153
+ }
154
+ else {
155
+ logger.debug('setup_visibility_listener_skipped_no_document');
156
+ }
157
+ }
158
+ teardownVisibilityListener() {
159
+ if (this._visibilityHandler &&
160
+ typeof document !== 'undefined' &&
161
+ document.removeEventListener) {
162
+ document.removeEventListener('visibilitychange', this._visibilityHandler);
163
+ this._visibilityHandler = null;
164
+ }
165
+ }
136
166
  async start(options = {}) {
137
167
  const { waitUntilReady = true } = options;
138
168
  if (this.fsmTask) {
@@ -140,6 +170,8 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
140
170
  }
141
171
  this.stopEvent.clear();
142
172
  this.readyEvent.clear();
173
+ this.wakeEvent.clear();
174
+ this.setupVisibilityListener();
143
175
  const taskName = `upstream-fsm-${this.connectEpoch}`;
144
176
  this.fsmTask = this.spawn(() => this.fsmLoop(), { name: taskName });
145
177
  if (!waitUntilReady) {
@@ -160,6 +192,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
160
192
  }
161
193
  async stop() {
162
194
  logger.debug('upstream_session_manager_stopping');
195
+ this.teardownVisibilityListener();
163
196
  this.stopEvent.set();
164
197
  this.currentStopSubtasks?.set();
165
198
  if (this.fsmTask) {
@@ -213,11 +246,16 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
213
246
  async fsmLoop() {
214
247
  let delay = UpstreamSessionManager.BACKOFF_INITIAL;
215
248
  while (!this.stopEvent.isSet()) {
249
+ const startTime = Date.now();
216
250
  try {
217
251
  await this.connectCycle();
218
252
  delay = UpstreamSessionManager.BACKOFF_INITIAL;
219
253
  }
220
254
  catch (error) {
255
+ // Reset backoff if the connection was alive for more than 10 seconds
256
+ if (Date.now() - startTime > 10000) {
257
+ delay = UpstreamSessionManager.BACKOFF_INITIAL;
258
+ }
221
259
  if (error instanceof task_types_js_1.TaskCancelledError) {
222
260
  throw error;
223
261
  }
@@ -232,11 +270,20 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
232
270
  }
233
271
  }
234
272
  else {
235
- logger.warning('upstream_link_closed', {
236
- error: error.message,
237
- will_retry: true,
238
- exc_info: true,
239
- });
273
+ const err = error;
274
+ if (err.name === 'OAuth2PkceRedirectInitiatedError') {
275
+ logger.info('upstream_link_redirecting', {
276
+ error: err.message,
277
+ will_retry: true,
278
+ });
279
+ }
280
+ else {
281
+ logger.warning('upstream_link_closed', {
282
+ error: err.message,
283
+ will_retry: true,
284
+ exc_info: true,
285
+ });
286
+ }
240
287
  if (!this.hadSuccessfulAttach) {
241
288
  throw error;
242
289
  }
@@ -254,14 +301,40 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
254
301
  if (delaySeconds <= 0) {
255
302
  return;
256
303
  }
304
+ // If the document is visible, cap the backoff delay to improve UX
305
+ // This ensures that if the user is watching, we retry quickly (e.g. 1s)
306
+ // instead of waiting for the full exponential backoff (up to 30s).
307
+ let effectiveDelay = delaySeconds;
308
+ if (typeof document !== 'undefined' &&
309
+ document.visibilityState === 'visible') {
310
+ effectiveDelay = Math.min(delaySeconds, 1.0);
311
+ if (effectiveDelay < delaySeconds) {
312
+ logger.debug('sleep_reduced_document_visible', {
313
+ original: delaySeconds,
314
+ new: effectiveDelay,
315
+ });
316
+ }
317
+ }
318
+ if (this.wakeEvent.isSet()) {
319
+ this.wakeEvent.clear();
320
+ return;
321
+ }
257
322
  let timeout;
258
323
  const sleepPromise = new Promise((resolve) => {
259
324
  timeout = setTimeout(() => {
260
325
  timeout = undefined;
261
326
  resolve();
262
- }, delaySeconds * 1000);
327
+ }, effectiveDelay * 1000);
263
328
  });
264
- await Promise.race([sleepPromise, this.stopEvent.wait()]);
329
+ await Promise.race([
330
+ sleepPromise,
331
+ this.stopEvent.wait(),
332
+ this.wakeEvent.wait(),
333
+ ]);
334
+ if (this.wakeEvent.isSet()) {
335
+ logger.debug('sleep_interrupted_by_wake_event');
336
+ this.wakeEvent.clear();
337
+ }
265
338
  if (timeout !== undefined) {
266
339
  clearTimeout(timeout);
267
340
  }
@@ -281,7 +354,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
281
354
  return signal ? event.wait({ signal }) : event.wait();
282
355
  }
283
356
  _getLocalNodeId() {
284
- const normalized = this._normalizeNodeId(this.node.id);
357
+ const normalized = this._normalizeNodeId(this.node.provisionalId);
285
358
  if (!normalized) {
286
359
  throw new Error('UpstreamSessionManager requires node with a stable identifier');
287
360
  }
@@ -299,7 +372,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
299
372
  throw new errors_js_1.FameConnectError('Admission client is required to attach upstream');
300
373
  }
301
374
  this.connectEpoch += 1;
302
- const welcome = await this.admissionClient.hello(this.node.id, (0, core_1.generateId)(), this.requestedLogicals);
375
+ const welcome = await this.admissionClient.hello(this.node.provisionalId, (0, core_1.generateId)(), this.requestedLogicals);
303
376
  const connectionGrants = welcome.frame.connectionGrants;
304
377
  if (!connectionGrants?.length) {
305
378
  throw new Error('Welcome frame missing connection grants');
@@ -341,6 +414,11 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
341
414
  Array.isArray(welcome.frame.connectionGrants)) {
342
415
  for (const grant of welcome.frame.connectionGrants) {
343
416
  if (grant && typeof grant === 'object') {
417
+ const grantType = grant.type;
418
+ if (grantType === 'WebSocketConnectionGrant' ||
419
+ grantType === 'HttpConnectionGrant') {
420
+ continue;
421
+ }
344
422
  // Avoid duplicates by checking if grant already exists
345
423
  const isDuplicate = callbackGrants.some((existing) => JSON.stringify(existing) === JSON.stringify(grant));
346
424
  if (!isDuplicate) {
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isMaterializableTokenProvider = isMaterializableTokenProvider;
4
+ function isMaterializableTokenProvider(candidate) {
5
+ return (typeof candidate === 'object' &&
6
+ candidate !== null &&
7
+ typeof candidate.materialize ===
8
+ 'function');
9
+ }
@@ -275,6 +275,15 @@ class OAuth2PkceTokenProvider {
275
275
  constructor(rawOptions) {
276
276
  this.options = normalizeOptions(rawOptions);
277
277
  }
278
+ async materialize() {
279
+ const token = await this.getToken();
280
+ return {
281
+ type: 'StaticTokenProvider',
282
+ token: token.value,
283
+ expiresAt: token.expiresAt,
284
+ expires_at: token.expiresAt,
285
+ };
286
+ }
278
287
  async getToken() {
279
288
  if (!isBrowserEnvironment()) {
280
289
  throw new Error('OAuth2PkceTokenProvider requires a browser environment with sessionStorage support');
@@ -44,6 +44,50 @@ class StaticTokenProvider {
44
44
  async getToken() {
45
45
  return { ...this.token };
46
46
  }
47
+ async getIdentity() {
48
+ const tokenValue = this.token.value;
49
+ const parts = tokenValue.split('.');
50
+ if (parts.length !== 3) {
51
+ return undefined;
52
+ }
53
+ try {
54
+ const payloadSegment = parts[1];
55
+ // Fix padding for base64url
56
+ const padding = '='.repeat((4 - (payloadSegment.length % 4)) % 4);
57
+ const base64 = (payloadSegment + padding)
58
+ .replace(/-/g, '+')
59
+ .replace(/_/g, '/');
60
+ let jsonString;
61
+ if (typeof Buffer !== 'undefined') {
62
+ jsonString = Buffer.from(base64, 'base64').toString('utf-8');
63
+ }
64
+ else if (typeof atob === 'function') {
65
+ jsonString = atob(base64);
66
+ try {
67
+ jsonString = decodeURIComponent(jsonString
68
+ .split('')
69
+ .map(function (c) {
70
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
71
+ })
72
+ .join(''));
73
+ }
74
+ catch {
75
+ // ignore
76
+ }
77
+ }
78
+ else {
79
+ return undefined;
80
+ }
81
+ const payload = JSON.parse(jsonString);
82
+ if (payload && typeof payload.sub === 'string') {
83
+ return { subject: payload.sub, claims: payload };
84
+ }
85
+ }
86
+ catch {
87
+ // ignore decoding errors
88
+ }
89
+ return undefined;
90
+ }
47
91
  }
48
92
  exports.StaticTokenProvider = StaticTokenProvider;
49
93
  function normalizeOptions(input) {
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isTokenProvider = isTokenProvider;
4
+ exports.isIdentityExposingTokenProvider = isIdentityExposingTokenProvider;
4
5
  function isTokenProvider(candidate) {
5
6
  return (typeof candidate === 'object' &&
6
7
  candidate !== null &&
7
8
  typeof candidate.getToken === 'function');
8
9
  }
10
+ function isIdentityExposingTokenProvider(candidate) {
11
+ return (isTokenProvider(candidate) &&
12
+ typeof candidate.getIdentity ===
13
+ 'function');
14
+ }
@@ -380,7 +380,7 @@ class DefaultSecurityManager {
380
380
  async onNodeInitialized(node) {
381
381
  this._node = node;
382
382
  logger.debug('security_manager_node_initialized', {
383
- node_id: node.id,
383
+ node_id: node.provisionalId,
384
384
  has_node_crypto_provider: Boolean(node.cryptoProvider),
385
385
  provider_type: node.cryptoProvider
386
386
  ? (node.cryptoProvider.constructor?.name ?? 'unknown')
@@ -404,7 +404,9 @@ class DefaultSecurityManager {
404
404
  if (encryption && hasNodeListenerMethod(encryption, 'onNodeInitialized')) {
405
405
  await encryption.onNodeInitialized(node);
406
406
  }
407
- logger.debug('node_security_initialization_complete', { node_id: node.id });
407
+ logger.debug('node_security_initialization_complete', {
408
+ node_id: node.provisionalId,
409
+ });
408
410
  }
409
411
  async onNodeAttachToPeer(node, attachInfo, connector) {
410
412
  const attachRecord = attachInfo;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROFILE_NAME_OPEN = exports.PROFILE_NAME_GATED_CALLBACK = exports.PROFILE_NAME_GATED = exports.PROFILE_NAME_OVERLAY_CALLBACK = exports.PROFILE_NAME_OVERLAY = exports.PROFILE_NAME_STRICT_OVERLAY = exports.ENV_VAR_JWT_REVERSE_AUTH_AUDIENCE = exports.ENV_VAR_JWT_REVERSE_AUTH_TRUSTED_ISSUER = exports.ENV_VAR_HMAC_SECRET = exports.ENV_VAR_DEFAULT_ENCRYPTION_LEVEL = exports.ENV_VAR_JWKS_URL = exports.ENV_VAR_JWT_AUDIENCE = exports.ENV_VAR_JWT_ALGORITHM = exports.ENV_VAR_JWT_TRUSTED_ISSUER = exports.CREDENTIAL_PROVIDER_FACTORY_BASE_TYPE = exports.EdDSAEnvelopeSigner = exports.encodeUtf8 = exports.immutableHeaders = exports.frameDigest = exports.decodeBase64Url = exports.canonicalJson = exports.SigningConfigClass = exports.SECURITY_MANAGER_FACTORY_BASE_TYPE = exports.SECURITY_POLICY_FACTORY_BASE_TYPE = exports.KEY_STORE_FACTORY_BASE_TYPE = exports.ATTACHMENT_KEY_VALIDATOR_FACTORY_BASE_TYPE = exports.KEY_MANAGER_FACTORY_BASE_TYPE = exports.SecureChannelManagerFactory = exports.SECURE_CHANNEL_MANAGER_FACTORY_BASE_TYPE = exports.ENCRYPTION_MANAGER_FACTORY_BASE_TYPE = exports.NoopTrustStoreProvider = exports.TrustStoreProviderFactory = exports.TRUST_STORE_PROVIDER_FACTORY_BASE_TYPE = exports.CertificateManagerFactory = exports.CERTIFICATE_MANAGER_FACTORY_BASE_TYPE = exports.TokenProviderFactory = exports.TOKEN_PROVIDER_FACTORY_BASE_TYPE = exports.TokenVerifierFactory = exports.TOKEN_VERIFIER_FACTORY_BASE_TYPE = exports.TokenIssuerFactory = exports.TOKEN_ISSUER_FACTORY_BASE_TYPE = exports.AuthInjectionStrategyFactory = exports.AUTH_INJECTION_STRATEGY_FACTORY_BASE_TYPE = exports.AuthorizerFactory = exports.AUTHORIZER_FACTORY_BASE_TYPE = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  tslib_1.__exportStar(require("./auth/authorizer.js"), exports);
6
+ tslib_1.__exportStar(require("./auth/auth-identity.js"), exports);
6
7
  var authorizer_factory_js_1 = require("./auth/authorizer-factory.js");
7
8
  Object.defineProperty(exports, "AUTHORIZER_FACTORY_BASE_TYPE", { enumerable: true, get: function () { return authorizer_factory_js_1.AUTHORIZER_FACTORY_BASE_TYPE; } });
8
9
  Object.defineProperty(exports, "AuthorizerFactory", { enumerable: true, get: function () { return authorizer_factory_js_1.AuthorizerFactory; } });
@@ -95,7 +95,7 @@ class DefaultKeyManager {
95
95
  this.node = node;
96
96
  this.routingNode = isRoutingNode(node) ? node : null;
97
97
  logger.debug('key_manager_started', {
98
- node_id: this.nodeId,
98
+ node_id: node.provisionalId,
99
99
  physical_path: this.physicalPath,
100
100
  has_upstream: this.hasUpstream,
101
101
  });
@@ -278,6 +278,14 @@ class TaskSpawner {
278
278
  });
279
279
  return;
280
280
  }
281
+ // Handle PKCE redirect "errors" as info
282
+ if (error.name === 'OAuth2PkceRedirectInitiatedError') {
283
+ logger.debug('background_task_redirecting', {
284
+ task_name: taskName,
285
+ note: 'Task interrupted for PKCE redirect',
286
+ });
287
+ return;
288
+ }
281
289
  // Check if this is a retriable connection error (will be logged and retried by FSM)
282
290
  const isRetriableError = error.name === 'FameConnectError' ||
283
291
  error.message.includes('missed heartbeat') ||
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  // This file is auto-generated during build - do not edit manually
3
- // Generated from package.json version: 0.3.12
3
+ // Generated from package.json version: 0.3.13
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.VERSION = void 0;
6
6
  /**
7
7
  * The package version, injected at build time.
8
8
  * @internal
9
9
  */
10
- exports.VERSION = '0.3.12';
10
+ exports.VERSION = '0.3.13';
@@ -239,7 +239,7 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
239
239
  // Track page lifecycle events to detect browser unload/discard
240
240
  if (typeof window !== 'undefined') {
241
241
  const lifecycleLogger = (event) => {
242
- logger.info('broadcast_channel_page_lifecycle', {
242
+ logger.debug('broadcast_channel_page_lifecycle', {
243
243
  channel: this.channelName,
244
244
  connector_id: this.connectorId,
245
245
  event_type: event.type,
@@ -21,7 +21,10 @@ export const MODULES = [
21
21
  "./node/admission/direct-admission-client-factory.js",
22
22
  "./node/admission/noop-admission-client-factory.js",
23
23
  "./node/admission/welcome-service-client-factory.js",
24
+ "./node/default-node-identity-policy-factory.js",
24
25
  "./node/node-factory.js",
26
+ "./node/node-identity-policy-profile-factory.js",
27
+ "./node/token-subject-node-identity-policy-factory.js",
25
28
  "./placement/static-node-placement-strategy-factory.js",
26
29
  "./security/auth/bearer-token-header-auth-injection-strategy-factory.js",
27
30
  "./security/auth/default-authorizer-factory.js",
@@ -96,7 +99,10 @@ export const MODULE_LOADERS = {
96
99
  "./node/admission/direct-admission-client-factory.js": () => import("./node/admission/direct-admission-client-factory.js"),
97
100
  "./node/admission/noop-admission-client-factory.js": () => import("./node/admission/noop-admission-client-factory.js"),
98
101
  "./node/admission/welcome-service-client-factory.js": () => import("./node/admission/welcome-service-client-factory.js"),
102
+ "./node/default-node-identity-policy-factory.js": () => import("./node/default-node-identity-policy-factory.js"),
99
103
  "./node/node-factory.js": () => import("./node/node-factory.js"),
104
+ "./node/node-identity-policy-profile-factory.js": () => import("./node/node-identity-policy-profile-factory.js"),
105
+ "./node/token-subject-node-identity-policy-factory.js": () => import("./node/token-subject-node-identity-policy-factory.js"),
100
106
  "./placement/static-node-placement-strategy-factory.js": () => import("./placement/static-node-placement-strategy-factory.js"),
101
107
  "./security/auth/bearer-token-header-auth-injection-strategy-factory.js": () => import("./security/auth/bearer-token-header-auth-injection-strategy-factory.js"),
102
108
  "./security/auth/default-authorizer-factory.js": () => import("./security/auth/default-authorizer-factory.js"),
@@ -0,0 +1,55 @@
1
+ import { TokenProviderFactory } from '../security/auth/token-provider-factory.js';
2
+ import { isMaterializableTokenProvider } from '../security/auth/materializable-token-provider.js';
3
+ import { getLogger } from '../util/logging.js';
4
+ const logger = getLogger('naylence.fame.grants.grant_materializer');
5
+ export class GrantMaterializer {
6
+ static async materialize(grant) {
7
+ const candidate = grant;
8
+ const auth = candidate.auth;
9
+ if (!auth) {
10
+ return grant;
11
+ }
12
+ const tokenProviderConfig = (auth.tokenProvider ??
13
+ auth.token_provider);
14
+ if (!tokenProviderConfig || typeof tokenProviderConfig.type !== 'string') {
15
+ return grant;
16
+ }
17
+ try {
18
+ const provider = await TokenProviderFactory.createTokenProvider(tokenProviderConfig);
19
+ if (isMaterializableTokenProvider(provider)) {
20
+ const materializedConfig = await provider.materialize();
21
+ if (materializedConfig) {
22
+ logger.debug('grant_materialized', {
23
+ grantType: candidate.type,
24
+ providerType: tokenProviderConfig.type,
25
+ });
26
+ const newAuth = { ...auth };
27
+ if ('tokenProvider' in newAuth) {
28
+ newAuth.tokenProvider = materializedConfig;
29
+ }
30
+ if ('token_provider' in newAuth) {
31
+ newAuth.token_provider = materializedConfig;
32
+ }
33
+ return {
34
+ ...grant,
35
+ auth: newAuth,
36
+ };
37
+ }
38
+ }
39
+ }
40
+ catch (error) {
41
+ if (error &&
42
+ error.name === 'OAuth2PkceRedirectInitiatedError') {
43
+ logger.info('grant_materialization_redirecting', {
44
+ grantType: candidate.type,
45
+ });
46
+ throw error;
47
+ }
48
+ logger.warning('grant_materialization_failed', {
49
+ error: error instanceof Error ? error.message : String(error),
50
+ grantType: candidate.type,
51
+ });
52
+ }
53
+ return grant;
54
+ }
55
+ }
@@ -234,11 +234,13 @@ export class AdmissionProfileFactory extends AdmissionClientFactory {
234
234
  super(...arguments);
235
235
  this.type = 'AdmissionProfile';
236
236
  }
237
- async create(config) {
237
+ async create(config, ...factoryArgs) {
238
238
  const normalized = normalizeConfig(config);
239
239
  const profileConfig = resolveProfileConfig(normalized.profile);
240
240
  logger.debug('enabling_admission_profile', { profile: normalized.profile });
241
- return AdmissionClientFactory.createAdmissionClient(profileConfig);
241
+ return AdmissionClientFactory.createAdmissionClient(profileConfig, {
242
+ factoryArgs,
243
+ });
242
244
  }
243
245
  }
244
246
  function normalizeConfig(config) {