@palantir/pack.auth.foundry 0.0.1-beta.1

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 (48) hide show
  1. package/.turbo/turbo-lint.log +20 -0
  2. package/.turbo/turbo-transpileBrowser.log +5 -0
  3. package/.turbo/turbo-transpileCjs.log +5 -0
  4. package/.turbo/turbo-transpileEsm.log +5 -0
  5. package/.turbo/turbo-transpileTypes.log +5 -0
  6. package/.turbo/turbo-typecheck.log +4 -0
  7. package/LICENSE.txt +13 -0
  8. package/README.md +159 -0
  9. package/build/browser/index.js +374 -0
  10. package/build/browser/index.js.map +1 -0
  11. package/build/cjs/index.cjs +381 -0
  12. package/build/cjs/index.cjs.map +1 -0
  13. package/build/cjs/index.d.cts +90 -0
  14. package/build/esm/index.js +374 -0
  15. package/build/esm/index.js.map +1 -0
  16. package/build/types/AuthModuleConfig.d.ts +1 -0
  17. package/build/types/AuthModuleConfig.d.ts.map +1 -0
  18. package/build/types/__tests__/AuthServiceConfidential.test.d.ts +1 -0
  19. package/build/types/__tests__/AuthServiceConfidential.test.d.ts.map +1 -0
  20. package/build/types/__tests__/AuthServicePublic.test.d.ts +1 -0
  21. package/build/types/__tests__/AuthServicePublic.test.d.ts.map +1 -0
  22. package/build/types/__tests__/AuthServiceStatic.test.d.ts +1 -0
  23. package/build/types/__tests__/AuthServiceStatic.test.d.ts.map +1 -0
  24. package/build/types/auth/AuthServiceBase.d.ts +32 -0
  25. package/build/types/auth/AuthServiceBase.d.ts.map +1 -0
  26. package/build/types/auth/AuthServiceConfidential.d.ts +20 -0
  27. package/build/types/auth/AuthServiceConfidential.d.ts.map +1 -0
  28. package/build/types/auth/AuthServicePublic.d.ts +23 -0
  29. package/build/types/auth/AuthServicePublic.d.ts.map +1 -0
  30. package/build/types/auth/AuthServiceStatic.d.ts +17 -0
  31. package/build/types/auth/AuthServiceStatic.d.ts.map +1 -0
  32. package/build/types/index.d.ts +4 -0
  33. package/build/types/index.d.ts.map +1 -0
  34. package/build/types/utils/jwtUtils.d.ts +28 -0
  35. package/build/types/utils/jwtUtils.d.ts.map +1 -0
  36. package/package.json +74 -0
  37. package/src/AuthModuleConfig.ts +17 -0
  38. package/src/__tests__/AuthServiceConfidential.test.ts +407 -0
  39. package/src/__tests__/AuthServicePublic.test.ts +413 -0
  40. package/src/__tests__/AuthServiceStatic.test.ts +341 -0
  41. package/src/auth/AuthServiceBase.ts +201 -0
  42. package/src/auth/AuthServiceConfidential.ts +96 -0
  43. package/src/auth/AuthServicePublic.ts +124 -0
  44. package/src/auth/AuthServiceStatic.ts +91 -0
  45. package/src/index.ts +20 -0
  46. package/src/utils/jwtUtils.ts +127 -0
  47. package/tsconfig.json +21 -0
  48. package/vitest.config.mjs +26 -0
