@bodhiapp/bodhi-js 0.0.5 → 0.0.6
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/bodhi-browser-ext/src/types/common.d.ts +1 -0
- package/dist/bodhi-js-sdk/core/src/direct-client-base.d.ts +5 -5
- package/dist/bodhi-js-sdk/core/src/facade-client-base.d.ts +5 -5
- package/dist/bodhi-js-sdk/core/src/interface.d.ts +14 -6
- package/dist/bodhi-js-sdk/core/src/onboarding/modal.d.ts +1 -1
- package/dist/bodhi-js-sdk/core/src/types/auth.d.ts +37 -0
- package/dist/bodhi-js-sdk/core/src/types/callback.d.ts +1 -1
- package/dist/bodhi-js-sdk/core/src/types/client-state.d.ts +43 -93
- package/dist/bodhi-js-sdk/core/src/types/index.d.ts +4 -3
- package/dist/bodhi-js-sdk/core/src/types/user-info.d.ts +0 -32
- package/dist/bodhi-js-sdk/web/src/direct-client.d.ts +4 -5
- package/dist/bodhi-js-sdk/web/src/ext-client.d.ts +6 -6
- package/dist/bodhi-js-sdk/web/src/facade-client.d.ts +2 -2
- package/dist/bodhi-web.cjs.js +1 -1
- package/dist/bodhi-web.esm.js +233 -223
- package/package.json +6 -3
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,38 +1,48 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
class
|
|
3
|
-
constructor(
|
|
4
|
-
const r =
|
|
5
|
-
super({ ...
|
|
1
|
+
import { DirectClientBase as C, createStoragePrefixWithBasePath as p, STORAGE_PREFIXES as S, isApiResultOperationError as f, createOperationError as h, isApiResultError as w, isApiResultSuccess as m, generateCodeVerifier as g, generateCodeChallenge as T, createStorageKeys as I, EXTENSION_STATE_NOT_INITIALIZED as y, Logger as R, createOAuthEndpoints as x, NOOP_STATE_CALLBACK as k, EXTENSION_STATE_NOT_FOUND as A, PENDING_EXTENSION_READY as v, BACKEND_SERVER_NOT_REACHABLE as d, extractUserInfo as _, refreshAccessToken as b, backendServerNotReady as E, SERVER_ERROR_CODES as O, createApiError as P, BaseFacadeClient as K } from "@bodhiapp/bodhi-js-core";
|
|
2
|
+
class U extends C {
|
|
3
|
+
constructor(t, e) {
|
|
4
|
+
const r = t.basePath || "/", s = p(r, S.DIRECT);
|
|
5
|
+
super({ ...t, storagePrefix: s }, "DirectWebClient", e), this.redirectUri = t.redirectUri;
|
|
6
6
|
}
|
|
7
7
|
// ============================================================================
|
|
8
8
|
// Authentication (Browser Redirect OAuth)
|
|
9
9
|
// ============================================================================
|
|
10
10
|
async login() {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
return
|
|
14
|
-
const
|
|
15
|
-
|
|
11
|
+
const t = await this.getAuthState();
|
|
12
|
+
if (t.status === "authenticated")
|
|
13
|
+
return t;
|
|
14
|
+
const e = await this.requestResourceAccess();
|
|
15
|
+
if (f(e))
|
|
16
|
+
throw h(e.error.message, e.error.type);
|
|
17
|
+
if (w(e)) {
|
|
18
|
+
const { message: i } = e.body.error;
|
|
19
|
+
throw h(i, "auth_error");
|
|
20
|
+
}
|
|
21
|
+
if (!m(e))
|
|
22
|
+
throw h(`Unexpected HTTP ${e.status}`, "auth_error");
|
|
23
|
+
const r = e.body.scope;
|
|
24
|
+
localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, r);
|
|
25
|
+
const s = `openid profile email roles ${this.userScope} ${r}`, a = g(), o = await T(a), c = g();
|
|
26
|
+
localStorage.setItem(this.storageKeys.CODE_VERIFIER, a), localStorage.setItem(this.storageKeys.STATE, c);
|
|
16
27
|
const n = new URL(this.authEndpoints.authorize);
|
|
17
|
-
throw n.searchParams.set("client_id", this.authClientId), n.searchParams.set("response_type", "code"), n.searchParams.set("redirect_uri", this.redirectUri), n.searchParams.set("scope",
|
|
28
|
+
throw n.searchParams.set("client_id", this.authClientId), n.searchParams.set("response_type", "code"), n.searchParams.set("redirect_uri", this.redirectUri), n.searchParams.set("scope", s), n.searchParams.set("code_challenge", o), n.searchParams.set("code_challenge_method", "S256"), n.searchParams.set("state", c), window.location.href = n.toString(), new Error("Redirect initiated");
|
|
18
29
|
}
|
|
19
|
-
async handleOAuthCallback(
|
|
30
|
+
async handleOAuthCallback(t, e) {
|
|
20
31
|
const r = localStorage.getItem(this.storageKeys.STATE);
|
|
21
|
-
if (!r || r !==
|
|
32
|
+
if (!r || r !== e)
|
|
22
33
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
23
|
-
await this.exchangeCodeForTokens(
|
|
34
|
+
await this.exchangeCodeForTokens(t), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
|
|
24
35
|
const s = await this.getAuthState();
|
|
25
|
-
if (
|
|
36
|
+
if (s.status !== "authenticated")
|
|
26
37
|
throw new Error("Login failed");
|
|
27
|
-
|
|
28
|
-
return this.setAuthState(i), i;
|
|
38
|
+
return this.setAuthState(s), s;
|
|
29
39
|
}
|
|
30
40
|
async logout() {
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
41
|
+
const t = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
42
|
+
if (t)
|
|
33
43
|
try {
|
|
34
44
|
const r = new URLSearchParams({
|
|
35
|
-
token:
|
|
45
|
+
token: t,
|
|
36
46
|
client_id: this.authClientId,
|
|
37
47
|
token_type_hint: "refresh_token"
|
|
38
48
|
});
|
|
@@ -47,32 +57,20 @@ class K extends R {
|
|
|
47
57
|
this.logger.warn("Token revocation failed:", r);
|
|
48
58
|
}
|
|
49
59
|
localStorage.removeItem(this.storageKeys.ACCESS_TOKEN), localStorage.removeItem(this.storageKeys.REFRESH_TOKEN), localStorage.removeItem(this.storageKeys.EXPIRES_AT), localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);
|
|
50
|
-
const
|
|
51
|
-
|
|
60
|
+
const e = {
|
|
61
|
+
status: "unauthenticated",
|
|
62
|
+
user: null,
|
|
63
|
+
accessToken: null,
|
|
64
|
+
error: null
|
|
52
65
|
};
|
|
53
|
-
return this.setAuthState(
|
|
66
|
+
return this.setAuthState(e), e;
|
|
54
67
|
}
|
|
55
68
|
// ============================================================================
|
|
56
69
|
// OAuth Helper Methods
|
|
57
70
|
// ============================================================================
|
|
58
|
-
async
|
|
59
|
-
const e =
|
|
60
|
-
|
|
61
|
-
"/bodhi/v1/apps/request-access",
|
|
62
|
-
{ app_client_id: this.authClientId },
|
|
63
|
-
{},
|
|
64
|
-
!1
|
|
65
|
-
);
|
|
66
|
-
if (w(e))
|
|
67
|
-
throw new Error("Failed to get resource access scope from server");
|
|
68
|
-
if (!f(e))
|
|
69
|
-
throw new Error("Failed to get resource access scope from server: API error");
|
|
70
|
-
const t = e.body.scope;
|
|
71
|
-
return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, t), t;
|
|
72
|
-
}
|
|
73
|
-
async exchangeCodeForTokens(e) {
|
|
74
|
-
const t = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
|
|
75
|
-
if (!t)
|
|
71
|
+
async exchangeCodeForTokens(t) {
|
|
72
|
+
const e = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
|
|
73
|
+
if (!e)
|
|
76
74
|
throw new Error("Code verifier not found");
|
|
77
75
|
const r = await fetch(this.authEndpoints.token, {
|
|
78
76
|
method: "POST",
|
|
@@ -81,67 +79,67 @@ class K extends R {
|
|
|
81
79
|
},
|
|
82
80
|
body: new URLSearchParams({
|
|
83
81
|
grant_type: "authorization_code",
|
|
84
|
-
code:
|
|
82
|
+
code: t,
|
|
85
83
|
redirect_uri: this.redirectUri,
|
|
86
84
|
client_id: this.authClientId,
|
|
87
|
-
code_verifier:
|
|
85
|
+
code_verifier: e
|
|
88
86
|
})
|
|
89
87
|
});
|
|
90
88
|
if (!r.ok) {
|
|
91
|
-
const
|
|
92
|
-
throw new Error(`Token exchange failed: ${r.status} ${
|
|
89
|
+
const a = await r.text();
|
|
90
|
+
throw new Error(`Token exchange failed: ${r.status} ${a}`);
|
|
93
91
|
}
|
|
94
92
|
const s = await r.json();
|
|
95
93
|
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN, s.access_token), s.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, s.refresh_token), s.expires_in) {
|
|
96
|
-
const
|
|
97
|
-
localStorage.setItem(this.storageKeys.EXPIRES_AT,
|
|
94
|
+
const a = Date.now() + s.expires_in * 1e3;
|
|
95
|
+
localStorage.setItem(this.storageKeys.EXPIRES_AT, a.toString());
|
|
98
96
|
}
|
|
99
97
|
}
|
|
100
98
|
// ============================================================================
|
|
101
99
|
// Storage Implementation (localStorage)
|
|
102
100
|
// ============================================================================
|
|
103
|
-
async _storageGet(
|
|
104
|
-
return localStorage.getItem(
|
|
101
|
+
async _storageGet(t) {
|
|
102
|
+
return localStorage.getItem(t);
|
|
105
103
|
}
|
|
106
|
-
async _storageSet(
|
|
107
|
-
Object.entries(
|
|
108
|
-
localStorage.setItem(
|
|
104
|
+
async _storageSet(t) {
|
|
105
|
+
Object.entries(t).forEach(([e, r]) => {
|
|
106
|
+
localStorage.setItem(e, String(r));
|
|
109
107
|
});
|
|
110
108
|
}
|
|
111
|
-
async _storageRemove(
|
|
112
|
-
|
|
109
|
+
async _storageRemove(t) {
|
|
110
|
+
t.forEach((e) => localStorage.removeItem(e));
|
|
113
111
|
}
|
|
114
112
|
_getRedirectUri() {
|
|
115
113
|
return this.redirectUri;
|
|
116
114
|
}
|
|
117
115
|
}
|
|
118
|
-
const
|
|
116
|
+
const N = 500, D = 5e3;
|
|
119
117
|
I(S.WEB);
|
|
120
|
-
function
|
|
121
|
-
const
|
|
122
|
-
return I(
|
|
118
|
+
function L(u = "/") {
|
|
119
|
+
const t = p(u, S.WEB);
|
|
120
|
+
return I(t);
|
|
123
121
|
}
|
|
124
|
-
class
|
|
125
|
-
constructor(
|
|
126
|
-
this.state =
|
|
122
|
+
class F {
|
|
123
|
+
constructor(t, e, r, s) {
|
|
124
|
+
this.state = y, this.bodhiext = null, this.refreshPromise = null, this.logger = new R("WindowBodhiextClient", e.logLevel), this.authClientId = t, this.config = e, this.authEndpoints = x(this.config.authServerUrl), this.onStateChange = r ?? k, this.storageKeys = L(s || "/");
|
|
127
125
|
}
|
|
128
126
|
/**
|
|
129
127
|
* Set client state and notify callback
|
|
130
128
|
*/
|
|
131
|
-
setState(
|
|
132
|
-
this.state =
|
|
129
|
+
setState(t) {
|
|
130
|
+
this.state = t, this.logger.info(`{state: ${JSON.stringify(t)}} - Setting client state`), this.onStateChange({ type: "client-state", state: t });
|
|
133
131
|
}
|
|
134
132
|
/**
|
|
135
133
|
* Set auth state and notify callback
|
|
136
134
|
*/
|
|
137
|
-
setAuthState(
|
|
138
|
-
this.onStateChange({ type: "auth-state", state:
|
|
135
|
+
setAuthState(t) {
|
|
136
|
+
this.onStateChange({ type: "auth-state", state: t });
|
|
139
137
|
}
|
|
140
138
|
/**
|
|
141
139
|
* Set or update the state change callback
|
|
142
140
|
*/
|
|
143
|
-
setStateCallback(
|
|
144
|
-
this.onStateChange =
|
|
141
|
+
setStateCallback(t) {
|
|
142
|
+
this.onStateChange = t;
|
|
145
143
|
}
|
|
146
144
|
// ============================================================================
|
|
147
145
|
// Extension Communication
|
|
@@ -157,14 +155,14 @@ class D {
|
|
|
157
155
|
/**
|
|
158
156
|
* Send extension request via window.bodhiext.sendExtRequest
|
|
159
157
|
*/
|
|
160
|
-
async sendExtRequest(
|
|
161
|
-
return this.ensureBodhiext(), this.bodhiext.sendExtRequest(
|
|
158
|
+
async sendExtRequest(t, e) {
|
|
159
|
+
return this.ensureBodhiext(), this.bodhiext.sendExtRequest(t, e);
|
|
162
160
|
}
|
|
163
161
|
/**
|
|
164
162
|
* Send API message via window.bodhiext.sendApiRequest
|
|
165
163
|
* Converts ApiResponse to ApiResponseResult
|
|
166
164
|
*/
|
|
167
|
-
async sendApiRequest(
|
|
165
|
+
async sendApiRequest(t, e, r, s, a) {
|
|
168
166
|
try {
|
|
169
167
|
this.ensureBodhiext();
|
|
170
168
|
} catch (o) {
|
|
@@ -177,9 +175,9 @@ class D {
|
|
|
177
175
|
}
|
|
178
176
|
try {
|
|
179
177
|
let o = s || {};
|
|
180
|
-
if (
|
|
181
|
-
const
|
|
182
|
-
if (!
|
|
178
|
+
if (a) {
|
|
179
|
+
const n = await this._getAccessTokenRaw();
|
|
180
|
+
if (!n)
|
|
183
181
|
return {
|
|
184
182
|
error: {
|
|
185
183
|
message: "Not authenticated. Please log in first.",
|
|
@@ -188,21 +186,21 @@ class D {
|
|
|
188
186
|
};
|
|
189
187
|
o = {
|
|
190
188
|
...o,
|
|
191
|
-
Authorization: `Bearer ${
|
|
189
|
+
Authorization: `Bearer ${n}`
|
|
192
190
|
};
|
|
193
191
|
}
|
|
194
192
|
return await this.bodhiext.sendApiRequest(
|
|
195
|
-
e,
|
|
196
193
|
t,
|
|
194
|
+
e,
|
|
197
195
|
r,
|
|
198
196
|
o
|
|
199
197
|
);
|
|
200
198
|
} catch (o) {
|
|
201
|
-
const
|
|
199
|
+
const c = o?.error, n = c?.message ?? (o instanceof Error ? o.message : String(o)), i = c?.type || "extension_error";
|
|
202
200
|
return {
|
|
203
201
|
error: {
|
|
204
|
-
message:
|
|
205
|
-
type:
|
|
202
|
+
message: n,
|
|
203
|
+
type: i
|
|
206
204
|
}
|
|
207
205
|
};
|
|
208
206
|
}
|
|
@@ -226,43 +224,43 @@ class D {
|
|
|
226
224
|
* Note: Web mode uses stateless discovery (always polls for window.bodhiext)
|
|
227
225
|
* No extensionId storage/restoration needed - window.bodhiext handle is ephemeral
|
|
228
226
|
*/
|
|
229
|
-
async init(
|
|
230
|
-
if (!
|
|
231
|
-
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"),
|
|
232
|
-
if (this.bodhiext && !
|
|
227
|
+
async init(t = {}) {
|
|
228
|
+
if (!t.testConnection && !t.selectedConnection)
|
|
229
|
+
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), y;
|
|
230
|
+
if (this.bodhiext && !t.testConnection)
|
|
233
231
|
return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
|
|
234
232
|
if (!this.bodhiext) {
|
|
235
|
-
const s =
|
|
236
|
-
if (!await new Promise((
|
|
237
|
-
const
|
|
233
|
+
const s = t.timeoutMs ?? this.config.initParams?.extension?.timeoutMs ?? D, a = t.intervalMs ?? this.config.initParams?.extension?.intervalMs ?? N, o = Date.now();
|
|
234
|
+
if (!await new Promise((n) => {
|
|
235
|
+
const i = () => {
|
|
238
236
|
if (window.bodhiext) {
|
|
239
|
-
this.bodhiext = window.bodhiext,
|
|
237
|
+
this.bodhiext = window.bodhiext, n(!0);
|
|
240
238
|
return;
|
|
241
239
|
}
|
|
242
240
|
if (Date.now() - o >= s) {
|
|
243
|
-
|
|
241
|
+
n(!1);
|
|
244
242
|
return;
|
|
245
243
|
}
|
|
246
|
-
setTimeout(
|
|
244
|
+
setTimeout(i, a);
|
|
247
245
|
};
|
|
248
|
-
|
|
246
|
+
i();
|
|
249
247
|
}))
|
|
250
248
|
return this.logger.warn("Extension discovery timed out"), this.setState(A), this.state;
|
|
251
249
|
}
|
|
252
|
-
const
|
|
253
|
-
this.logger.info(`Extension discovered: ${
|
|
250
|
+
const e = await this.bodhiext.getExtensionId();
|
|
251
|
+
this.logger.info(`Extension discovered: ${e}`);
|
|
254
252
|
const r = {
|
|
255
253
|
type: "extension",
|
|
256
254
|
extension: "ready",
|
|
257
|
-
extensionId:
|
|
258
|
-
server:
|
|
255
|
+
extensionId: e,
|
|
256
|
+
server: v
|
|
259
257
|
};
|
|
260
|
-
if (
|
|
258
|
+
if (t.testConnection)
|
|
261
259
|
try {
|
|
262
260
|
const s = await this.getServerState();
|
|
263
261
|
this.setState({ ...r, server: s }), this.logger.info(`Server connectivity tested: ${s.status}`);
|
|
264
262
|
} catch (s) {
|
|
265
|
-
this.logger.error("Failed to get server state:", s), this.setState({ ...r, server:
|
|
263
|
+
this.logger.error("Failed to get server state:", s), this.setState({ ...r, server: d });
|
|
266
264
|
}
|
|
267
265
|
else
|
|
268
266
|
this.setState(r);
|
|
@@ -276,66 +274,74 @@ class D {
|
|
|
276
274
|
* Required for authenticated API access
|
|
277
275
|
*/
|
|
278
276
|
async requestResourceAccess() {
|
|
279
|
-
this.ensureBodhiext()
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
throw new Error("Failed to get resource access scope: API error");
|
|
285
|
-
const t = e.body.scope;
|
|
286
|
-
return localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, t), t;
|
|
277
|
+
return this.ensureBodhiext(), this.bodhiext.sendApiRequest(
|
|
278
|
+
"POST",
|
|
279
|
+
"/bodhi/v1/apps/request-access",
|
|
280
|
+
{ app_client_id: this.authClientId }
|
|
281
|
+
);
|
|
287
282
|
}
|
|
288
283
|
/**
|
|
289
284
|
* Login via browser redirect OAuth2 + PKCE flow
|
|
290
|
-
* @returns
|
|
285
|
+
* @returns AuthState (though in practice, this redirects and never returns)
|
|
291
286
|
*/
|
|
292
287
|
async login() {
|
|
293
|
-
const
|
|
294
|
-
if (
|
|
295
|
-
return
|
|
288
|
+
const t = await this.getAuthState();
|
|
289
|
+
if (t.status === "authenticated")
|
|
290
|
+
return t;
|
|
296
291
|
this.ensureBodhiext();
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
292
|
+
const e = await this.requestResourceAccess();
|
|
293
|
+
if (f(e))
|
|
294
|
+
throw h(e.error.message, e.error.type);
|
|
295
|
+
if (w(e)) {
|
|
296
|
+
const { message: l } = e.body.error;
|
|
297
|
+
throw h(l, "auth_error");
|
|
298
|
+
}
|
|
299
|
+
if (!m(e))
|
|
300
|
+
throw h(`Unexpected HTTP ${e.status}`, "auth_error");
|
|
301
|
+
const r = e.body.scope;
|
|
302
|
+
localStorage.setItem(this.storageKeys.RESOURCE_SCOPE, r);
|
|
303
|
+
const s = g(), a = await T(s), o = g();
|
|
304
|
+
localStorage.setItem(this.storageKeys.CODE_VERIFIER, s), localStorage.setItem(this.storageKeys.STATE, o);
|
|
305
|
+
const c = ["openid", "profile", "email", "roles", this.config.userScope, r], n = new URLSearchParams({
|
|
300
306
|
response_type: "code",
|
|
301
307
|
client_id: this.authClientId,
|
|
302
308
|
redirect_uri: this.config.redirectUri,
|
|
303
|
-
scope:
|
|
304
|
-
state:
|
|
305
|
-
code_challenge:
|
|
309
|
+
scope: c.join(" "),
|
|
310
|
+
state: o,
|
|
311
|
+
code_challenge: a,
|
|
306
312
|
code_challenge_method: "S256"
|
|
307
|
-
}),
|
|
308
|
-
return window.location.href =
|
|
313
|
+
}), i = `${this.authEndpoints.authorize}?${n}`;
|
|
314
|
+
return window.location.href = i, new Promise(() => {
|
|
309
315
|
});
|
|
310
316
|
}
|
|
311
317
|
/**
|
|
312
318
|
* Handle OAuth callback with authorization code
|
|
313
319
|
* Should be called from callback page with extracted URL params
|
|
314
|
-
* @returns
|
|
320
|
+
* @returns AuthState with login state and user info
|
|
315
321
|
*/
|
|
316
|
-
async handleOAuthCallback(
|
|
322
|
+
async handleOAuthCallback(t, e) {
|
|
317
323
|
const r = localStorage.getItem(this.storageKeys.STATE);
|
|
318
|
-
if (!r || r !==
|
|
324
|
+
if (!r || r !== e)
|
|
319
325
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
320
|
-
await this.exchangeCodeForTokens(
|
|
326
|
+
await this.exchangeCodeForTokens(t), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
|
|
321
327
|
const s = await this.getAuthState();
|
|
322
|
-
if (
|
|
328
|
+
if (s.status !== "authenticated")
|
|
323
329
|
throw new Error("Login failed");
|
|
324
330
|
return this.setAuthState(s), s;
|
|
325
331
|
}
|
|
326
332
|
/**
|
|
327
333
|
* Exchange authorization code for tokens
|
|
328
334
|
*/
|
|
329
|
-
async exchangeCodeForTokens(
|
|
330
|
-
const
|
|
331
|
-
if (!
|
|
335
|
+
async exchangeCodeForTokens(t) {
|
|
336
|
+
const e = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
|
|
337
|
+
if (!e)
|
|
332
338
|
throw new Error("Code verifier not found");
|
|
333
339
|
const r = new URLSearchParams({
|
|
334
340
|
grant_type: "authorization_code",
|
|
335
341
|
client_id: this.authClientId,
|
|
336
|
-
code:
|
|
342
|
+
code: t,
|
|
337
343
|
redirect_uri: this.config.redirectUri,
|
|
338
|
-
code_verifier:
|
|
344
|
+
code_verifier: e
|
|
339
345
|
}), s = await fetch(this.authEndpoints.token, {
|
|
340
346
|
method: "POST",
|
|
341
347
|
headers: {
|
|
@@ -347,11 +353,11 @@ class D {
|
|
|
347
353
|
const o = await s.text();
|
|
348
354
|
throw new Error(`Token exchange failed: ${s.status} ${o}`);
|
|
349
355
|
}
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
356
|
+
const a = await s.json();
|
|
357
|
+
if (!a.access_token)
|
|
352
358
|
throw new Error("No access token received");
|
|
353
|
-
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN,
|
|
354
|
-
const o = Date.now() +
|
|
359
|
+
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN, a.access_token), a.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, a.refresh_token), a.expires_in) {
|
|
360
|
+
const o = Date.now() + a.expires_in * 1e3;
|
|
355
361
|
localStorage.setItem(this.storageKeys.EXPIRES_AT, o.toString());
|
|
356
362
|
}
|
|
357
363
|
}
|
|
@@ -360,11 +366,11 @@ class D {
|
|
|
360
366
|
* @returns AuthLoggedOut with logged out state
|
|
361
367
|
*/
|
|
362
368
|
async logout() {
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
369
|
+
const t = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
370
|
+
if (t)
|
|
365
371
|
try {
|
|
366
372
|
const r = new URLSearchParams({
|
|
367
|
-
token:
|
|
373
|
+
token: t,
|
|
368
374
|
client_id: this.authClientId,
|
|
369
375
|
token_type_hint: "refresh_token"
|
|
370
376
|
});
|
|
@@ -379,22 +385,25 @@ class D {
|
|
|
379
385
|
this.logger.warn("Token revocation failed:", r);
|
|
380
386
|
}
|
|
381
387
|
localStorage.removeItem(this.storageKeys.ACCESS_TOKEN), localStorage.removeItem(this.storageKeys.REFRESH_TOKEN), localStorage.removeItem(this.storageKeys.EXPIRES_AT), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE), localStorage.removeItem(this.storageKeys.RESOURCE_SCOPE);
|
|
382
|
-
const
|
|
383
|
-
|
|
388
|
+
const e = {
|
|
389
|
+
status: "unauthenticated",
|
|
390
|
+
user: null,
|
|
391
|
+
accessToken: null,
|
|
392
|
+
error: null
|
|
384
393
|
};
|
|
385
|
-
return this.setAuthState(
|
|
394
|
+
return this.setAuthState(e), e;
|
|
386
395
|
}
|
|
387
396
|
/**
|
|
388
397
|
* Get current authentication state
|
|
389
398
|
*/
|
|
390
399
|
async getAuthState() {
|
|
391
|
-
const
|
|
392
|
-
if (!
|
|
393
|
-
return {
|
|
400
|
+
const t = await this._getAccessTokenRaw();
|
|
401
|
+
if (!t)
|
|
402
|
+
return { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
394
403
|
try {
|
|
395
|
-
return {
|
|
396
|
-
} catch (
|
|
397
|
-
return this.logger.error("Failed to parse token:",
|
|
404
|
+
return { status: "authenticated", user: _(t), accessToken: t, error: null };
|
|
405
|
+
} catch (e) {
|
|
406
|
+
return this.logger.error("Failed to parse token:", e), { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
398
407
|
}
|
|
399
408
|
}
|
|
400
409
|
/**
|
|
@@ -402,26 +411,26 @@ class D {
|
|
|
402
411
|
* Returns null if not logged in or token expired
|
|
403
412
|
*/
|
|
404
413
|
async _getAccessTokenRaw() {
|
|
405
|
-
const
|
|
406
|
-
if (!
|
|
414
|
+
const t = localStorage.getItem(this.storageKeys.ACCESS_TOKEN), e = localStorage.getItem(this.storageKeys.EXPIRES_AT);
|
|
415
|
+
if (!t)
|
|
407
416
|
return null;
|
|
408
|
-
if (
|
|
409
|
-
const r = parseInt(
|
|
417
|
+
if (e) {
|
|
418
|
+
const r = parseInt(e, 10);
|
|
410
419
|
if (Date.now() >= r - 5 * 1e3) {
|
|
411
420
|
const s = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
412
421
|
return s ? this._tryRefreshToken(s) : null;
|
|
413
422
|
}
|
|
414
423
|
}
|
|
415
|
-
return
|
|
424
|
+
return t;
|
|
416
425
|
}
|
|
417
426
|
/**
|
|
418
427
|
* Try to refresh access token using refresh token
|
|
419
428
|
* Race condition prevention: Returns existing promise if refresh already in progress
|
|
420
429
|
*/
|
|
421
|
-
async _tryRefreshToken(
|
|
430
|
+
async _tryRefreshToken(t) {
|
|
422
431
|
if (this.refreshPromise)
|
|
423
432
|
return this.logger.debug("Refresh already in progress, returning existing promise"), this.refreshPromise;
|
|
424
|
-
this.refreshPromise = this._doRefreshToken(
|
|
433
|
+
this.refreshPromise = this._doRefreshToken(t);
|
|
425
434
|
try {
|
|
426
435
|
return await this.refreshPromise;
|
|
427
436
|
} finally {
|
|
@@ -431,25 +440,26 @@ class D {
|
|
|
431
440
|
/**
|
|
432
441
|
* Perform the actual token refresh
|
|
433
442
|
*/
|
|
434
|
-
async _doRefreshToken(
|
|
443
|
+
async _doRefreshToken(t) {
|
|
435
444
|
this.logger.debug("Refreshing access token");
|
|
436
445
|
try {
|
|
437
|
-
const
|
|
446
|
+
const e = await b(
|
|
438
447
|
this.authEndpoints.token,
|
|
439
|
-
|
|
448
|
+
t,
|
|
440
449
|
this.authClientId
|
|
441
450
|
);
|
|
442
|
-
if (
|
|
443
|
-
this._storeRefreshedTokens(
|
|
444
|
-
const r =
|
|
451
|
+
if (e) {
|
|
452
|
+
this._storeRefreshedTokens(e);
|
|
453
|
+
const r = _(e.access_token);
|
|
445
454
|
return this.setAuthState({
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
accessToken:
|
|
449
|
-
|
|
455
|
+
status: "authenticated",
|
|
456
|
+
user: r,
|
|
457
|
+
accessToken: e.access_token,
|
|
458
|
+
error: null
|
|
459
|
+
}), this.logger.info("Token refreshed successfully"), e.access_token;
|
|
450
460
|
}
|
|
451
|
-
} catch (
|
|
452
|
-
this.logger.warn("Token refresh failed:",
|
|
461
|
+
} catch (e) {
|
|
462
|
+
this.logger.warn("Token refresh failed:", e);
|
|
453
463
|
}
|
|
454
464
|
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), h(
|
|
455
465
|
"Access token expired and unable to refresh. Try logging out and logging in again.",
|
|
@@ -459,9 +469,9 @@ class D {
|
|
|
459
469
|
/**
|
|
460
470
|
* Store refreshed tokens
|
|
461
471
|
*/
|
|
462
|
-
_storeRefreshedTokens(
|
|
463
|
-
const
|
|
464
|
-
localStorage.setItem(this.storageKeys.ACCESS_TOKEN,
|
|
472
|
+
_storeRefreshedTokens(t) {
|
|
473
|
+
const e = Date.now() + t.expires_in * 1e3;
|
|
474
|
+
localStorage.setItem(this.storageKeys.ACCESS_TOKEN, t.access_token), localStorage.setItem(this.storageKeys.EXPIRES_AT, String(e)), t.refresh_token && localStorage.setItem(this.storageKeys.REFRESH_TOKEN, t.refresh_token);
|
|
465
475
|
}
|
|
466
476
|
/**
|
|
467
477
|
* Ping API
|
|
@@ -486,76 +496,76 @@ class D {
|
|
|
486
496
|
* Calls /bodhi/v1/info and returns structured server state
|
|
487
497
|
*/
|
|
488
498
|
async getServerState() {
|
|
489
|
-
const
|
|
490
|
-
if (
|
|
491
|
-
return
|
|
492
|
-
if (!
|
|
493
|
-
return
|
|
494
|
-
const
|
|
495
|
-
switch (
|
|
499
|
+
const t = await this.sendApiRequest("GET", "/bodhi/v1/info");
|
|
500
|
+
if (f(t))
|
|
501
|
+
return d;
|
|
502
|
+
if (!m(t))
|
|
503
|
+
return d;
|
|
504
|
+
const e = t.body;
|
|
505
|
+
switch (e.status) {
|
|
496
506
|
case "ready":
|
|
497
|
-
return { status: "ready", version:
|
|
507
|
+
return { status: "ready", version: e.version || "unknown", error: null };
|
|
498
508
|
case "setup":
|
|
499
|
-
return E("setup",
|
|
509
|
+
return E("setup", e.version || "unknown");
|
|
500
510
|
case "resource-admin":
|
|
501
|
-
return E("resource-admin",
|
|
511
|
+
return E("resource-admin", e.version || "unknown");
|
|
502
512
|
case "error":
|
|
503
513
|
return E(
|
|
504
514
|
"error",
|
|
505
|
-
|
|
506
|
-
|
|
515
|
+
e.version || "unknown",
|
|
516
|
+
e.error ? { message: e.error.message, type: e.error.type } : O.SERVER_NOT_READY
|
|
507
517
|
);
|
|
508
518
|
default:
|
|
509
|
-
return
|
|
519
|
+
return d;
|
|
510
520
|
}
|
|
511
521
|
}
|
|
512
522
|
/**
|
|
513
523
|
* Generic streaming via window.bodhiext.sendStreamRequest
|
|
514
524
|
* Wraps ReadableStream as AsyncGenerator
|
|
515
525
|
*/
|
|
516
|
-
async *stream(
|
|
526
|
+
async *stream(t, e, r, s, a = !0) {
|
|
517
527
|
this.ensureBodhiext();
|
|
518
528
|
let o = s || {};
|
|
519
|
-
if (
|
|
520
|
-
const
|
|
521
|
-
if (!
|
|
529
|
+
if (a) {
|
|
530
|
+
const i = await this._getAccessTokenRaw();
|
|
531
|
+
if (!i)
|
|
522
532
|
throw h("Not authenticated. Please log in first.", "auth_error");
|
|
523
533
|
o = {
|
|
524
534
|
...o,
|
|
525
|
-
Authorization: `Bearer ${
|
|
535
|
+
Authorization: `Bearer ${i}`
|
|
526
536
|
};
|
|
527
537
|
}
|
|
528
|
-
const
|
|
538
|
+
const n = this.bodhiext.sendStreamRequest(t, e, r, o).getReader();
|
|
529
539
|
try {
|
|
530
540
|
for (; ; ) {
|
|
531
|
-
const { value:
|
|
532
|
-
if (
|
|
541
|
+
const { value: i, done: l } = await n.read();
|
|
542
|
+
if (l || i?.done)
|
|
533
543
|
break;
|
|
534
|
-
yield
|
|
544
|
+
yield i.body;
|
|
535
545
|
}
|
|
536
|
-
} catch (
|
|
537
|
-
if (
|
|
538
|
-
if ("response" in
|
|
539
|
-
const
|
|
540
|
-
throw
|
|
546
|
+
} catch (i) {
|
|
547
|
+
if (i instanceof Error) {
|
|
548
|
+
if ("response" in i) {
|
|
549
|
+
const l = i;
|
|
550
|
+
throw P(i.message, l.response.status, l.response.body);
|
|
541
551
|
}
|
|
542
|
-
throw "error" in
|
|
552
|
+
throw "error" in i ? h(i.message, "extension_error") : h(i.message, "extension_error");
|
|
543
553
|
}
|
|
544
|
-
throw
|
|
554
|
+
throw i;
|
|
545
555
|
} finally {
|
|
546
|
-
|
|
556
|
+
n.releaseLock();
|
|
547
557
|
}
|
|
548
558
|
}
|
|
549
559
|
/**
|
|
550
560
|
* Chat streaming
|
|
551
561
|
*/
|
|
552
|
-
async *streamChat(
|
|
562
|
+
async *streamChat(t, e, r = !0) {
|
|
553
563
|
yield* this.stream(
|
|
554
564
|
"POST",
|
|
555
565
|
"/v1/chat/completions",
|
|
556
566
|
{
|
|
557
|
-
model:
|
|
558
|
-
messages: [{ role: "user", content:
|
|
567
|
+
model: t,
|
|
568
|
+
messages: [{ role: "user", content: e }],
|
|
559
569
|
stream: !0
|
|
560
570
|
},
|
|
561
571
|
void 0,
|
|
@@ -586,34 +596,34 @@ class D {
|
|
|
586
596
|
};
|
|
587
597
|
}
|
|
588
598
|
}
|
|
589
|
-
class
|
|
590
|
-
constructor(
|
|
591
|
-
const
|
|
592
|
-
redirectUri:
|
|
593
|
-
authServerUrl:
|
|
594
|
-
userScope:
|
|
595
|
-
basePath:
|
|
596
|
-
logLevel:
|
|
597
|
-
initParams:
|
|
599
|
+
class q extends K {
|
|
600
|
+
constructor(t, e, r, s) {
|
|
601
|
+
const a = {
|
|
602
|
+
redirectUri: e.redirectUri,
|
|
603
|
+
authServerUrl: e.authServerUrl || "https://id.getbodhi.app/realms/bodhi",
|
|
604
|
+
userScope: e.userScope || "scope_user_user",
|
|
605
|
+
basePath: e.basePath || "/",
|
|
606
|
+
logLevel: e.logLevel || "warn",
|
|
607
|
+
initParams: e.initParams
|
|
598
608
|
};
|
|
599
|
-
super(
|
|
609
|
+
super(t, a, r, s, e.basePath);
|
|
600
610
|
}
|
|
601
|
-
createLogger(
|
|
602
|
-
return new
|
|
611
|
+
createLogger(t) {
|
|
612
|
+
return new R("WebUIClient", t.logLevel);
|
|
603
613
|
}
|
|
604
|
-
createExtClient(
|
|
605
|
-
return new
|
|
614
|
+
createExtClient(t, e) {
|
|
615
|
+
return new F(this.authClientId, t, e, t.basePath);
|
|
606
616
|
}
|
|
607
|
-
createDirectClient(
|
|
608
|
-
return new
|
|
617
|
+
createDirectClient(t, e, r) {
|
|
618
|
+
return new U(
|
|
609
619
|
{
|
|
610
|
-
authClientId:
|
|
611
|
-
authServerUrl:
|
|
612
|
-
redirectUri:
|
|
613
|
-
userScope:
|
|
614
|
-
logLevel:
|
|
620
|
+
authClientId: t,
|
|
621
|
+
authServerUrl: e.authServerUrl,
|
|
622
|
+
redirectUri: e.redirectUri,
|
|
623
|
+
userScope: e.userScope,
|
|
624
|
+
logLevel: e.logLevel,
|
|
615
625
|
storagePrefix: S.WEB,
|
|
616
|
-
basePath:
|
|
626
|
+
basePath: e.basePath
|
|
617
627
|
},
|
|
618
628
|
r
|
|
619
629
|
);
|
|
@@ -621,12 +631,12 @@ class B extends P {
|
|
|
621
631
|
// ============================================================================
|
|
622
632
|
// Web-specific OAuth Callback
|
|
623
633
|
// ============================================================================
|
|
624
|
-
async handleOAuthCallback(
|
|
625
|
-
return this.connectionMode === "direct" ? this.directClient.handleOAuthCallback(
|
|
634
|
+
async handleOAuthCallback(t, e) {
|
|
635
|
+
return this.connectionMode === "direct" ? this.directClient.handleOAuthCallback(t, e) : this.extClient.handleOAuthCallback(t, e);
|
|
626
636
|
}
|
|
627
637
|
}
|
|
628
|
-
const
|
|
638
|
+
const V = "production";
|
|
629
639
|
export {
|
|
630
|
-
|
|
631
|
-
|
|
640
|
+
V as WEB_BUILD_MODE,
|
|
641
|
+
q as WebUIClient
|
|
632
642
|
};
|