@bodhiapp/bodhi-js 0.0.4 → 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/bodhiext.d.ts +0 -1
- package/dist/bodhi-browser-ext/src/types/common.d.ts +1 -0
- package/dist/bodhi-browser-ext/src/types/protocol.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/direct-client-base.d.ts +5 -5
- package/dist/bodhi-js-sdk/core/src/errors.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/facade-client-base.d.ts +5 -6
- package/dist/bodhi-js-sdk/core/src/interface.d.ts +14 -7
- package/dist/bodhi-js-sdk/core/src/logger.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/oauth.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/onboarding/config.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/onboarding/modal.d.ts +1 -2
- package/dist/bodhi-js-sdk/core/src/onboarding/protocol-utils.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/platform.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/api.d.ts +0 -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 -2
- package/dist/bodhi-js-sdk/core/src/types/client-state.d.ts +43 -94
- package/dist/bodhi-js-sdk/core/src/types/config.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/index.d.ts +4 -3
- package/dist/bodhi-js-sdk/core/src/types/platform.d.ts +0 -1
- package/dist/bodhi-js-sdk/core/src/types/user-info.d.ts +0 -32
- package/dist/bodhi-js-sdk/web/src/constants.d.ts +0 -1
- package/dist/bodhi-js-sdk/web/src/direct-client.d.ts +4 -6
- package/dist/bodhi-js-sdk/web/src/ext-client.d.ts +7 -8
- package/dist/bodhi-js-sdk/web/src/facade-client.d.ts +2 -3
- package/dist/bodhi-js-sdk/web/src/index.d.ts +0 -1
- package/dist/bodhi-web.cjs.js +1 -1
- package/dist/bodhi-web.esm.d.ts +1 -0
- package/dist/bodhi-web.esm.js +274 -265
- package/dist/setup-modal/src/types/message-types.d.ts +0 -1
- package/dist/setup-modal/src/types/protocol.d.ts +0 -1
- package/dist/setup-modal/src/types/state.d.ts +0 -1
- package/dist/setup-modal/src/types/type-guards.d.ts +0 -1
- package/package.json +10 -6
package/dist/bodhi-web.esm.js
CHANGED
|
@@ -1,38 +1,48 @@
|
|
|
1
|
-
import { DirectClientBase as
|
|
2
|
-
class
|
|
3
|
-
constructor(
|
|
4
|
-
const
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!
|
|
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);
|
|
27
|
+
const n = new URL(this.authEndpoints.authorize);
|
|
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");
|
|
29
|
+
}
|
|
30
|
+
async handleOAuthCallback(t, e) {
|
|
31
|
+
const r = localStorage.getItem(this.storageKeys.STATE);
|
|
32
|
+
if (!r || r !== e)
|
|
22
33
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
23
|
-
await this.exchangeCodeForTokens(
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
34
|
+
await this.exchangeCodeForTokens(t), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
|
|
35
|
+
const s = await this.getAuthState();
|
|
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
|
-
const
|
|
35
|
-
token:
|
|
44
|
+
const r = new URLSearchParams({
|
|
45
|
+
token: t,
|
|
36
46
|
client_id: this.authClientId,
|
|
37
47
|
token_type_hint: "refresh_token"
|
|
38
48
|
});
|
|
@@ -41,130 +51,118 @@ class L extends x {
|
|
|
41
51
|
headers: {
|
|
42
52
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
43
53
|
},
|
|
44
|
-
body:
|
|
54
|
+
body: r
|
|
45
55
|
});
|
|
46
|
-
} catch (
|
|
47
|
-
this.logger.warn("Token revocation failed:",
|
|
56
|
+
} catch (r) {
|
|
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 (T(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
|
-
const
|
|
75
|
+
const r = await fetch(this.authEndpoints.token, {
|
|
78
76
|
method: "POST",
|
|
79
77
|
headers: {
|
|
80
78
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
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
|
-
if (!
|
|
91
|
-
const
|
|
92
|
-
throw new Error(`Token exchange failed: ${
|
|
88
|
+
if (!r.ok) {
|
|
89
|
+
const a = await r.text();
|
|
90
|
+
throw new Error(`Token exchange failed: ${r.status} ${a}`);
|
|
93
91
|
}
|
|
94
|
-
const
|
|
95
|
-
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN,
|
|
96
|
-
const
|
|
97
|
-
localStorage.setItem(this.storageKeys.EXPIRES_AT,
|
|
92
|
+
const s = await r.json();
|
|
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) {
|
|
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
116
|
const N = 500, D = 5e3;
|
|
119
|
-
|
|
120
|
-
function
|
|
121
|
-
const
|
|
122
|
-
return
|
|
117
|
+
I(S.WEB);
|
|
118
|
+
function L(u = "/") {
|
|
119
|
+
const t = p(u, S.WEB);
|
|
120
|
+
return I(t);
|
|
123
121
|
}
|
|
124
|
-
class
|
|
125
|
-
constructor(e,
|
|
126
|
-
this.state = y, this.bodhiext = null, this.refreshPromise = null, this.logger = new
|
|
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
|
|
148
146
|
// ============================================================================
|
|
149
147
|
/**
|
|
150
148
|
* Ensure bodhiext is available, attempting to acquire it if not already set
|
|
151
|
-
* @throws
|
|
149
|
+
* @throws OperationError if client not initialized
|
|
152
150
|
*/
|
|
153
151
|
ensureBodhiext() {
|
|
154
152
|
if (!this.bodhiext && window.bodhiext && (this.logger.info("Acquiring window.bodhiext reference"), this.bodhiext = window.bodhiext), !this.bodhiext)
|
|
155
|
-
throw
|
|
153
|
+
throw h("Client not initialized", "extension_error");
|
|
156
154
|
}
|
|
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(e,
|
|
165
|
+
async sendApiRequest(t, e, r, s, a) {
|
|
168
166
|
try {
|
|
169
167
|
this.ensureBodhiext();
|
|
170
168
|
} catch (o) {
|
|
@@ -176,10 +174,10 @@ class B {
|
|
|
176
174
|
};
|
|
177
175
|
}
|
|
178
176
|
try {
|
|
179
|
-
let o =
|
|
180
|
-
if (
|
|
181
|
-
const
|
|
182
|
-
if (!
|
|
177
|
+
let o = s || {};
|
|
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 B {
|
|
|
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,
|
|
197
|
-
|
|
194
|
+
e,
|
|
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,47 +224,46 @@ class B {
|
|
|
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
|
-
|
|
231
|
-
if (!e.testConnection && !e.selectedConnection)
|
|
227
|
+
async init(t = {}) {
|
|
228
|
+
if (!t.testConnection && !t.selectedConnection)
|
|
232
229
|
return this.logger.info("No testConnection or selectedConnection, returning not-initialized state"), y;
|
|
233
|
-
if (this.bodhiext && !
|
|
230
|
+
if (this.bodhiext && !t.testConnection)
|
|
234
231
|
return this.logger.debug("Already have bodhiext handle, skipping polling"), this.state;
|
|
235
232
|
if (!this.bodhiext) {
|
|
236
|
-
const
|
|
237
|
-
if (!await new Promise((
|
|
238
|
-
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 = () => {
|
|
239
236
|
if (window.bodhiext) {
|
|
240
|
-
this.bodhiext = window.bodhiext,
|
|
237
|
+
this.bodhiext = window.bodhiext, n(!0);
|
|
241
238
|
return;
|
|
242
239
|
}
|
|
243
|
-
if (Date.now() -
|
|
244
|
-
|
|
240
|
+
if (Date.now() - o >= s) {
|
|
241
|
+
n(!1);
|
|
245
242
|
return;
|
|
246
243
|
}
|
|
247
|
-
setTimeout(
|
|
244
|
+
setTimeout(i, a);
|
|
248
245
|
};
|
|
249
|
-
|
|
246
|
+
i();
|
|
250
247
|
}))
|
|
251
|
-
return this.logger.warn("Extension discovery timed out"), this.setState(
|
|
248
|
+
return this.logger.warn("Extension discovery timed out"), this.setState(A), this.state;
|
|
252
249
|
}
|
|
253
|
-
const
|
|
254
|
-
this.logger.info(`Extension discovered: ${
|
|
255
|
-
const
|
|
250
|
+
const e = await this.bodhiext.getExtensionId();
|
|
251
|
+
this.logger.info(`Extension discovered: ${e}`);
|
|
252
|
+
const r = {
|
|
256
253
|
type: "extension",
|
|
257
254
|
extension: "ready",
|
|
258
|
-
extensionId:
|
|
259
|
-
server:
|
|
255
|
+
extensionId: e,
|
|
256
|
+
server: v
|
|
260
257
|
};
|
|
261
|
-
if (
|
|
258
|
+
if (t.testConnection)
|
|
262
259
|
try {
|
|
263
|
-
const
|
|
264
|
-
this.setState({ ...
|
|
265
|
-
} catch (
|
|
266
|
-
this.logger.error("Failed to get server state:",
|
|
260
|
+
const s = await this.getServerState();
|
|
261
|
+
this.setState({ ...r, server: s }), this.logger.info(`Server connectivity tested: ${s.status}`);
|
|
262
|
+
} catch (s) {
|
|
263
|
+
this.logger.error("Failed to get server state:", s), this.setState({ ...r, server: d });
|
|
267
264
|
}
|
|
268
265
|
else
|
|
269
|
-
this.setState(
|
|
266
|
+
this.setState(r);
|
|
270
267
|
return this.state;
|
|
271
268
|
}
|
|
272
269
|
// ============================================================================
|
|
@@ -277,82 +274,90 @@ class B {
|
|
|
277
274
|
* Required for authenticated API access
|
|
278
275
|
*/
|
|
279
276
|
async requestResourceAccess() {
|
|
280
|
-
this.ensureBodhiext()
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
throw new Error("Failed to get resource access scope: API error");
|
|
286
|
-
const t = e.body.scope;
|
|
287
|
-
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
|
+
);
|
|
288
282
|
}
|
|
289
283
|
/**
|
|
290
284
|
* Login via browser redirect OAuth2 + PKCE flow
|
|
291
|
-
* @returns
|
|
285
|
+
* @returns AuthState (though in practice, this redirects and never returns)
|
|
292
286
|
*/
|
|
293
287
|
async login() {
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
return
|
|
288
|
+
const t = await this.getAuthState();
|
|
289
|
+
if (t.status === "authenticated")
|
|
290
|
+
return t;
|
|
297
291
|
this.ensureBodhiext();
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
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({
|
|
301
306
|
response_type: "code",
|
|
302
307
|
client_id: this.authClientId,
|
|
303
308
|
redirect_uri: this.config.redirectUri,
|
|
304
|
-
scope:
|
|
305
|
-
state:
|
|
306
|
-
code_challenge:
|
|
309
|
+
scope: c.join(" "),
|
|
310
|
+
state: o,
|
|
311
|
+
code_challenge: a,
|
|
307
312
|
code_challenge_method: "S256"
|
|
308
|
-
}),
|
|
309
|
-
return window.location.href =
|
|
313
|
+
}), i = `${this.authEndpoints.authorize}?${n}`;
|
|
314
|
+
return window.location.href = i, new Promise(() => {
|
|
310
315
|
});
|
|
311
316
|
}
|
|
312
317
|
/**
|
|
313
318
|
* Handle OAuth callback with authorization code
|
|
314
319
|
* Should be called from callback page with extracted URL params
|
|
315
|
-
* @returns
|
|
320
|
+
* @returns AuthState with login state and user info
|
|
316
321
|
*/
|
|
317
|
-
async handleOAuthCallback(
|
|
318
|
-
const
|
|
319
|
-
if (!
|
|
322
|
+
async handleOAuthCallback(t, e) {
|
|
323
|
+
const r = localStorage.getItem(this.storageKeys.STATE);
|
|
324
|
+
if (!r || r !== e)
|
|
320
325
|
throw new Error("Invalid state parameter - possible CSRF attack");
|
|
321
|
-
await this.exchangeCodeForTokens(
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
326
|
+
await this.exchangeCodeForTokens(t), localStorage.removeItem(this.storageKeys.CODE_VERIFIER), localStorage.removeItem(this.storageKeys.STATE);
|
|
327
|
+
const s = await this.getAuthState();
|
|
328
|
+
if (s.status !== "authenticated")
|
|
324
329
|
throw new Error("Login failed");
|
|
325
|
-
return this.setAuthState(
|
|
330
|
+
return this.setAuthState(s), s;
|
|
326
331
|
}
|
|
327
332
|
/**
|
|
328
333
|
* Exchange authorization code for tokens
|
|
329
334
|
*/
|
|
330
|
-
async exchangeCodeForTokens(
|
|
331
|
-
const
|
|
332
|
-
if (!
|
|
335
|
+
async exchangeCodeForTokens(t) {
|
|
336
|
+
const e = localStorage.getItem(this.storageKeys.CODE_VERIFIER);
|
|
337
|
+
if (!e)
|
|
333
338
|
throw new Error("Code verifier not found");
|
|
334
|
-
const
|
|
339
|
+
const r = new URLSearchParams({
|
|
335
340
|
grant_type: "authorization_code",
|
|
336
341
|
client_id: this.authClientId,
|
|
337
|
-
code:
|
|
342
|
+
code: t,
|
|
338
343
|
redirect_uri: this.config.redirectUri,
|
|
339
|
-
code_verifier:
|
|
340
|
-
}),
|
|
344
|
+
code_verifier: e
|
|
345
|
+
}), s = await fetch(this.authEndpoints.token, {
|
|
341
346
|
method: "POST",
|
|
342
347
|
headers: {
|
|
343
348
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
344
349
|
},
|
|
345
|
-
body:
|
|
350
|
+
body: r
|
|
346
351
|
});
|
|
347
|
-
if (!
|
|
348
|
-
const o = await
|
|
349
|
-
throw new Error(`Token exchange failed: ${
|
|
352
|
+
if (!s.ok) {
|
|
353
|
+
const o = await s.text();
|
|
354
|
+
throw new Error(`Token exchange failed: ${s.status} ${o}`);
|
|
350
355
|
}
|
|
351
|
-
const
|
|
352
|
-
if (!
|
|
356
|
+
const a = await s.json();
|
|
357
|
+
if (!a.access_token)
|
|
353
358
|
throw new Error("No access token received");
|
|
354
|
-
if (localStorage.setItem(this.storageKeys.ACCESS_TOKEN,
|
|
355
|
-
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;
|
|
356
361
|
localStorage.setItem(this.storageKeys.EXPIRES_AT, o.toString());
|
|
357
362
|
}
|
|
358
363
|
}
|
|
@@ -361,11 +366,11 @@ class B {
|
|
|
361
366
|
* @returns AuthLoggedOut with logged out state
|
|
362
367
|
*/
|
|
363
368
|
async logout() {
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
369
|
+
const t = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
370
|
+
if (t)
|
|
366
371
|
try {
|
|
367
|
-
const
|
|
368
|
-
token:
|
|
372
|
+
const r = new URLSearchParams({
|
|
373
|
+
token: t,
|
|
369
374
|
client_id: this.authClientId,
|
|
370
375
|
token_type_hint: "refresh_token"
|
|
371
376
|
});
|
|
@@ -374,28 +379,31 @@ class B {
|
|
|
374
379
|
headers: {
|
|
375
380
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
376
381
|
},
|
|
377
|
-
body:
|
|
382
|
+
body: r
|
|
378
383
|
});
|
|
379
|
-
} catch (
|
|
380
|
-
this.logger.warn("Token revocation failed:",
|
|
384
|
+
} catch (r) {
|
|
385
|
+
this.logger.warn("Token revocation failed:", r);
|
|
381
386
|
}
|
|
382
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);
|
|
383
|
-
const
|
|
384
|
-
|
|
388
|
+
const e = {
|
|
389
|
+
status: "unauthenticated",
|
|
390
|
+
user: null,
|
|
391
|
+
accessToken: null,
|
|
392
|
+
error: null
|
|
385
393
|
};
|
|
386
|
-
return this.setAuthState(
|
|
394
|
+
return this.setAuthState(e), e;
|
|
387
395
|
}
|
|
388
396
|
/**
|
|
389
397
|
* Get current authentication state
|
|
390
398
|
*/
|
|
391
399
|
async getAuthState() {
|
|
392
|
-
const
|
|
393
|
-
if (!
|
|
394
|
-
return {
|
|
400
|
+
const t = await this._getAccessTokenRaw();
|
|
401
|
+
if (!t)
|
|
402
|
+
return { status: "unauthenticated", user: null, accessToken: null, error: null };
|
|
395
403
|
try {
|
|
396
|
-
return {
|
|
397
|
-
} catch (
|
|
398
|
-
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 };
|
|
399
407
|
}
|
|
400
408
|
}
|
|
401
409
|
/**
|
|
@@ -403,26 +411,26 @@ class B {
|
|
|
403
411
|
* Returns null if not logged in or token expired
|
|
404
412
|
*/
|
|
405
413
|
async _getAccessTokenRaw() {
|
|
406
|
-
const
|
|
407
|
-
if (!
|
|
414
|
+
const t = localStorage.getItem(this.storageKeys.ACCESS_TOKEN), e = localStorage.getItem(this.storageKeys.EXPIRES_AT);
|
|
415
|
+
if (!t)
|
|
408
416
|
return null;
|
|
409
|
-
if (
|
|
410
|
-
const
|
|
411
|
-
if (Date.now() >=
|
|
412
|
-
const
|
|
413
|
-
return
|
|
417
|
+
if (e) {
|
|
418
|
+
const r = parseInt(e, 10);
|
|
419
|
+
if (Date.now() >= r - 5 * 1e3) {
|
|
420
|
+
const s = localStorage.getItem(this.storageKeys.REFRESH_TOKEN);
|
|
421
|
+
return s ? this._tryRefreshToken(s) : null;
|
|
414
422
|
}
|
|
415
423
|
}
|
|
416
|
-
return
|
|
424
|
+
return t;
|
|
417
425
|
}
|
|
418
426
|
/**
|
|
419
427
|
* Try to refresh access token using refresh token
|
|
420
428
|
* Race condition prevention: Returns existing promise if refresh already in progress
|
|
421
429
|
*/
|
|
422
|
-
async _tryRefreshToken(
|
|
430
|
+
async _tryRefreshToken(t) {
|
|
423
431
|
if (this.refreshPromise)
|
|
424
432
|
return this.logger.debug("Refresh already in progress, returning existing promise"), this.refreshPromise;
|
|
425
|
-
this.refreshPromise = this._doRefreshToken(
|
|
433
|
+
this.refreshPromise = this._doRefreshToken(t);
|
|
426
434
|
try {
|
|
427
435
|
return await this.refreshPromise;
|
|
428
436
|
} finally {
|
|
@@ -432,27 +440,28 @@ class B {
|
|
|
432
440
|
/**
|
|
433
441
|
* Perform the actual token refresh
|
|
434
442
|
*/
|
|
435
|
-
async _doRefreshToken(
|
|
443
|
+
async _doRefreshToken(t) {
|
|
436
444
|
this.logger.debug("Refreshing access token");
|
|
437
445
|
try {
|
|
438
|
-
const
|
|
446
|
+
const e = await b(
|
|
439
447
|
this.authEndpoints.token,
|
|
440
|
-
|
|
448
|
+
t,
|
|
441
449
|
this.authClientId
|
|
442
450
|
);
|
|
443
|
-
if (
|
|
444
|
-
this._storeRefreshedTokens(
|
|
445
|
-
const
|
|
451
|
+
if (e) {
|
|
452
|
+
this._storeRefreshedTokens(e);
|
|
453
|
+
const r = _(e.access_token);
|
|
446
454
|
return this.setAuthState({
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
accessToken:
|
|
450
|
-
|
|
455
|
+
status: "authenticated",
|
|
456
|
+
user: r,
|
|
457
|
+
accessToken: e.access_token,
|
|
458
|
+
error: null
|
|
459
|
+
}), this.logger.info("Token refreshed successfully"), e.access_token;
|
|
451
460
|
}
|
|
452
|
-
} catch (
|
|
453
|
-
this.logger.warn("Token refresh failed:",
|
|
461
|
+
} catch (e) {
|
|
462
|
+
this.logger.warn("Token refresh failed:", e);
|
|
454
463
|
}
|
|
455
|
-
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"),
|
|
464
|
+
throw this.logger.warn("Token refresh failed, keeping tokens for manual retry"), h(
|
|
456
465
|
"Access token expired and unable to refresh. Try logging out and logging in again.",
|
|
457
466
|
"token_refresh_failed"
|
|
458
467
|
);
|
|
@@ -460,9 +469,9 @@ class B {
|
|
|
460
469
|
/**
|
|
461
470
|
* Store refreshed tokens
|
|
462
471
|
*/
|
|
463
|
-
_storeRefreshedTokens(
|
|
464
|
-
const
|
|
465
|
-
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);
|
|
466
475
|
}
|
|
467
476
|
/**
|
|
468
477
|
* Ping API
|
|
@@ -487,24 +496,24 @@ class B {
|
|
|
487
496
|
* Calls /bodhi/v1/info and returns structured server state
|
|
488
497
|
*/
|
|
489
498
|
async getServerState() {
|
|
490
|
-
const
|
|
491
|
-
if (
|
|
499
|
+
const t = await this.sendApiRequest("GET", "/bodhi/v1/info");
|
|
500
|
+
if (f(t))
|
|
492
501
|
return d;
|
|
493
|
-
if (!
|
|
502
|
+
if (!m(t))
|
|
494
503
|
return d;
|
|
495
|
-
const
|
|
496
|
-
switch (
|
|
504
|
+
const e = t.body;
|
|
505
|
+
switch (e.status) {
|
|
497
506
|
case "ready":
|
|
498
|
-
return { status: "ready", version:
|
|
507
|
+
return { status: "ready", version: e.version || "unknown", error: null };
|
|
499
508
|
case "setup":
|
|
500
|
-
return E("setup",
|
|
509
|
+
return E("setup", e.version || "unknown");
|
|
501
510
|
case "resource-admin":
|
|
502
|
-
return E("resource-admin",
|
|
511
|
+
return E("resource-admin", e.version || "unknown");
|
|
503
512
|
case "error":
|
|
504
513
|
return E(
|
|
505
514
|
"error",
|
|
506
|
-
|
|
507
|
-
|
|
515
|
+
e.version || "unknown",
|
|
516
|
+
e.error ? { message: e.error.message, type: e.error.type } : O.SERVER_NOT_READY
|
|
508
517
|
);
|
|
509
518
|
default:
|
|
510
519
|
return d;
|
|
@@ -514,53 +523,53 @@ class B {
|
|
|
514
523
|
* Generic streaming via window.bodhiext.sendStreamRequest
|
|
515
524
|
* Wraps ReadableStream as AsyncGenerator
|
|
516
525
|
*/
|
|
517
|
-
async *stream(e,
|
|
526
|
+
async *stream(t, e, r, s, a = !0) {
|
|
518
527
|
this.ensureBodhiext();
|
|
519
|
-
let o =
|
|
520
|
-
if (
|
|
521
|
-
const
|
|
522
|
-
if (!
|
|
523
|
-
throw
|
|
528
|
+
let o = s || {};
|
|
529
|
+
if (a) {
|
|
530
|
+
const i = await this._getAccessTokenRaw();
|
|
531
|
+
if (!i)
|
|
532
|
+
throw h("Not authenticated. Please log in first.", "auth_error");
|
|
524
533
|
o = {
|
|
525
534
|
...o,
|
|
526
|
-
Authorization: `Bearer ${
|
|
535
|
+
Authorization: `Bearer ${i}`
|
|
527
536
|
};
|
|
528
537
|
}
|
|
529
|
-
const
|
|
538
|
+
const n = this.bodhiext.sendStreamRequest(t, e, r, o).getReader();
|
|
530
539
|
try {
|
|
531
540
|
for (; ; ) {
|
|
532
|
-
const { value:
|
|
533
|
-
if (
|
|
541
|
+
const { value: i, done: l } = await n.read();
|
|
542
|
+
if (l || i?.done)
|
|
534
543
|
break;
|
|
535
|
-
yield
|
|
544
|
+
yield i.body;
|
|
536
545
|
}
|
|
537
|
-
} catch (
|
|
538
|
-
if (
|
|
539
|
-
if ("response" in
|
|
540
|
-
const
|
|
541
|
-
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);
|
|
542
551
|
}
|
|
543
|
-
throw "error" in
|
|
552
|
+
throw "error" in i ? h(i.message, "extension_error") : h(i.message, "extension_error");
|
|
544
553
|
}
|
|
545
|
-
throw
|
|
554
|
+
throw i;
|
|
546
555
|
} finally {
|
|
547
|
-
|
|
556
|
+
n.releaseLock();
|
|
548
557
|
}
|
|
549
558
|
}
|
|
550
559
|
/**
|
|
551
560
|
* Chat streaming
|
|
552
561
|
*/
|
|
553
|
-
async *streamChat(
|
|
562
|
+
async *streamChat(t, e, r = !0) {
|
|
554
563
|
yield* this.stream(
|
|
555
564
|
"POST",
|
|
556
565
|
"/v1/chat/completions",
|
|
557
566
|
{
|
|
558
|
-
model:
|
|
559
|
-
messages: [{ role: "user", content:
|
|
567
|
+
model: t,
|
|
568
|
+
messages: [{ role: "user", content: e }],
|
|
560
569
|
stream: !0
|
|
561
570
|
},
|
|
562
571
|
void 0,
|
|
563
|
-
|
|
572
|
+
r
|
|
564
573
|
);
|
|
565
574
|
}
|
|
566
575
|
/**
|
|
@@ -587,47 +596,47 @@ class B {
|
|
|
587
596
|
};
|
|
588
597
|
}
|
|
589
598
|
}
|
|
590
|
-
class
|
|
591
|
-
constructor(e,
|
|
592
|
-
const
|
|
593
|
-
redirectUri:
|
|
594
|
-
authServerUrl:
|
|
595
|
-
userScope:
|
|
596
|
-
basePath:
|
|
597
|
-
logLevel:
|
|
598
|
-
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
|
|
599
608
|
};
|
|
600
|
-
super(
|
|
609
|
+
super(t, a, r, s, e.basePath);
|
|
601
610
|
}
|
|
602
|
-
createLogger(
|
|
603
|
-
return new
|
|
611
|
+
createLogger(t) {
|
|
612
|
+
return new R("WebUIClient", t.logLevel);
|
|
604
613
|
}
|
|
605
|
-
createExtClient(
|
|
606
|
-
return new
|
|
614
|
+
createExtClient(t, e) {
|
|
615
|
+
return new F(this.authClientId, t, e, t.basePath);
|
|
607
616
|
}
|
|
608
|
-
createDirectClient(
|
|
609
|
-
return new
|
|
617
|
+
createDirectClient(t, e, r) {
|
|
618
|
+
return new U(
|
|
610
619
|
{
|
|
611
|
-
authClientId:
|
|
612
|
-
authServerUrl:
|
|
613
|
-
redirectUri:
|
|
614
|
-
userScope:
|
|
615
|
-
logLevel:
|
|
616
|
-
storagePrefix:
|
|
617
|
-
basePath:
|
|
620
|
+
authClientId: t,
|
|
621
|
+
authServerUrl: e.authServerUrl,
|
|
622
|
+
redirectUri: e.redirectUri,
|
|
623
|
+
userScope: e.userScope,
|
|
624
|
+
logLevel: e.logLevel,
|
|
625
|
+
storagePrefix: S.WEB,
|
|
626
|
+
basePath: e.basePath
|
|
618
627
|
},
|
|
619
|
-
|
|
628
|
+
r
|
|
620
629
|
);
|
|
621
630
|
}
|
|
622
631
|
// ============================================================================
|
|
623
632
|
// Web-specific OAuth Callback
|
|
624
633
|
// ============================================================================
|
|
625
|
-
async handleOAuthCallback(
|
|
626
|
-
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);
|
|
627
636
|
}
|
|
628
637
|
}
|
|
629
|
-
const
|
|
638
|
+
const V = "production";
|
|
630
639
|
export {
|
|
631
|
-
|
|
632
|
-
|
|
640
|
+
V as WEB_BUILD_MODE,
|
|
641
|
+
q as WebUIClient
|
|
633
642
|
};
|