@insureco/relay 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -73,19 +73,29 @@ interface ApiResponse<T> {
73
73
  }
74
74
  /** Configuration for the RelayClient */
75
75
  interface RelayClientConfig {
76
- /** Shared JWT secret for minting service tokens (required when using Janus) */
77
- jwtSecret?: string;
78
- /** Program/org ID included in the JWT (default: derived from service name) */
79
- programId?: string;
76
+ /**
77
+ * Async function returning a Bearer token (e.g. Bio-ID client_credentials).
78
+ * This is the recommended auth method works with both gateway (Janus) and direct modes.
79
+ * In gateway mode, the builder auto-provisions BIO_CLIENT_ID + BIO_CLIENT_SECRET.
80
+ */
81
+ accessTokenFn?: () => Promise<string>;
80
82
  /** Janus gateway URL (default: http://janus.janus-prod.svc.cluster.local:3000) */
81
83
  janusUrl?: string;
82
- /** Direct relay URL for local dev (bypasses Janus, uses X-Internal-Key auth) */
84
+ /** Direct relay URL for local dev (bypasses Janus) */
83
85
  relayUrl?: string;
84
- /** Internal API key for direct relay access */
86
+ /** Internal API key for direct relay access (local dev only) */
85
87
  internalKey?: string;
86
- /** Async function returning a Bearer token (e.g. Bio-ID client_credentials). Used in direct mode. */
87
- accessTokenFn?: () => Promise<string>;
88
- /** JWT token lifetime in seconds (default: 300 = 5 minutes) */
88
+ /**
89
+ * @deprecated Use accessTokenFn with Bio-ID client_credentials instead.
90
+ * Shared JWT secret for minting service tokens.
91
+ */
92
+ jwtSecret?: string;
93
+ /**
94
+ * @deprecated Use accessTokenFn with Bio-ID client_credentials instead.
95
+ * Program/org ID included in the JWT.
96
+ */
97
+ programId?: string;
98
+ /** JWT token lifetime in seconds (default: 300). Only used with legacy jwtSecret auth. */
89
99
  tokenTtlSeconds?: number;
90
100
  /**
91
101
  * Number of retry attempts on transient failures (default: 2).
@@ -96,7 +106,7 @@ interface RelayClientConfig {
96
106
  retries?: number;
97
107
  /** Request timeout in milliseconds (default: 10000) */
98
108
  timeoutMs?: number;
99
- /** Source service name for metadata (default: programId) */
109
+ /** Source service name for metadata (default: programId or 'unknown') */
100
110
  sourceService?: string;
101
111
  }
102
112
  /** Base options shared by sendEmail and sendSMS */