@@ -0,0 +1,341 @@
1
+ /*
2
+ * Copyright 2025 Palantir Technologies, Inc. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import type { Logger } from "@osdk/api";
18
+ import type { Client } from "@osdk/client";
19
+ import { Users } from "@osdk/foundry.admin";
20
+ import { AuthState, type BaseAuthService } from "@palantir/pack.auth";
21
+ import type { PackAppInternal, TokenProvider } from "@palantir/pack.core";
22
+ import { beforeEach, describe, expect, it, vi } from "vitest";
23
+ import type { MockProxy } from "vitest-mock-extended";
24
+ import { mock } from "vitest-mock-extended";
25
+ import { createStaticTokenService } from "../auth/AuthServiceStatic.js";
26
+
27
+ // Mock the Users module
28
+ vi.mock("@osdk/foundry.admin", () => ({
29
+ Users: {
30
+ getCurrent: vi.fn(),
31
+ },
32
+ }));
33
+
34
+ describe("StaticTokenService", () => {
35
+ let mockTokenProvider: TokenProvider;
36
+ let mockApp: PackAppInternal;
37
+ let mockLogger: MockProxy<Logger>;
38
+ let service: BaseAuthService;
39
+
40
+ beforeEach(() => {
41
+ mockTokenProvider = vi.fn().mockResolvedValue("static-test-token");
42
+ mockLogger = mock<Logger>();
43
+ mockLogger.child.mockReturnValue(mockLogger);
44
+
45
+ const mockOsdkClient = mock<Client>();
46
+
47
+ mockApp = {
48
+ config: {
49
+ app: { appId: "test-app" },
50
+ isTestMode: false,
51
+ logger: mockLogger,
52
+ osdkClient: mockOsdkClient,
53
+ remote: {
54
+ packWsPath: "/pack/ws",
55
+ baseUrl: "https://test.example.com",
56
+ fetchFn: globalThis.fetch,
57
+ },
58
+ },
59
+ getModule: vi.fn(),
60
+ };
61
+
62
+ // Mock Users.getCurrent to return a mock user
63
+ vi.mocked(Users.getCurrent).mockResolvedValue({
64
+ id: "test-user-id",
65
+ username: "test-user",
66
+ realm: "test-realm",
67
+ attributes: {},
68
+ });
69
+
70
+ service = createStaticTokenService(mockApp, mockTokenProvider);
71
+ });
72
+
73
+ describe("initialization", () => {
74
+ it("should eagerly fetch token and transition to authenticated", async () => {
75
+ await vi.waitFor(() => {
76
+ expect(service.getCurrentState()).toBe(AuthState.Authenticated);
77
+ });
78
+ expect(service.isAuthenticated()).toBe(true);
79
+ expect(service.getTokenOrUndefined()).toBe("static-test-token");
80
+ expect(mockTokenProvider).toHaveBeenCalled();
81
+ });
82
+
83
+ it("should start in un-validated state", () => {
84
+ expect(service.isValidated()).toBe(false);
85
+ });
86
+
87
+ it("should return undefined for getCurrentUser before validation", () => {
88
+ expect(service.getCurrentUserId()).toBeUndefined();
89
+ });
90
+ });
91
+
92
+ describe("getToken method", () => {
93
+ it("should return token from provider", async () => {
94
+ const token = await service.getToken();
95
+ expect(token).toBe("static-test-token");
96
+ expect(mockTokenProvider).toHaveBeenCalled();
97
+ });
98
+
99
+ describe("with custom token providers", () => {
100
+ it("should handle async token provider", async () => {
101
+ const customProvider = vi.fn().mockResolvedValue("async-token");
102
+ const customService = createStaticTokenService(mockApp, customProvider);
103
+
104
+ const token = await customService.getToken();
105
+ expect(token).toBe("async-token");
106
+ });
107
+
108
+ it("should handle token provider that throws", async () => {
109
+ const error = new Error("Token provider error");
110
+ const failingProvider = vi.fn().mockRejectedValue(error);
111
+ const customService = createStaticTokenService(mockApp, failingProvider);
112
+
113
+ await vi.waitFor(() => {
114
+ expect(customService.getCurrentState()).toBe(AuthState.Error);
115
+ });
116
+
117
+ await expect(customService.getToken()).rejects.toThrow("Token provider error");
118
+ });
119
+ });
120
+ });
121
+
122
+ describe("authentication methods", () => {
123
+ it("should return true for isAuthenticated after eager fetch", async () => {
124
+ await vi.waitFor(() => {
125
+ expect(service.isAuthenticated()).toBe(true);
126
+ });
127
+ });
128
+
129
+ it("should return token for getTokenOrUndefined after eager fetch", async () => {
130
+ await vi.waitFor(() => {
131
+ expect(service.getTokenOrUndefined()).toBe("static-test-token");
132
+ });
133
+ });
134
+
135
+ it("should be no-op for signIn", async () => {
136
+ await expect(service.signIn()).resolves.toBeUndefined();
137
+ });
138
+
139
+ it("should reject signOut for static token providers", async () => {
140
+ await expect(service.signOut()).rejects.toThrow(
141
+ "Sign out not supported for static token providers",
142
+ );
143
+ });
144
+
145
+ it("should not have refresh capability", () => {
146
+ expect(service.refresh).toBeUndefined();
147
+ });
148
+ });
149
+
150
+ describe("state management", () => {
151
+ it("should fire auth state change event with current state immediately", () => {
152
+ const callback = vi.fn();
153
+ const unsubscribe = service.onAuthStateChange(callback);
154
+
155
+ // First call should be immediate callback with current state (no previousState)
156
+ expect(callback).toHaveBeenCalledWith({
157
+ state: AuthState.Authenticated,
158
+ });
159
+
160
+ expect(typeof unsubscribe).toBe("function");
161
+ });
162
+
163
+ it("should allow unsubscribing from state changes", () => {
164
+ const callback = vi.fn();
165
+ const unsubscribe = service.onAuthStateChange(callback);
166
+
167
+ unsubscribe();
168
+ expect(typeof unsubscribe).toBe("function");
169
+ });
170
+
171
+ it("should maintain authenticated state after signIn", async () => {
172
+ await vi.waitFor(() => {
173
+ expect(service.getCurrentState()).toBe(AuthState.Authenticated);
174
+ });
175
+
176
+ await expect(service.signIn()).resolves.toBeUndefined();
177
+ expect(service.getCurrentState()).toBe(AuthState.Authenticated);
178
+ });
179
+ });
180
+
181
+ describe("token validation", () => {
182
+ beforeEach(() => {
183
+ global.fetch = vi.fn();
184
+ });
185
+
186
+ it("should validate token successfully with baseUrl", async () => {
187
+ await service.getToken();
188
+
189
+ const isValid = await service.validateToken();
190
+
191
+ expect(isValid).toBe(true);
192
+ expect(service.isValidated()).toBe(true);
193
+ expect(service.getCurrentUserId()).toBeDefined();
194
+ expect(Users.getCurrent).toHaveBeenCalledWith(mockApp.config.osdkClient);
195
+ });
196
+
197
+ it("should handle validation failure", async () => {
198
+ vi.mocked(Users.getCurrent).mockRejectedValueOnce(new Error("Unauthorized"));
199
+
200
+ await service.getToken();
201
+
202
+ const isValid = await service.validateToken();
203
+
204
+ expect(isValid).toBe(false);
205
+ expect(service.isValidated()).toBe(false);
206
+ expect(service.getCurrentUserId()).toBeUndefined();
207
+ });
208
+
209
+ it("should handle network errors during validation", async () => {
210
+ vi.mocked(Users.getCurrent).mockRejectedValueOnce(new Error("Network error"));
211
+
212
+ await service.getToken();
213
+
214
+ const isValid = await service.validateToken();
215
+
216
+ expect(isValid).toBe(false);
217
+ expect(service.isValidated()).toBe(false);
218
+ expect(service.getCurrentUserId()).toBeUndefined();
219
+ });
220
+
221
+ describe("in test mode", () => {
222
+ it("should always return true for validation without baseUrl", async () => {
223
+ vi.clearAllMocks();
224
+
225
+ const testModeApp: PackAppInternal = {
226
+ config: {
227
+ app: { appId: "test-app" },
228
+ isTestMode: true,
229
+ logger: mockLogger,
230
+ osdkClient: mock<Client>(),
231
+ remote: {
232
+ packWsPath: "/pack/ws",
233
+ baseUrl: "https://localhost:5173",
234
+ fetchFn: globalThis.fetch,
235
+ },
236
+ },
237
+ getModule: vi.fn(),
238
+ };
239
+
240
+ service = createStaticTokenService(testModeApp, mockTokenProvider);
241
+ await service.getToken();
242
+
243
+ const isValid = await service.validateToken();
244
+
245
+ expect(isValid).toBe(true);
246
+ expect(service.isValidated()).toBe(true);
247
+ expect(service.getCurrentUserId()).toBeUndefined();
248
+ expect(Users.getCurrent).not.toHaveBeenCalled();
249
+ });
250
+ });
251
+
252
+ it("should reset validation state on signOut", async () => {
253
+ await service.getToken();
254
+
255
+ global.fetch = vi.fn().mockResolvedValue({
256
+ ok: true,
257
+ json: () => Promise.resolve({ userId: "test-user" }),
258
+ });
259
+ await service.validateToken();
260
+
261
+ expect(service.isValidated()).toBe(true);
262
+
263
+ await expect(service.signOut()).rejects.toThrow();
264
+
265
+ expect(service.isValidated()).toBe(false);
266
+ expect(service.getCurrentUserId()).toBeUndefined();
267
+ });
268
+ });
269
+
270
+ describe("cleanup", () => {
271
+ it("should clear state callbacks on dispose", () => {
272
+ const callback = vi.fn();
273
+ service.onAuthStateChange(callback);
274
+
275
+ service.dispose();
276
+
277
+ expect(() => {
278
+ service.dispose();
279
+ }).not.toThrow();
280
+ });
281
+
282
+ it("should reset validation state on dispose", async () => {
283
+ global.fetch = vi.fn().mockResolvedValue({
284
+ ok: true,
285
+ json: () => Promise.resolve({ userId: "test-user" }),
286
+ });
287
+
288
+ await service.getToken();
289
+ await service.validateToken();
290
+
291
+ expect(service.isValidated()).toBe(true);
292
+
293
+ service.dispose();
294
+
295
+ expect(service.isValidated()).toBe(false);
296
+ expect(service.getCurrentUserId()).toBeUndefined();
297
+ });
298
+
299
+ it("should not affect token provider after dispose", async () => {
300
+ await service.getToken();
301
+
302
+ service.dispose();
303
+
304
+ const token = await service.getToken();
305
+ expect(token).toBe("static-test-token");
306
+ });
307
+ });
308
+
309
+ describe("multiple instances", () => {
310
+ it("should create independent services", () => {
311
+ const provider1 = vi.fn().mockResolvedValue("token1");
312
+ const provider2 = vi.fn().mockResolvedValue("token2");
313
+
314
+ const service1 = createStaticTokenService(mockApp, provider1);
315
+ const service2 = createStaticTokenService(mockApp, provider2);
316
+
317
+ expect(service1).not.toBe(service2);
318
+ expect(service1.getCurrentState()).toBe(AuthState.Unauthenticated);
319
+ expect(service2.getCurrentState()).toBe(AuthState.Unauthenticated);
320
+ });
321
+
322
+ it("should handle different token provider behaviors", async () => {
323
+ const syncProvider = () => Promise.resolve("sync-token");
324
+ const asyncProvider = async () => {
325
+ await new Promise(resolve => setTimeout(resolve, 10));
326
+ return "async-token";
327
+ };
328
+
329
+ const syncService = createStaticTokenService(mockApp, syncProvider);
330
+ const asyncService = createStaticTokenService(mockApp, asyncProvider);
331
+
332
+ const [syncToken, asyncToken] = await Promise.all([
333
+ syncService.getToken(),
334
+ asyncService.getToken(),
335
+ ]);
336
+
337
+ expect(syncToken).toBe("sync-token");
338
+ expect(asyncToken).toBe("async-token");
339
+ });
340
+ });
341
+ });
@@ -0,0 +1,201 @@
1
+ /*
2
+ * Copyright 2025 Palantir Technologies, Inc. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import type { Logger } from "@osdk/api";
18
+ import { Users } from "@osdk/foundry.admin";
19
+ import {
20
+ AuthState,
21
+ type AuthStateCallback,
22
+ type AuthStateChangeEvent,
23
+ type BaseAuthService,
24
+ type TokenChangeCallback,
25
+ } from "@palantir/pack.auth";
26
+ import type { PackAppInternal, Unsubscribe } from "@palantir/pack.core";
27
+ import { parseJwtToken } from "../utils/jwtUtils.js";
28
+
29
+ export abstract class AuthServiceBase implements BaseAuthService {
30
+ protected currentState: AuthState;
31
+ protected isTokenValidated = false;
32
+ protected currentUserId: string | undefined;
33
+ protected cachedUserData: unknown;
34
+ private readonly stateCallbacks: Set<AuthStateCallback> = new Set<AuthStateCallback>();
35
+ private readonly tokenChangeCallbacks = new Set<TokenChangeCallback>();
36
+ protected currentToken?: string;
37
+ protected readonly app: PackAppInternal;
38
+ protected readonly logger: Logger;
39
+
40
+ constructor(
41
+ app: PackAppInternal,
42
+ logger: Logger,
43
+ initialState: AuthState = AuthState.Unauthenticated,
44
+ ) {
45
+ this.app = app;
46
+ this.logger = logger;
47
+ this.currentState = initialState;
48
+ }
49
+
50
+ abstract getToken(): Promise<string>;
51
+
52
+ protected clearUserState(): void {
53
+ this.isTokenValidated = false;
54
+ this.currentUserId = undefined;
55
+ this.cachedUserData = undefined;
56
+ }
57
+
58
+ isAuthenticated(): boolean {
59
+ return this.getTokenOrUndefined() != null;
60
+ }
61
+
62
+ getCurrentUserId(): string | undefined {
63
+ return this.isTokenValidated ? this.currentUserId : undefined;
64
+ }
65
+
66
+ abstract getTokenOrUndefined(): string | undefined;
67
+
68
+ isValidated(): boolean {
69
+ return this.isTokenValidated;
70
+ }
71
+
72
+ async validateToken(): Promise<boolean> {
73
+ if (!this.isAuthenticated()) {
74
+ this.clearUserState();
75
+ return false;
76
+ }
77
+
78
+ if (this.app.config.isTestMode) {
79
+ this.isTokenValidated = true;
80
+ return true;
81
+ }
82
+
83
+ try {
84
+ const user = await Users.getCurrent(this.app.config.osdkClient);
85
+ this.isTokenValidated = true;
86
+ this.cachedUserData = user;
87
+ this.currentUserId = user.id;
88
+ return true;
89
+ } catch (error) {
90
+ this.logger.error("Token validation failed", error);
91
+ this.clearUserState();
92
+ return false;
93
+ }
94
+ }
95
+
96
+ abstract signIn(): Promise<void>;
97
+
98
+ abstract signOut(): Promise<void>;
99
+
100
+ refresh?(): Promise<void>;
101
+
102
+ onAuthStateChange(callback: AuthStateCallback): Unsubscribe {
103
+ this.stateCallbacks.add(callback);
104
+
105
+ try {
106
+ callback({
107
+ state: this.currentState,
108
+ });
109
+ } catch (err) {
110
+ this.logger.error("Error in auth state callback:", err);
111
+ }
112
+
113
+ return () => {
114
+ this.stateCallbacks.delete(callback);
115
+ };
116
+ }
117
+
118
+ onTokenChange(callback: TokenChangeCallback): Unsubscribe {
119
+ this.tokenChangeCallbacks.add(callback);
120
+
121
+ // Immediately notify with current token if available
122
+ const token = this.getTokenOrUndefined();
123
+ if (token != null) {
124
+ const info = parseJwtToken(token) ?? {};
125
+ try {
126
+ callback(token, info);
127
+ } catch (err) {
128
+ this.logger.error("Error in token change callback:", err);
129
+ }
130
+ }
131
+
132
+ return () => {
133
+ this.tokenChangeCallbacks.delete(callback);
134
+ };
135
+ }
136
+
137
+ getCurrentState(): AuthState {
138
+ return this.currentState;
139
+ }
140
+
141
+ async getUserData(userId: string, force?: boolean): Promise<unknown> {
142
+ if (!this.isTokenValidated || !this.currentUserId || this.currentUserId !== userId) {
143
+ throw new Error(`User data not available for userId: ${userId}`);
144
+ }
145
+
146
+ if (!force && this.cachedUserData) {
147
+ return this.cachedUserData;
148
+ }
149
+
150
+ if (this.app.config.isTestMode) {
151
+ throw new Error("Cannot refresh user data in test mode");
152
+ }
153
+
154
+ try {
155
+ // TODO: debounce and use Users.getBatch for efficiency when looking up multiple users
156
+ const user = await Users.get(this.app.config.osdkClient, userId);
157
+ this.cachedUserData = user;
158
+ return user;
159
+ } catch (error) {
160
+ this.logger.error("Failed to refresh user data", error);
161
+ throw new Error("Failed to refresh user data");
162
+ }
163
+ }
164
+
165
+ dispose(): void {
166
+ this.stateCallbacks.clear();
167
+ this.tokenChangeCallbacks.clear();
168
+ this.clearUserState();
169
+ }
170
+
171
+ protected notifyTokenChange(token: string | undefined): void {
172
+ const info = (token != null ? parseJwtToken(token) : undefined) ?? {};
173
+
174
+ this.tokenChangeCallbacks.forEach(callback => {
175
+ try {
176
+ callback(token, info);
177
+ } catch (err) {
178
+ this.logger.error("Error in token change callback:", err);
179
+ }
180
+ });
181
+ }
182
+
183
+ protected updateState(newState: AuthState, error?: Error): void {
184
+ const previousState = this.currentState;
185
+ this.currentState = newState;
186
+
187
+ const event: AuthStateChangeEvent = {
188
+ previousState,
189
+ state: newState,
190
+ error,
191
+ };
192
+
193
+ this.stateCallbacks.forEach(callback => {
194
+ try {
195
+ callback(event);
196
+ } catch (err) {
197
+ this.logger.error("Error in auth state callback:", err);
198
+ }
199
+ });
200
+ }
201
+ }
@@ -0,0 +1,96 @@
1
+ /*
2
+ * Copyright 2025 Palantir Technologies, Inc. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import type { createConfidentialOauthClient } from "@osdk/oauth";
18
+ import { AuthState } from "@palantir/pack.auth";
19
+ import type { PackAppInternal } from "@palantir/pack.core";
20
+ import { AuthServiceBase } from "./AuthServiceBase.js";
21
+
22
+ type ConfidentialOauthClient = ReturnType<typeof createConfidentialOauthClient>;
23
+
24
+ export class AuthServiceConfidential extends AuthServiceBase {
25
+ private readonly oauthClient: ConfidentialOauthClient;
26
+
27
+ constructor(app: PackAppInternal, oauthClient: ConfidentialOauthClient) {
28
+ const initialState = oauthClient.getTokenOrUndefined()
29
+ ? AuthState.Authenticated
30
+ : AuthState.Unauthenticated;
31
+ super(
32
+ app,
33
+ app.config.logger.child({}, { msgPrefix: "AuthServiceConfidential" }),
34
+ initialState,
35
+ );
36
+ this.oauthClient = oauthClient;
37
+
38
+ this.oauthClient.addEventListener("signIn", this.signInHandler);
39
+ this.oauthClient.addEventListener("signOut", this.signOutHandler);
40
+ }
41
+
42
+ override async getToken(): Promise<string> {
43
+ return this.oauthClient();
44
+ }
45
+
46
+ private readonly signInHandler = () => {
47
+ this.updateState(AuthState.Authenticated);
48
+ const token = this.getTokenOrUndefined();
49
+ this.notifyTokenChange(token);
50
+ };
51
+
52
+ private readonly signOutHandler = () => {
53
+ this.updateState(AuthState.Unauthenticated);
54
+ this.notifyTokenChange(undefined);
55
+ };
56
+
57
+ override getTokenOrUndefined(): string | undefined {
58
+ return this.oauthClient.getTokenOrUndefined();
59
+ }
60
+
61
+ override async signIn(): Promise<void> {
62
+ try {
63
+ this.updateState(AuthState.Authenticating);
64
+ await this.oauthClient.signIn();
65
+ } catch (error) {
66
+ this.updateState(AuthState.Error, error instanceof Error ? error : new Error(String(error)));
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ override async signOut(): Promise<void> {
72
+ try {
73
+ this.clearUserState();
74
+ await this.oauthClient.signOut();
75
+ } catch (error) {
76
+ this.updateState(AuthState.Error, error instanceof Error ? error : new Error(String(error)));
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ override dispose(): void {
82
+ this.oauthClient.removeEventListener("signIn", this.signInHandler);
83
+ this.oauthClient.removeEventListener("signOut", this.signOutHandler);
84
+ super.dispose();
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Creates a confidential OAuth auth service that wraps a ConfidentialOauthClient
90
+ */
91
+ export function createConfidentialOauthService(
92
+ app: PackAppInternal,
93
+ oauthClient: ConfidentialOauthClient,
94
+ ): AuthServiceConfidential {
95
+ return new AuthServiceConfidential(app, oauthClient);
96
+ }