@zskj-idaas/sdk 1.0.1 → 1.0.4

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
@@ -18,6 +18,11 @@ interface TokenSet {
18
18
  refresh_token?: string;
19
19
  scope: string;
20
20
  }
21
+ interface ServiceTokenState {
22
+ accessToken: string;
23
+ expiresAt: number;
24
+ scope: string;
25
+ }
21
26
  interface UserInfo {
22
27
  sub: string;
23
28
  preferred_username?: string;
@@ -35,6 +40,10 @@ interface AuthState {
35
40
  idToken: string | null;
36
41
  expiresAt: number | null;
37
42
  }
43
+ interface SessionLogoutOptions {
44
+ /** Redirect after the server-side session and local tokens are cleared. */
45
+ redirectTo?: string;
46
+ }
38
47
  interface PKCEPair {
39
48
  codeVerifier: string;
40
49
  codeChallenge: string;
@@ -45,6 +54,7 @@ declare class IdaasClient {
45
54
  private endpoints;
46
55
  private storage;
47
56
  private userInfo;
57
+ private serviceTokens;
48
58
  constructor(config: IdaasConfig);
49
59
  init(): Promise<AuthState>;
50
60
  private discover;
@@ -53,13 +63,16 @@ declare class IdaasClient {
53
63
  refreshToken(): Promise<TokenSet | null>;
54
64
  getUserInfo(forceRefresh?: boolean): Promise<UserInfo | null>;
55
65
  getAccessToken(): Promise<string | null>;
66
+ getServiceToken(serviceKey: string): Promise<string | null>;
56
67
  getIdToken(): string | null;
57
68
  getAuthState(): AuthState;
58
69
  isAuthenticated(): boolean;
59
70
  logout(): Promise<void>;
71
+ logoutSession(options?: SessionLogoutOptions): Promise<void>;
60
72
  revokeToken(): Promise<void>;
61
73
  /** Create an authenticated fetch wrapper */
62
74
  fetch(url: string, init?: RequestInit): Promise<Response>;
75
+ fetchService(serviceKey: string, url: string, init?: RequestInit): Promise<Response>;
63
76
  }
64
77
 
65
78
  declare function createPKCE(): Promise<PKCEPair>;
@@ -70,6 +83,7 @@ declare class TokenStorage {
70
83
  private prefix;
71
84
  constructor(prefix?: string);
72
85
  private key;
86
+ private migrateTokenValue;
73
87
  saveTokens(tokens: TokenSet): void;
74
88
  getAccessToken(): string | null;
75
89
  getIdToken(): string | null;
@@ -86,4 +100,4 @@ declare class TokenStorage {
86
100
  clearAuthState(): void;
87
101
  }
88
102
 
89
- export { type AuthState, IdaasClient, type IdaasConfig, type TokenSet, TokenStorage, type UserInfo, createPKCE, generateNonce, generateState };
103
+ export { type AuthState, IdaasClient, type IdaasConfig, type ServiceTokenState, type SessionLogoutOptions, type TokenSet, TokenStorage, type UserInfo, createPKCE, generateNonce, generateState };
package/dist/index.d.ts CHANGED
@@ -18,6 +18,11 @@ interface TokenSet {
18
18
  refresh_token?: string;
19
19
  scope: string;
20
20
  }
21
+ interface ServiceTokenState {
22
+ accessToken: string;
23
+ expiresAt: number;
24
+ scope: string;
25
+ }
21
26
  interface UserInfo {
22
27
  sub: string;
23
28
  preferred_username?: string;
@@ -35,6 +40,10 @@ interface AuthState {
35
40
  idToken: string | null;
36
41
  expiresAt: number | null;
37
42
  }
43
+ interface SessionLogoutOptions {
44
+ /** Redirect after the server-side session and local tokens are cleared. */
45
+ redirectTo?: string;
46
+ }
38
47
  interface PKCEPair {
39
48
  codeVerifier: string;
40
49
  codeChallenge: string;
@@ -45,6 +54,7 @@ declare class IdaasClient {
45
54
  private endpoints;
46
55
  private storage;
47
56
  private userInfo;
57
+ private serviceTokens;
48
58
  constructor(config: IdaasConfig);
49
59
  init(): Promise<AuthState>;
50
60
  private discover;
@@ -53,13 +63,16 @@ declare class IdaasClient {
53
63
  refreshToken(): Promise<TokenSet | null>;
54
64
  getUserInfo(forceRefresh?: boolean): Promise<UserInfo | null>;
55
65
  getAccessToken(): Promise<string | null>;
66
+ getServiceToken(serviceKey: string): Promise<string | null>;
56
67
  getIdToken(): string | null;
57
68
  getAuthState(): AuthState;
58
69
  isAuthenticated(): boolean;
59
70
  logout(): Promise<void>;
71
+ logoutSession(options?: SessionLogoutOptions): Promise<void>;
60
72
  revokeToken(): Promise<void>;
61
73
  /** Create an authenticated fetch wrapper */
62
74
  fetch(url: string, init?: RequestInit): Promise<Response>;
75
+ fetchService(serviceKey: string, url: string, init?: RequestInit): Promise<Response>;
63
76
  }
64
77
 
65
78
  declare function createPKCE(): Promise<PKCEPair>;
@@ -70,6 +83,7 @@ declare class TokenStorage {
70
83
  private prefix;
71
84
  constructor(prefix?: string);
72
85
  private key;
86
+ private migrateTokenValue;
73
87
  saveTokens(tokens: TokenSet): void;
74
88
  getAccessToken(): string | null;
75
89
  getIdToken(): string | null;
@@ -86,4 +100,4 @@ declare class TokenStorage {
86
100
  clearAuthState(): void;
87
101
  }
88
102
 
89
- export { type AuthState, IdaasClient, type IdaasConfig, type TokenSet, TokenStorage, type UserInfo, createPKCE, generateNonce, generateState };
103
+ export { type AuthState, IdaasClient, type IdaasConfig, type ServiceTokenState, type SessionLogoutOptions, type TokenSet, TokenStorage, type UserInfo, createPKCE, generateNonce, generateState };
package/dist/index.js CHANGED
@@ -183,33 +183,48 @@ var TokenStorage = class {
183
183
  key(name) {
184
184
  return `${this.prefix}:${name}`;
185
185
  }
186
+ migrateTokenValue(name) {
187
+ const key = this.key(name);
188
+ const current = localStorage.getItem(key);
189
+ if (current) return current;
190
+ const legacy = sessionStorage.getItem(key);
191
+ if (!legacy) return null;
192
+ localStorage.setItem(key, legacy);
193
+ sessionStorage.removeItem(key);
194
+ return legacy;
195
+ }
186
196
  saveTokens(tokens) {
187
197
  const expiresAt = Date.now() + tokens.expires_in * 1e3;
188
- sessionStorage.setItem(this.key("access_token"), tokens.access_token);
189
- sessionStorage.setItem(this.key("expires_at"), expiresAt.toString());
198
+ localStorage.setItem(this.key("access_token"), tokens.access_token);
199
+ localStorage.setItem(this.key("expires_at"), expiresAt.toString());
200
+ sessionStorage.removeItem(this.key("access_token"));
201
+ sessionStorage.removeItem(this.key("expires_at"));
190
202
  if (tokens.id_token) {
191
- sessionStorage.setItem(this.key("id_token"), tokens.id_token);
203
+ localStorage.setItem(this.key("id_token"), tokens.id_token);
204
+ sessionStorage.removeItem(this.key("id_token"));
192
205
  }
193
206
  if (tokens.refresh_token) {
194
207
  localStorage.setItem(this.key("refresh_token"), tokens.refresh_token);
195
208
  }
196
- sessionStorage.setItem(this.key("scope"), tokens.scope);
209
+ localStorage.setItem(this.key("scope"), tokens.scope);
210
+ sessionStorage.removeItem(this.key("scope"));
197
211
  }
198
212
  getAccessToken() {
199
- const token = sessionStorage.getItem(this.key("access_token"));
200
- const expiresAt = sessionStorage.getItem(this.key("expires_at"));
213
+ const token = this.migrateTokenValue("access_token");
214
+ const expiresAt = this.migrateTokenValue("expires_at");
215
+ this.migrateTokenValue("scope");
201
216
  if (!token || !expiresAt) return null;
202
217
  if (Date.now() > parseInt(expiresAt, 10)) return null;
203
218
  return token;
204
219
  }
205
220
  getIdToken() {
206
- return sessionStorage.getItem(this.key("id_token"));
221
+ return this.migrateTokenValue("id_token");
207
222
  }
208
223
  getRefreshToken() {
209
224
  return localStorage.getItem(this.key("refresh_token"));
210
225
  }
211
226
  getExpiresAt() {
212
- const val = sessionStorage.getItem(this.key("expires_at"));
227
+ const val = this.migrateTokenValue("expires_at");
213
228
  return val ? parseInt(val, 10) : null;
214
229
  }
215
230
  isTokenExpired() {
@@ -218,11 +233,15 @@ var TokenStorage = class {
218
233
  return Date.now() > expiresAt - 6e4;
219
234
  }
220
235
  clear() {
236
+ localStorage.removeItem(this.key("access_token"));
237
+ localStorage.removeItem(this.key("id_token"));
238
+ localStorage.removeItem(this.key("expires_at"));
239
+ localStorage.removeItem(this.key("scope"));
240
+ localStorage.removeItem(this.key("refresh_token"));
221
241
  sessionStorage.removeItem(this.key("access_token"));
222
242
  sessionStorage.removeItem(this.key("id_token"));
223
243
  sessionStorage.removeItem(this.key("expires_at"));
224
244
  sessionStorage.removeItem(this.key("scope"));
225
- localStorage.removeItem(this.key("refresh_token"));
226
245
  }
227
246
  saveState(state) {
228
247
  sessionStorage.setItem(this.key("state"), state);
@@ -254,6 +273,7 @@ var IdaasClient = class {
254
273
  constructor(config) {
255
274
  this.endpoints = null;
256
275
  this.userInfo = null;
276
+ this.serviceTokens = /* @__PURE__ */ new Map();
257
277
  this.config = {
258
278
  scope: "openid profile email",
259
279
  autoDiscovery: true,
@@ -276,6 +296,7 @@ var IdaasClient = class {
276
296
  introspection_endpoint: `${this.config.issuer}/oauth2/introspect`
277
297
  };
278
298
  }
299
+ await this.getAccessToken();
279
300
  return this.getAuthState();
280
301
  }
281
302
  async discover() {
@@ -392,14 +413,40 @@ var IdaasClient = class {
392
413
  return this.userInfo;
393
414
  }
394
415
  async getAccessToken() {
395
- let token = this.storage.getAccessToken();
396
- if (token) return token;
397
- if (this.storage.isTokenExpired()) {
398
- const tokens = await this.refreshToken();
399
- if (tokens) return tokens.access_token;
400
- }
416
+ const token = this.storage.getAccessToken();
417
+ if (token && !this.storage.isTokenExpired()) return token;
418
+ const tokens = await this.refreshToken();
419
+ if (tokens) return tokens.access_token;
401
420
  return null;
402
421
  }
422
+ async getServiceToken(serviceKey) {
423
+ if (!this.endpoints) await this.init();
424
+ const cached = this.serviceTokens.get(serviceKey);
425
+ if (cached && Date.now() < cached.expiresAt - 6e4) {
426
+ return cached.accessToken;
427
+ }
428
+ const baseToken = await this.getAccessToken();
429
+ if (!baseToken) return null;
430
+ const res = await fetch(`${this.config.issuer}/oauth2/business-token`, {
431
+ method: "POST",
432
+ headers: {
433
+ "Content-Type": "application/json",
434
+ Authorization: `Bearer ${baseToken}`
435
+ },
436
+ body: JSON.stringify({ service_key: serviceKey })
437
+ });
438
+ if (!res.ok) {
439
+ this.serviceTokens.delete(serviceKey);
440
+ return null;
441
+ }
442
+ const tokenSet = await res.json();
443
+ this.serviceTokens.set(serviceKey, {
444
+ accessToken: tokenSet.access_token,
445
+ expiresAt: Date.now() + tokenSet.expires_in * 1e3,
446
+ scope: tokenSet.scope
447
+ });
448
+ return tokenSet.access_token;
449
+ }
403
450
  getIdToken() {
404
451
  return this.storage.getIdToken();
405
452
  }
@@ -428,6 +475,17 @@ var IdaasClient = class {
428
475
  }
429
476
  window.location.href = `${this.endpoints.end_session_endpoint}?${params.toString()}`;
430
477
  }
478
+ async logoutSession(options = {}) {
479
+ this.storage.clear();
480
+ this.userInfo = null;
481
+ await fetch(`${this.config.issuer}/auth/logout`, {
482
+ method: "POST",
483
+ credentials: "include"
484
+ }).catch(() => void 0);
485
+ if (options.redirectTo) {
486
+ window.location.href = options.redirectTo;
487
+ }
488
+ }
431
489
  async revokeToken() {
432
490
  if (!this.endpoints) await this.init();
433
491
  const refreshToken = this.storage.getRefreshToken();
@@ -451,6 +509,13 @@ var IdaasClient = class {
451
509
  }
452
510
  return fetch(url, { ...init, headers });
453
511
  }
512
+ async fetchService(serviceKey, url, init) {
513
+ const token = await this.getServiceToken(serviceKey);
514
+ if (!token) throw new Error(`No service token available for ${serviceKey}`);
515
+ const headers = new Headers(init?.headers);
516
+ headers.set("Authorization", `Bearer ${token}`);
517
+ return fetch(url, { ...init, headers });
518
+ }
454
519
  };
455
520
  // Annotate the CommonJS export names for ESM import in node:
456
521
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -153,33 +153,48 @@ var TokenStorage = class {
153
153
  key(name) {
154
154
  return `${this.prefix}:${name}`;
155
155
  }
156
+ migrateTokenValue(name) {
157
+ const key = this.key(name);
158
+ const current = localStorage.getItem(key);
159
+ if (current) return current;
160
+ const legacy = sessionStorage.getItem(key);
161
+ if (!legacy) return null;
162
+ localStorage.setItem(key, legacy);
163
+ sessionStorage.removeItem(key);
164
+ return legacy;
165
+ }
156
166
  saveTokens(tokens) {
157
167
  const expiresAt = Date.now() + tokens.expires_in * 1e3;
158
- sessionStorage.setItem(this.key("access_token"), tokens.access_token);
159
- sessionStorage.setItem(this.key("expires_at"), expiresAt.toString());
168
+ localStorage.setItem(this.key("access_token"), tokens.access_token);
169
+ localStorage.setItem(this.key("expires_at"), expiresAt.toString());
170
+ sessionStorage.removeItem(this.key("access_token"));
171
+ sessionStorage.removeItem(this.key("expires_at"));
160
172
  if (tokens.id_token) {
161
- sessionStorage.setItem(this.key("id_token"), tokens.id_token);
173
+ localStorage.setItem(this.key("id_token"), tokens.id_token);
174
+ sessionStorage.removeItem(this.key("id_token"));
162
175
  }
163
176
  if (tokens.refresh_token) {
164
177
  localStorage.setItem(this.key("refresh_token"), tokens.refresh_token);
165
178
  }
166
- sessionStorage.setItem(this.key("scope"), tokens.scope);
179
+ localStorage.setItem(this.key("scope"), tokens.scope);
180
+ sessionStorage.removeItem(this.key("scope"));
167
181
  }
168
182
  getAccessToken() {
169
- const token = sessionStorage.getItem(this.key("access_token"));
170
- const expiresAt = sessionStorage.getItem(this.key("expires_at"));
183
+ const token = this.migrateTokenValue("access_token");
184
+ const expiresAt = this.migrateTokenValue("expires_at");
185
+ this.migrateTokenValue("scope");
171
186
  if (!token || !expiresAt) return null;
172
187
  if (Date.now() > parseInt(expiresAt, 10)) return null;
173
188
  return token;
174
189
  }
175
190
  getIdToken() {
176
- return sessionStorage.getItem(this.key("id_token"));
191
+ return this.migrateTokenValue("id_token");
177
192
  }
178
193
  getRefreshToken() {
179
194
  return localStorage.getItem(this.key("refresh_token"));
180
195
  }
181
196
  getExpiresAt() {
182
- const val = sessionStorage.getItem(this.key("expires_at"));
197
+ const val = this.migrateTokenValue("expires_at");
183
198
  return val ? parseInt(val, 10) : null;
184
199
  }
185
200
  isTokenExpired() {
@@ -188,11 +203,15 @@ var TokenStorage = class {
188
203
  return Date.now() > expiresAt - 6e4;
189
204
  }
190
205
  clear() {
206
+ localStorage.removeItem(this.key("access_token"));
207
+ localStorage.removeItem(this.key("id_token"));
208
+ localStorage.removeItem(this.key("expires_at"));
209
+ localStorage.removeItem(this.key("scope"));
210
+ localStorage.removeItem(this.key("refresh_token"));
191
211
  sessionStorage.removeItem(this.key("access_token"));
192
212
  sessionStorage.removeItem(this.key("id_token"));
193
213
  sessionStorage.removeItem(this.key("expires_at"));
194
214
  sessionStorage.removeItem(this.key("scope"));
195
- localStorage.removeItem(this.key("refresh_token"));
196
215
  }
197
216
  saveState(state) {
198
217
  sessionStorage.setItem(this.key("state"), state);
@@ -224,6 +243,7 @@ var IdaasClient = class {
224
243
  constructor(config) {
225
244
  this.endpoints = null;
226
245
  this.userInfo = null;
246
+ this.serviceTokens = /* @__PURE__ */ new Map();
227
247
  this.config = {
228
248
  scope: "openid profile email",
229
249
  autoDiscovery: true,
@@ -246,6 +266,7 @@ var IdaasClient = class {
246
266
  introspection_endpoint: `${this.config.issuer}/oauth2/introspect`
247
267
  };
248
268
  }
269
+ await this.getAccessToken();
249
270
  return this.getAuthState();
250
271
  }
251
272
  async discover() {
@@ -362,14 +383,40 @@ var IdaasClient = class {
362
383
  return this.userInfo;
363
384
  }
364
385
  async getAccessToken() {
365
- let token = this.storage.getAccessToken();
366
- if (token) return token;
367
- if (this.storage.isTokenExpired()) {
368
- const tokens = await this.refreshToken();
369
- if (tokens) return tokens.access_token;
370
- }
386
+ const token = this.storage.getAccessToken();
387
+ if (token && !this.storage.isTokenExpired()) return token;
388
+ const tokens = await this.refreshToken();
389
+ if (tokens) return tokens.access_token;
371
390
  return null;
372
391
  }
392
+ async getServiceToken(serviceKey) {
393
+ if (!this.endpoints) await this.init();
394
+ const cached = this.serviceTokens.get(serviceKey);
395
+ if (cached && Date.now() < cached.expiresAt - 6e4) {
396
+ return cached.accessToken;
397
+ }
398
+ const baseToken = await this.getAccessToken();
399
+ if (!baseToken) return null;
400
+ const res = await fetch(`${this.config.issuer}/oauth2/business-token`, {
401
+ method: "POST",
402
+ headers: {
403
+ "Content-Type": "application/json",
404
+ Authorization: `Bearer ${baseToken}`
405
+ },
406
+ body: JSON.stringify({ service_key: serviceKey })
407
+ });
408
+ if (!res.ok) {
409
+ this.serviceTokens.delete(serviceKey);
410
+ return null;
411
+ }
412
+ const tokenSet = await res.json();
413
+ this.serviceTokens.set(serviceKey, {
414
+ accessToken: tokenSet.access_token,
415
+ expiresAt: Date.now() + tokenSet.expires_in * 1e3,
416
+ scope: tokenSet.scope
417
+ });
418
+ return tokenSet.access_token;
419
+ }
373
420
  getIdToken() {
374
421
  return this.storage.getIdToken();
375
422
  }
@@ -398,6 +445,17 @@ var IdaasClient = class {
398
445
  }
399
446
  window.location.href = `${this.endpoints.end_session_endpoint}?${params.toString()}`;
400
447
  }
448
+ async logoutSession(options = {}) {
449
+ this.storage.clear();
450
+ this.userInfo = null;
451
+ await fetch(`${this.config.issuer}/auth/logout`, {
452
+ method: "POST",
453
+ credentials: "include"
454
+ }).catch(() => void 0);
455
+ if (options.redirectTo) {
456
+ window.location.href = options.redirectTo;
457
+ }
458
+ }
401
459
  async revokeToken() {
402
460
  if (!this.endpoints) await this.init();
403
461
  const refreshToken = this.storage.getRefreshToken();
@@ -421,6 +479,13 @@ var IdaasClient = class {
421
479
  }
422
480
  return fetch(url, { ...init, headers });
423
481
  }
482
+ async fetchService(serviceKey, url, init) {
483
+ const token = await this.getServiceToken(serviceKey);
484
+ if (!token) throw new Error(`No service token available for ${serviceKey}`);
485
+ const headers = new Headers(init?.headers);
486
+ headers.set("Authorization", `Bearer ${token}`);
487
+ return fetch(url, { ...init, headers });
488
+ }
424
489
  };
425
490
  export {
426
491
  IdaasClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zskj-idaas/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "IDaaS OIDC Client SDK for browser applications — supports Authorization Code Flow with PKCE, token management, and authenticated fetch.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -26,6 +26,7 @@
26
26
  "scripts": {
27
27
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
28
28
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
29
+ "test": "vitest run",
29
30
  "prepublishOnly": "npm run build"
30
31
  },
31
32
  "keywords": [
@@ -57,9 +58,9 @@
57
58
  "not dead",
58
59
  "not ie 11"
59
60
  ],
60
- "dependencies": {},
61
61
  "devDependencies": {
62
62
  "tsup": "^8.2.0",
63
- "typescript": "^5.6.0"
63
+ "typescript": "^5.6.0",
64
+ "vitest": "^4.1.7"
64
65
  }
65
66
  }