@@ -134,7 +144,10 @@ declare class RelayClient {
134
144
  /**
135
145
  * Create a RelayClient from environment variables.
136
146
  *
137
- * Reads: JANUS_URL, JWT_SECRET, PROGRAM_ID, RELAY_DIRECT_URL, RELAY_INTERNAL_KEY
147
+ * Priority:
148
+ * 1. RELAY_DIRECT_URL — local dev (direct to relay, optional RELAY_INTERNAL_KEY)
149
+ * 2. BIO_CLIENT_ID + BIO_CLIENT_SECRET — production gateway (Bio-ID token through Janus)
150
+ * 3. JWT_SECRET + PROGRAM_ID — legacy gateway (self-signed JWT through Janus)
138
151
  */
139
152
  static fromEnv(): RelayClient;
140
153
  /**
package/dist/index.d.ts CHANGED
@@ -73,19 +73,29 @@ interface ApiResponse<T> {
73
73
  }
74
74
  /** Configuration for the RelayClient */
75
75
  interface RelayClientConfig {
76
- /** Shared JWT secret for minting service tokens (required when using Janus) */
77
- jwtSecret?: string;
78
- /** Program/org ID included in the JWT (default: derived from service name) */
79
- programId?: string;
76
+ /**
77
+ * Async function returning a Bearer token (e.g. Bio-ID client_credentials).
78
+ * This is the recommended auth method works with both gateway (Janus) and direct modes.
79
+ * In gateway mode, the builder auto-provisions BIO_CLIENT_ID + BIO_CLIENT_SECRET.
80
+ */
81
+ accessTokenFn?: () => Promise<string>;
80
82
  /** Janus gateway URL (default: http://janus.janus-prod.svc.cluster.local:3000) */
81
83
  janusUrl?: string;
82
- /** Direct relay URL for local dev (bypasses Janus, uses X-Internal-Key auth) */
84
+ /** Direct relay URL for local dev (bypasses Janus) */
83
85
  relayUrl?: string;
84
- /** Internal API key for direct relay access */
86
+ /** Internal API key for direct relay access (local dev only) */
85
87
  internalKey?: string;
86
- /** Async function returning a Bearer token (e.g. Bio-ID client_credentials). Used in direct mode. */
87
- accessTokenFn?: () => Promise<string>;
88
- /** JWT token lifetime in seconds (default: 300 = 5 minutes) */
88
+ /**
89
+ * @deprecated Use accessTokenFn with Bio-ID client_credentials instead.
90
+ * Shared JWT secret for minting service tokens.
91
+ */
92
+ jwtSecret?: string;
93
+ /**
94
+ * @deprecated Use accessTokenFn with Bio-ID client_credentials instead.
95
+ * Program/org ID included in the JWT.
96
+ */
97
+ programId?: string;
98
+ /** JWT token lifetime in seconds (default: 300). Only used with legacy jwtSecret auth. */
89
99
  tokenTtlSeconds?: number;
90
100
  /**
91
101
  * Number of retry attempts on transient failures (default: 2).
@@ -96,7 +106,7 @@ interface RelayClientConfig {
96
106
  retries?: number;
97
107
  /** Request timeout in milliseconds (default: 10000) */
98
108
  timeoutMs?: number;
99
- /** Source service name for metadata (default: programId) */
109
+ /** Source service name for metadata (default: programId or 'unknown') */
100
110
  sourceService?: string;
101
111
  }
102
112
  /** Base options shared by sendEmail and sendSMS */
@@ -134,7 +144,10 @@ declare class RelayClient {
134
144
  /**
135
145
  * Create a RelayClient from environment variables.
136
146
  *
137
- * Reads: JANUS_URL, JWT_SECRET, PROGRAM_ID, RELAY_DIRECT_URL, RELAY_INTERNAL_KEY
147
+ * Priority:
148
+ * 1. RELAY_DIRECT_URL — local dev (direct to relay, optional RELAY_INTERNAL_KEY)
149
+ * 2. BIO_CLIENT_ID + BIO_CLIENT_SECRET — production gateway (Bio-ID token through Janus)
150
+ * 3. JWT_SECRET + PROGRAM_ID — legacy gateway (self-signed JWT through Janus)
138
151
  */
139
152
  static fromEnv(): RelayClient;
140
153
  /**
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ function isTokenExpired(token, bufferSeconds = 30) {
66
66
  var DEFAULT_JANUS_URL = "http://janus.janus-prod.svc.cluster.local:3000";
67
67
  var RELAY_API_PATH = "/api/relay";
68
68
  var DEFAULT_TIMEOUT_MS = 1e4;
69
+ var DEFAULT_BIO_ID_URL = "https://bio.tawa.insureco.io";
69
70
  var RelayError = class extends Error {
70
71
  constructor(message, statusCode, code, details) {
71
72
  super(message);
@@ -79,12 +80,15 @@ var RelayClient = class _RelayClient {
79
80
  config;
80
81
  cachedToken = null;
81
82
  constructor(config) {
82
- if (!config.jwtSecret && !config.relayUrl) {
83
+ const hasAccessTokenFn = typeof config.accessTokenFn === "function";
84
+ const hasJwtSecret = !!config.jwtSecret;
85
+ const hasRelayUrl = !!config.relayUrl;
86
+ if (!hasAccessTokenFn && !hasJwtSecret && !hasRelayUrl) {
83
87
  throw new Error(
84
- "iec-relay: Either jwtSecret (for Janus) or relayUrl (for direct) is required"
88
+ "iec-relay: Provide accessTokenFn (recommended), jwtSecret (legacy), or relayUrl (local dev)"
85
89
  );
86
90
  }
87
- if (config.jwtSecret && !config.programId) {
91
+ if (hasJwtSecret && !config.programId) {
88
92
  throw new Error("iec-relay: programId is required when using jwtSecret");
89
93
  }
90
94
  this.config = {
@@ -98,29 +102,71 @@ var RelayClient = class _RelayClient {
98
102
  /**
99
103
  * Create a RelayClient from environment variables.
100
104
  *
101
- * Reads: JANUS_URL, JWT_SECRET, PROGRAM_ID, RELAY_DIRECT_URL, RELAY_INTERNAL_KEY
105
+ * Priority:
106
+ * 1. RELAY_DIRECT_URL — local dev (direct to relay, optional RELAY_INTERNAL_KEY)
107
+ * 2. BIO_CLIENT_ID + BIO_CLIENT_SECRET — production gateway (Bio-ID token through Janus)
108
+ * 3. JWT_SECRET + PROGRAM_ID — legacy gateway (self-signed JWT through Janus)
102
109
  */
103
110
  static fromEnv() {
104
111
  const relayUrl = process.env.RELAY_DIRECT_URL;
105
- const internalKey = process.env.RELAY_INTERNAL_KEY;
106
112
  if (relayUrl) {
107
113
  return new _RelayClient({
108
114
  relayUrl,
109
- internalKey,
115
+ internalKey: process.env.RELAY_INTERNAL_KEY,
110
116
  sourceService: process.env.PROGRAM_ID ?? process.env.SERVICE_NAME
111
117
  });
112
118
  }
119
+ const bioClientId = process.env.BIO_CLIENT_ID;
120
+ const bioClientSecret = process.env.BIO_CLIENT_SECRET;
121
+ if (bioClientId && bioClientSecret) {
122
+ const bioIdUrl = process.env.BIO_ID_URL || DEFAULT_BIO_ID_URL;
123
+ let cachedBioToken = null;
124
+ let bioTokenExpiresAt = 0;
125
+ const accessTokenFn = async () => {
126
+ const now = Date.now();
127
+ if (cachedBioToken && now < bioTokenExpiresAt) {
128
+ return cachedBioToken;
129
+ }
130
+ const response = await fetch(`${bioIdUrl}/api/oauth/token`, {
131
+ method: "POST",
132
+ headers: { "Content-Type": "application/json" },
133
+ body: JSON.stringify({
134
+ grant_type: "client_credentials",
135
+ client_id: bioClientId,
136
+ client_secret: bioClientSecret
137
+ })
138
+ });
139
+ if (!response.ok) {
140
+ const body = await response.text();
141
+ throw new RelayError(
142
+ `Bio-ID token request failed (${response.status}): ${body}`,
143
+ response.status,
144
+ "AUTH_ERROR"
145
+ );
146
+ }
147
+ const data = await response.json();
148
+ cachedBioToken = data.access_token;
149
+ const expiresIn = (data.expires_in || 900) * 1e3;
150
+ bioTokenExpiresAt = now + expiresIn - 3e4;
151
+ return cachedBioToken;
152
+ };
153
+ return new _RelayClient({
154
+ accessTokenFn,
155
+ janusUrl: process.env.JANUS_URL,
156
+ sourceService: process.env.SERVICE_NAME ?? process.env.PROGRAM_ID
157
+ });
158
+ }
113
159
  const jwtSecret = process.env.JWT_SECRET;
114
- if (!jwtSecret) {
115
- throw new Error(
116
- "iec-relay: Set JWT_SECRET + PROGRAM_ID (for Janus) or RELAY_DIRECT_URL (for direct)"
117
- );
160
+ if (jwtSecret) {
161
+ return new _RelayClient({
162
+ jwtSecret,
163
+ programId: process.env.PROGRAM_ID,
164
+ janusUrl: process.env.JANUS_URL
165
+ });
118
166
  }
119
- return new _RelayClient({
120
- jwtSecret,
121
- programId: process.env.PROGRAM_ID,
122
- janusUrl: process.env.JANUS_URL
123
- });
167
+ throw new Error(
168
+ "iec-relay: No auth configured. Set BIO_CLIENT_ID + BIO_CLIENT_SECRET (recommended), JWT_SECRET + PROGRAM_ID (legacy), or RELAY_DIRECT_URL (local dev)"
169
+ );
124
170
  }
125
171
  /**
126
172
  * Send an email through InsureRelay.
@@ -323,6 +369,10 @@ var RelayClient = class _RelayClient {
323
369
  }
324
370
  const baseUrl = this.config.janusUrl ?? DEFAULT_JANUS_URL;
325
371
  const url = `${baseUrl}${RELAY_API_PATH}${path}`;
372
+ if (this.config.accessTokenFn) {
373
+ headers["Authorization"] = `Bearer ${await this.config.accessTokenFn()}`;
374
+ return { url, headers };
375
+ }
326
376
  headers["Authorization"] = `Bearer ${this.getToken()}`;
327
377
  return { url, headers };
328
378
  }
@@ -332,7 +382,7 @@ var RelayClient = class _RelayClient {
332
382
  }
333
383
  if (!this.config.jwtSecret || !this.config.programId) {
334
384
  throw new RelayError(
335
- "jwtSecret and programId are required for Janus auth",
385
+ "jwtSecret and programId are required for legacy Janus auth",
336
386
  500,
337
387
  "CONFIG_ERROR"
338
388
  );
package/dist/index.mjs CHANGED
@@ -37,6 +37,7 @@ function isTokenExpired(token, bufferSeconds = 30) {
37
37
  var DEFAULT_JANUS_URL = "http://janus.janus-prod.svc.cluster.local:3000";
38
38
  var RELAY_API_PATH = "/api/relay";
39
39
  var DEFAULT_TIMEOUT_MS = 1e4;
40
+ var DEFAULT_BIO_ID_URL = "https://bio.tawa.insureco.io";
40
41
  var RelayError = class extends Error {
41
42
  constructor(message, statusCode, code, details) {
42
43
  super(message);
@@ -50,12 +51,15 @@ var RelayClient = class _RelayClient {
50
51
  config;
51
52
  cachedToken = null;
52
53
  constructor(config) {
53
- if (!config.jwtSecret && !config.relayUrl) {
54
+ const hasAccessTokenFn = typeof config.accessTokenFn === "function";
55
+ const hasJwtSecret = !!config.jwtSecret;
56
+ const hasRelayUrl = !!config.relayUrl;
57
+ if (!hasAccessTokenFn && !hasJwtSecret && !hasRelayUrl) {
54
58
  throw new Error(
55
- "iec-relay: Either jwtSecret (for Janus) or relayUrl (for direct) is required"
59
+ "iec-relay: Provide accessTokenFn (recommended), jwtSecret (legacy), or relayUrl (local dev)"
56
60
  );
57
61
  }
58
- if (config.jwtSecret && !config.programId) {
62
+ if (hasJwtSecret && !config.programId) {
59
63
  throw new Error("iec-relay: programId is required when using jwtSecret");
60
64
  }
61
65
  this.config = {
@@ -69,29 +73,71 @@ var RelayClient = class _RelayClient {
69
73
  /**
70
74
  * Create a RelayClient from environment variables.
71
75
  *
72
- * Reads: JANUS_URL, JWT_SECRET, PROGRAM_ID, RELAY_DIRECT_URL, RELAY_INTERNAL_KEY
76
+ * Priority:
77
+ * 1. RELAY_DIRECT_URL — local dev (direct to relay, optional RELAY_INTERNAL_KEY)
78
+ * 2. BIO_CLIENT_ID + BIO_CLIENT_SECRET — production gateway (Bio-ID token through Janus)
79
+ * 3. JWT_SECRET + PROGRAM_ID — legacy gateway (self-signed JWT through Janus)
73
80
  */
74
81
  static fromEnv() {
75
82
  const relayUrl = process.env.RELAY_DIRECT_URL;
76
- const internalKey = process.env.RELAY_INTERNAL_KEY;
77
83
  if (relayUrl) {
78
84
  return new _RelayClient({
79
85
  relayUrl,
80
- internalKey,
86
+ internalKey: process.env.RELAY_INTERNAL_KEY,
81
87
  sourceService: process.env.PROGRAM_ID ?? process.env.SERVICE_NAME
82
88
  });
83
89
  }
90
+ const bioClientId = process.env.BIO_CLIENT_ID;
91
+ const bioClientSecret = process.env.BIO_CLIENT_SECRET;
92
+ if (bioClientId && bioClientSecret) {
93
+ const bioIdUrl = process.env.BIO_ID_URL || DEFAULT_BIO_ID_URL;
94
+ let cachedBioToken = null;
95
+ let bioTokenExpiresAt = 0;
96
+ const accessTokenFn = async () => {
97
+ const now = Date.now();
98
+ if (cachedBioToken && now < bioTokenExpiresAt) {
99
+ return cachedBioToken;
100
+ }
101
+ const response = await fetch(`${bioIdUrl}/api/oauth/token`, {
102
+ method: "POST",
103
+ headers: { "Content-Type": "application/json" },
104
+ body: JSON.stringify({
105
+ grant_type: "client_credentials",
106
+ client_id: bioClientId,
107
+ client_secret: bioClientSecret
108
+ })
109
+ });
110
+ if (!response.ok) {
111
+ const body = await response.text();
112
+ throw new RelayError(
113
+ `Bio-ID token request failed (${response.status}): ${body}`,
114
+ response.status,
115
+ "AUTH_ERROR"
116
+ );
117
+ }
118
+ const data = await response.json();
119
+ cachedBioToken = data.access_token;
120
+ const expiresIn = (data.expires_in || 900) * 1e3;
121
+ bioTokenExpiresAt = now + expiresIn - 3e4;
122
+ return cachedBioToken;
123
+ };
124
+ return new _RelayClient({
125
+ accessTokenFn,
126
+ janusUrl: process.env.JANUS_URL,
127
+ sourceService: process.env.SERVICE_NAME ?? process.env.PROGRAM_ID
128
+ });
129
+ }
84
130
  const jwtSecret = process.env.JWT_SECRET;
85
- if (!jwtSecret) {
86
- throw new Error(
87
- "iec-relay: Set JWT_SECRET + PROGRAM_ID (for Janus) or RELAY_DIRECT_URL (for direct)"
88
- );
131
+ if (jwtSecret) {
132
+ return new _RelayClient({
133
+ jwtSecret,
134
+ programId: process.env.PROGRAM_ID,
135
+ janusUrl: process.env.JANUS_URL
136
+ });
89
137
  }
90
- return new _RelayClient({
91
- jwtSecret,
92
- programId: process.env.PROGRAM_ID,
93
- janusUrl: process.env.JANUS_URL
94
- });
138
+ throw new Error(
139
+ "iec-relay: No auth configured. Set BIO_CLIENT_ID + BIO_CLIENT_SECRET (recommended), JWT_SECRET + PROGRAM_ID (legacy), or RELAY_DIRECT_URL (local dev)"
140
+ );
95
141
  }
96
142
  /**
97
143
  * Send an email through InsureRelay.
@@ -294,6 +340,10 @@ var RelayClient = class _RelayClient {
294
340
  }
295
341
  const baseUrl = this.config.janusUrl ?? DEFAULT_JANUS_URL;
296
342
  const url = `${baseUrl}${RELAY_API_PATH}${path}`;
343
+ if (this.config.accessTokenFn) {
344
+ headers["Authorization"] = `Bearer ${await this.config.accessTokenFn()}`;
345
+ return { url, headers };
346
+ }
297
347
  headers["Authorization"] = `Bearer ${this.getToken()}`;
298
348
  return { url, headers };
299
349
  }
@@ -303,7 +353,7 @@ var RelayClient = class _RelayClient {
303
353
  }
304
354
  if (!this.config.jwtSecret || !this.config.programId) {
305
355
  throw new RelayError(
306
- "jwtSecret and programId are required for Janus auth",
356
+ "jwtSecret and programId are required for legacy Janus auth",
307
357
  500,
308
358
  "CONFIG_ERROR"
309
359
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insureco/relay",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "SDK for sending email and SMS through InsureRelay on the Tawa platform",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",