modal 0.3.24 → 0.3.25

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 (3) hide show
  1. package/dist/index.cjs +138 -19
  2. package/dist/index.js +138 -19
  3. package/package.json +3 -1
package/dist/index.cjs CHANGED
@@ -42060,9 +42060,135 @@ function imageBuilderVersion(version) {
42060
42060
  return version || clientProfile.imageBuilderVersion || "2024.10";
42061
42061
  }
42062
42062
 
42063
+ // src/auth_token_manager.ts
42064
+ var REFRESH_WINDOW = 5 * 60;
42065
+ var DEFAULT_EXPIRY_OFFSET = 20 * 60;
42066
+ var AuthTokenManager = class {
42067
+ client;
42068
+ currentToken = "";
42069
+ tokenExpiry = 0;
42070
+ stopped = false;
42071
+ timeoutId = null;
42072
+ initialTokenPromise = null;
42073
+ constructor(client2) {
42074
+ this.client = client2;
42075
+ }
42076
+ /**
42077
+ * Returns the current cached token.
42078
+ * If the initial token fetch is still in progress, waits for it to complete.
42079
+ */
42080
+ async getToken() {
42081
+ if (this.initialTokenPromise) {
42082
+ await this.initialTokenPromise;
42083
+ }
42084
+ if (this.currentToken && !this.isExpired()) {
42085
+ return this.currentToken;
42086
+ }
42087
+ throw new Error("No valid auth token available");
42088
+ }
42089
+ /**
42090
+ * Fetches a new auth token from the server and stores it.
42091
+ */
42092
+ async fetchToken() {
42093
+ const response = await this.client.authTokenGet({});
42094
+ const token = response.token;
42095
+ if (!token) {
42096
+ throw new Error(
42097
+ "Internal error: did not receive auth token from server, please contact Modal support"
42098
+ );
42099
+ }
42100
+ this.currentToken = token;
42101
+ const exp = this.decodeJWT(token);
42102
+ if (exp > 0) {
42103
+ this.tokenExpiry = exp;
42104
+ } else {
42105
+ console.warn("Failed to decode x-modal-auth-token exp field");
42106
+ this.tokenExpiry = Math.floor(Date.now() / 1e3) + DEFAULT_EXPIRY_OFFSET;
42107
+ }
42108
+ }
42109
+ /**
42110
+ * Background loop that refreshes tokens REFRESH_WINDOW seconds before they expire.
42111
+ */
42112
+ async backgroundRefresh() {
42113
+ while (!this.stopped) {
42114
+ const now = Math.floor(Date.now() / 1e3);
42115
+ const refreshTime = this.tokenExpiry - REFRESH_WINDOW;
42116
+ const delay = Math.max(0, refreshTime - now) * 1e3;
42117
+ await new Promise((resolve) => {
42118
+ this.timeoutId = setTimeout(resolve, delay);
42119
+ this.timeoutId.unref();
42120
+ });
42121
+ if (this.stopped) {
42122
+ return;
42123
+ }
42124
+ try {
42125
+ await this.fetchToken();
42126
+ } catch (error) {
42127
+ console.error("Failed to refresh auth token:", error);
42128
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
42129
+ }
42130
+ }
42131
+ }
42132
+ /**
42133
+ * Fetches the initial token and starts the refresh loop.
42134
+ * Throws an error if the initial token fetch fails.
42135
+ */
42136
+ async start() {
42137
+ this.initialTokenPromise = this.fetchToken();
42138
+ try {
42139
+ await this.initialTokenPromise;
42140
+ } finally {
42141
+ this.initialTokenPromise = null;
42142
+ }
42143
+ this.stopped = false;
42144
+ this.backgroundRefresh();
42145
+ }
42146
+ /**
42147
+ * Stops the background refresh.
42148
+ */
42149
+ stop() {
42150
+ this.stopped = true;
42151
+ if (this.timeoutId) {
42152
+ clearTimeout(this.timeoutId);
42153
+ this.timeoutId = null;
42154
+ }
42155
+ }
42156
+ /**
42157
+ * Extracts the exp claim from a JWT token.
42158
+ */
42159
+ decodeJWT(token) {
42160
+ try {
42161
+ const parts = token.split(".");
42162
+ if (parts.length !== 3) {
42163
+ return 0;
42164
+ }
42165
+ let payload = parts[1];
42166
+ while (payload.length % 4 !== 0) {
42167
+ payload += "=";
42168
+ }
42169
+ const decoded = atob(payload);
42170
+ const claims = JSON.parse(decoded);
42171
+ return claims.exp || 0;
42172
+ } catch {
42173
+ return 0;
42174
+ }
42175
+ }
42176
+ isExpired() {
42177
+ const now = Math.floor(Date.now() / 1e3);
42178
+ return now >= this.tokenExpiry;
42179
+ }
42180
+ getCurrentToken() {
42181
+ return this.currentToken;
42182
+ }
42183
+ setToken(token, expiry) {
42184
+ this.currentToken = token;
42185
+ this.tokenExpiry = expiry;
42186
+ }
42187
+ };
42188
+
42063
42189
  // src/client.ts
42064
42190
  var defaultProfile = getProfile(process.env["MODAL_PROFILE"]);
42065
- var modalAuthToken;
42191
+ var authTokenManager = null;
42066
42192
  function authMiddleware(profile) {
42067
42193
  return async function* authMiddleware2(call, options) {
42068
42194
  if (!profile.tokenId || !profile.tokenSecret) {
@@ -42079,25 +42205,16 @@ function authMiddleware(profile) {
42079
42205
  options.metadata.set("x-modal-client-version", "1.0.0");
42080
42206
  options.metadata.set("x-modal-token-id", tokenId);
42081
42207
  options.metadata.set("x-modal-token-secret", tokenSecret);
42082
- if (modalAuthToken) {
42083
- options.metadata.set("x-modal-auth-token", modalAuthToken);
42084
- }
42085
- const prevOnHeader = options.onHeader;
42086
- options.onHeader = (header) => {
42087
- const token = header.get("x-modal-auth-token");
42088
- if (token) {
42089
- modalAuthToken = token;
42208
+ if (call.method.path !== "/modal.client.ModalClient/AuthTokenGet") {
42209
+ if (!authTokenManager) {
42210
+ authTokenManager = new AuthTokenManager(client);
42211
+ authTokenManager.start();
42090
42212
  }
42091
- prevOnHeader?.(header);
42092
- };
42093
- const prevOnTrailer = options.onTrailer;
42094
- options.onTrailer = (trailer) => {
42095
- const token = trailer.get("x-modal-auth-token");
42213
+ const token = await authTokenManager.getToken();
42096
42214
  if (token) {
42097
- modalAuthToken = token;
42215
+ options.metadata.set("x-modal-auth-token", token);
42098
42216
  }
42099
- prevOnTrailer?.(trailer);
42100
- };
42217
+ }
42101
42218
  return yield* call.next(call.request, options);
42102
42219
  };
42103
42220
  }
@@ -42233,6 +42350,8 @@ function initializeClient(options) {
42233
42350
  };
42234
42351
  clientProfile = mergedProfile;
42235
42352
  client = createClient(mergedProfile);
42353
+ authTokenManager = new AuthTokenManager(client);
42354
+ authTokenManager.start();
42236
42355
  }
42237
42356
 
42238
42357
  // src/secret.ts
@@ -43646,7 +43765,7 @@ function encodeValue(val, w, proto) {
43646
43765
  if (val >= 0 && val <= 255) {
43647
43766
  w.byte(75 /* BININT1 */);
43648
43767
  w.byte(val);
43649
- } else if (val >= -32768 && val <= 32767) {
43768
+ } else if (val >= 0 && val <= 65535) {
43650
43769
  w.byte(77 /* BININT2 */);
43651
43770
  w.byte(val & 255);
43652
43771
  w.byte(val >> 8 & 255);
@@ -43781,7 +43900,7 @@ function loads(buf) {
43781
43900
  case 77 /* BININT2 */: {
43782
43901
  const lo = r.byte(), hi = r.byte();
43783
43902
  const n = hi << 8 | lo;
43784
- push(n & 32768 ? n - 65536 : n);
43903
+ push(n);
43785
43904
  break;
43786
43905
  }
43787
43906
  case 74 /* BININT4 */: {
package/dist/index.js CHANGED
@@ -42006,9 +42006,135 @@ function imageBuilderVersion(version) {
42006
42006
  return version || clientProfile.imageBuilderVersion || "2024.10";
42007
42007
  }
42008
42008
 
42009
+ // src/auth_token_manager.ts
42010
+ var REFRESH_WINDOW = 5 * 60;
42011
+ var DEFAULT_EXPIRY_OFFSET = 20 * 60;
42012
+ var AuthTokenManager = class {
42013
+ client;
42014
+ currentToken = "";
42015
+ tokenExpiry = 0;
42016
+ stopped = false;
42017
+ timeoutId = null;
42018
+ initialTokenPromise = null;
42019
+ constructor(client2) {
42020
+ this.client = client2;
42021
+ }
42022
+ /**
42023
+ * Returns the current cached token.
42024
+ * If the initial token fetch is still in progress, waits for it to complete.
42025
+ */
42026
+ async getToken() {
42027
+ if (this.initialTokenPromise) {
42028
+ await this.initialTokenPromise;
42029
+ }
42030
+ if (this.currentToken && !this.isExpired()) {
42031
+ return this.currentToken;
42032
+ }
42033
+ throw new Error("No valid auth token available");
42034
+ }
42035
+ /**
42036
+ * Fetches a new auth token from the server and stores it.
42037
+ */
42038
+ async fetchToken() {
42039
+ const response = await this.client.authTokenGet({});
42040
+ const token = response.token;
42041
+ if (!token) {
42042
+ throw new Error(
42043
+ "Internal error: did not receive auth token from server, please contact Modal support"
42044
+ );
42045
+ }
42046
+ this.currentToken = token;
42047
+ const exp = this.decodeJWT(token);
42048
+ if (exp > 0) {
42049
+ this.tokenExpiry = exp;
42050
+ } else {
42051
+ console.warn("Failed to decode x-modal-auth-token exp field");
42052
+ this.tokenExpiry = Math.floor(Date.now() / 1e3) + DEFAULT_EXPIRY_OFFSET;
42053
+ }
42054
+ }
42055
+ /**
42056
+ * Background loop that refreshes tokens REFRESH_WINDOW seconds before they expire.
42057
+ */
42058
+ async backgroundRefresh() {
42059
+ while (!this.stopped) {
42060
+ const now = Math.floor(Date.now() / 1e3);
42061
+ const refreshTime = this.tokenExpiry - REFRESH_WINDOW;
42062
+ const delay = Math.max(0, refreshTime - now) * 1e3;
42063
+ await new Promise((resolve) => {
42064
+ this.timeoutId = setTimeout(resolve, delay);
42065
+ this.timeoutId.unref();
42066
+ });
42067
+ if (this.stopped) {
42068
+ return;
42069
+ }
42070
+ try {
42071
+ await this.fetchToken();
42072
+ } catch (error) {
42073
+ console.error("Failed to refresh auth token:", error);
42074
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
42075
+ }
42076
+ }
42077
+ }
42078
+ /**
42079
+ * Fetches the initial token and starts the refresh loop.
42080
+ * Throws an error if the initial token fetch fails.
42081
+ */
42082
+ async start() {
42083
+ this.initialTokenPromise = this.fetchToken();
42084
+ try {
42085
+ await this.initialTokenPromise;
42086
+ } finally {
42087
+ this.initialTokenPromise = null;
42088
+ }
42089
+ this.stopped = false;
42090
+ this.backgroundRefresh();
42091
+ }
42092
+ /**
42093
+ * Stops the background refresh.
42094
+ */
42095
+ stop() {
42096
+ this.stopped = true;
42097
+ if (this.timeoutId) {
42098
+ clearTimeout(this.timeoutId);
42099
+ this.timeoutId = null;
42100
+ }
42101
+ }
42102
+ /**
42103
+ * Extracts the exp claim from a JWT token.
42104
+ */
42105
+ decodeJWT(token) {
42106
+ try {
42107
+ const parts = token.split(".");
42108
+ if (parts.length !== 3) {
42109
+ return 0;
42110
+ }
42111
+ let payload = parts[1];
42112
+ while (payload.length % 4 !== 0) {
42113
+ payload += "=";
42114
+ }
42115
+ const decoded = atob(payload);
42116
+ const claims = JSON.parse(decoded);
42117
+ return claims.exp || 0;
42118
+ } catch {
42119
+ return 0;
42120
+ }
42121
+ }
42122
+ isExpired() {
42123
+ const now = Math.floor(Date.now() / 1e3);
42124
+ return now >= this.tokenExpiry;
42125
+ }
42126
+ getCurrentToken() {
42127
+ return this.currentToken;
42128
+ }
42129
+ setToken(token, expiry) {
42130
+ this.currentToken = token;
42131
+ this.tokenExpiry = expiry;
42132
+ }
42133
+ };
42134
+
42009
42135
  // src/client.ts
42010
42136
  var defaultProfile = getProfile(process.env["MODAL_PROFILE"]);
42011
- var modalAuthToken;
42137
+ var authTokenManager = null;
42012
42138
  function authMiddleware(profile) {
42013
42139
  return async function* authMiddleware2(call, options) {
42014
42140
  if (!profile.tokenId || !profile.tokenSecret) {
@@ -42025,25 +42151,16 @@ function authMiddleware(profile) {
42025
42151
  options.metadata.set("x-modal-client-version", "1.0.0");
42026
42152
  options.metadata.set("x-modal-token-id", tokenId);
42027
42153
  options.metadata.set("x-modal-token-secret", tokenSecret);
42028
- if (modalAuthToken) {
42029
- options.metadata.set("x-modal-auth-token", modalAuthToken);
42030
- }
42031
- const prevOnHeader = options.onHeader;
42032
- options.onHeader = (header) => {
42033
- const token = header.get("x-modal-auth-token");
42034
- if (token) {
42035
- modalAuthToken = token;
42154
+ if (call.method.path !== "/modal.client.ModalClient/AuthTokenGet") {
42155
+ if (!authTokenManager) {
42156
+ authTokenManager = new AuthTokenManager(client);
42157
+ authTokenManager.start();
42036
42158
  }
42037
- prevOnHeader?.(header);
42038
- };
42039
- const prevOnTrailer = options.onTrailer;
42040
- options.onTrailer = (trailer) => {
42041
- const token = trailer.get("x-modal-auth-token");
42159
+ const token = await authTokenManager.getToken();
42042
42160
  if (token) {
42043
- modalAuthToken = token;
42161
+ options.metadata.set("x-modal-auth-token", token);
42044
42162
  }
42045
- prevOnTrailer?.(trailer);
42046
- };
42163
+ }
42047
42164
  return yield* call.next(call.request, options);
42048
42165
  };
42049
42166
  }
@@ -42179,6 +42296,8 @@ function initializeClient(options) {
42179
42296
  };
42180
42297
  clientProfile = mergedProfile;
42181
42298
  client = createClient(mergedProfile);
42299
+ authTokenManager = new AuthTokenManager(client);
42300
+ authTokenManager.start();
42182
42301
  }
42183
42302
 
42184
42303
  // src/secret.ts
@@ -43592,7 +43711,7 @@ function encodeValue(val, w, proto) {
43592
43711
  if (val >= 0 && val <= 255) {
43593
43712
  w.byte(75 /* BININT1 */);
43594
43713
  w.byte(val);
43595
- } else if (val >= -32768 && val <= 32767) {
43714
+ } else if (val >= 0 && val <= 65535) {
43596
43715
  w.byte(77 /* BININT2 */);
43597
43716
  w.byte(val & 255);
43598
43717
  w.byte(val >> 8 & 255);
@@ -43727,7 +43846,7 @@ function loads(buf) {
43727
43846
  case 77 /* BININT2 */: {
43728
43847
  const lo = r.byte(), hi = r.byte();
43729
43848
  const n = hi << 8 | lo;
43730
- push(n & 32768 ? n - 65536 : n);
43849
+ push(n);
43731
43850
  break;
43732
43851
  }
43733
43852
  case 74 /* BININT4 */: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modal",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "Modal client library for JavaScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://modal.com/docs",
@@ -46,11 +46,13 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@eslint/js": "^9.28.0",
49
+ "@types/jsonwebtoken": "^9.0.10",
49
50
  "@types/node": "^22.15.2",
50
51
  "eslint": "^9.28.0",
51
52
  "globals": "^16.2.0",
52
53
  "grpc-tools": "^1.13.0",
53
54
  "http-server": "^14.1.1",
55
+ "jsonwebtoken": "^9.0.2",
54
56
  "p-queue": "^8.1.0",
55
57
  "prettier": "^3.5.3",
56
58
  "ts-proto": "^2.7.0",