@arkitektbedriftene/fe-lib 0.2.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/.github/workflows/auto-publish.yaml +20 -0
- package/.github/workflows/publish.yaml +21 -0
- package/.github/workflows/tag-versions.yaml +16 -0
- package/README.md +23 -0
- package/dist/hooks.cjs.js +1 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.es.js +5 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +4 -0
- package/dist/lib/hooks/hooks.d.ts +2 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/oidc/impersonate.d.ts +30 -0
- package/dist/lib/oidc/oidc.d.ts +29 -0
- package/dist/oidc.cjs.js +27 -0
- package/dist/oidc.d.ts +1 -0
- package/dist/oidc.es.js +3431 -0
- package/dist/useLocalStorageState-b4fb2a60.js +100 -0
- package/dist/useLocalStorageState-f72b4add.cjs +1 -0
- package/index.html +13 -0
- package/package.json +64 -0
- package/src/App.css +42 -0
- package/src/App.tsx +35 -0
- package/src/assets/react.svg +1 -0
- package/src/index.css +69 -0
- package/src/lib/hooks/hooks.ts +3 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/oidc/README.md +28 -0
- package/src/lib/oidc/impersonate.tsx +102 -0
- package/src/lib/oidc/oidc.tsx +248 -0
- package/src/main.tsx +10 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +21 -0
- package/tsconfig.node.json +9 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { SigninRedirectArgs, User, UserManager } from "oidc-client-ts";
|
|
2
|
+
import {
|
|
3
|
+
Context,
|
|
4
|
+
createContext,
|
|
5
|
+
ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from "react";
|
|
13
|
+
|
|
14
|
+
export * from "oidc-client-ts";
|
|
15
|
+
|
|
16
|
+
export * from "./impersonate";
|
|
17
|
+
|
|
18
|
+
export type AuthState = {
|
|
19
|
+
user: User | null;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
isAuthenticated: boolean;
|
|
22
|
+
isError: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type AuthContextData = {
|
|
27
|
+
state: AuthState;
|
|
28
|
+
handleSigninCallback: () => Promise<User | undefined>;
|
|
29
|
+
redirectToSignin: UserManager["signinRedirect"];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type AuthProviderConfig = {
|
|
33
|
+
onSigninComplete: (user: User | null) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const AuthProviderCore = ({
|
|
37
|
+
userManager,
|
|
38
|
+
context,
|
|
39
|
+
children,
|
|
40
|
+
}: {
|
|
41
|
+
userManager: UserManager;
|
|
42
|
+
context: Context<AuthContextData | null>;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}) => {
|
|
45
|
+
const [state, setState] = useState<AuthState>({
|
|
46
|
+
user: null,
|
|
47
|
+
isLoading: true,
|
|
48
|
+
isAuthenticated: false,
|
|
49
|
+
isError: false,
|
|
50
|
+
error: null,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Initialize on first render
|
|
54
|
+
const isInitialized = useRef(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (isInitialized.current) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
isInitialized.current = true;
|
|
60
|
+
|
|
61
|
+
void (async () => {
|
|
62
|
+
try {
|
|
63
|
+
const user = await userManager.getUser();
|
|
64
|
+
setState({
|
|
65
|
+
user,
|
|
66
|
+
isLoading: false,
|
|
67
|
+
isAuthenticated: user ? !user.expired : false,
|
|
68
|
+
isError: false,
|
|
69
|
+
error: null,
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
setState({
|
|
73
|
+
user: null,
|
|
74
|
+
isLoading: false,
|
|
75
|
+
isAuthenticated: false,
|
|
76
|
+
isError: true,
|
|
77
|
+
error:
|
|
78
|
+
error instanceof Error
|
|
79
|
+
? error
|
|
80
|
+
: new Error("Unknown error during auth"),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
}, [userManager]);
|
|
85
|
+
|
|
86
|
+
// Set up UserManager events
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const onUserLoaded = (user: User) => {
|
|
89
|
+
setState({
|
|
90
|
+
user,
|
|
91
|
+
isLoading: false,
|
|
92
|
+
isAuthenticated: !user.expired,
|
|
93
|
+
isError: false,
|
|
94
|
+
error: null,
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
userManager.events.addUserLoaded(onUserLoaded);
|
|
98
|
+
|
|
99
|
+
const onUserUnloaded = () => {
|
|
100
|
+
setState({
|
|
101
|
+
...state,
|
|
102
|
+
user: null,
|
|
103
|
+
isAuthenticated: false,
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
userManager.events.addUserUnloaded(onUserUnloaded);
|
|
107
|
+
|
|
108
|
+
const onSilentRenewError = (error: Error) => {
|
|
109
|
+
setState({
|
|
110
|
+
...state,
|
|
111
|
+
isLoading: false,
|
|
112
|
+
isError: true,
|
|
113
|
+
error,
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
userManager.events.addSilentRenewError(onSilentRenewError);
|
|
117
|
+
|
|
118
|
+
return () => {
|
|
119
|
+
userManager.events.removeUserLoaded(onUserLoaded);
|
|
120
|
+
userManager.events.removeUserUnloaded(onUserUnloaded);
|
|
121
|
+
userManager.events.removeSilentRenewError(onSilentRenewError);
|
|
122
|
+
};
|
|
123
|
+
}, [userManager]);
|
|
124
|
+
|
|
125
|
+
const handleSigninCallback = useCallback(async () => {
|
|
126
|
+
const user = await userManager.signinCallback();
|
|
127
|
+
|
|
128
|
+
setState({
|
|
129
|
+
user: user ?? null,
|
|
130
|
+
isLoading: false,
|
|
131
|
+
isAuthenticated: user ? !user.expired : false,
|
|
132
|
+
isError: false,
|
|
133
|
+
error: null,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return user ?? undefined;
|
|
137
|
+
}, [userManager]);
|
|
138
|
+
|
|
139
|
+
const redirectToSignin = useCallback(
|
|
140
|
+
async (args?: SigninRedirectArgs | undefined) => {
|
|
141
|
+
try {
|
|
142
|
+
await userManager.signinRedirect(args);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(error);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[userManager]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const contextValue = useMemo(
|
|
151
|
+
() => ({
|
|
152
|
+
state,
|
|
153
|
+
handleSigninCallback,
|
|
154
|
+
redirectToSignin,
|
|
155
|
+
}),
|
|
156
|
+
[state, handleSigninCallback, redirectToSignin]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return <context.Provider value={contextValue}>{children}</context.Provider>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const useAuthContextCore = (context: Context<AuthContextData | null>) => {
|
|
163
|
+
const contextData = useContext(context);
|
|
164
|
+
if (!contextData) {
|
|
165
|
+
throw new Error("useAuthContext must be used within an AuthProvider");
|
|
166
|
+
}
|
|
167
|
+
return contextData;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const useAuthStateCore = (context: Context<AuthContextData | null>) => {
|
|
171
|
+
const { state } = useAuthContextCore(context);
|
|
172
|
+
return state;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const useSigninCallbackCore = (
|
|
176
|
+
context: Context<AuthContextData | null>,
|
|
177
|
+
onDone?: (user?: User) => void
|
|
178
|
+
) => {
|
|
179
|
+
const { state, handleSigninCallback } = useAuthContextCore(context);
|
|
180
|
+
|
|
181
|
+
const isInitialized = useRef(false);
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
// Only run once
|
|
184
|
+
if (isInitialized.current) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
isInitialized.current = true;
|
|
188
|
+
handleSigninCallback()
|
|
189
|
+
// Wait for state to update
|
|
190
|
+
// Otherwise any navigation that happens in onDone will
|
|
191
|
+
// happen before the state update and the user will be
|
|
192
|
+
// redirected to the signin page again.
|
|
193
|
+
.then(
|
|
194
|
+
(u) =>
|
|
195
|
+
new Promise<User | undefined>((resolve) =>
|
|
196
|
+
setTimeout(() => resolve(u), 0)
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
.then((u) => onDone?.(u));
|
|
200
|
+
}, [handleSigninCallback]);
|
|
201
|
+
|
|
202
|
+
return state;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const createAuthContext = (userManager: UserManager) => {
|
|
206
|
+
const AuthContext = createContext<AuthContextData | null>(null);
|
|
207
|
+
|
|
208
|
+
const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
209
|
+
return (
|
|
210
|
+
<AuthProviderCore userManager={userManager} context={AuthContext}>
|
|
211
|
+
{children}
|
|
212
|
+
</AuthProviderCore>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const useAuthContext = () => useAuthContextCore(AuthContext);
|
|
217
|
+
const useAuthState = () => useAuthStateCore(AuthContext);
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Hook to handle the signin callback. Use this on the signin callback page.
|
|
221
|
+
* @param onDone Optional callback to run after signin is complete. Useful to redirect to a different page.
|
|
222
|
+
*/
|
|
223
|
+
const useSigninCallback = (onDone?: (user?: User) => void) =>
|
|
224
|
+
useSigninCallbackCore(AuthContext, onDone);
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Async function to get the access token for the current user
|
|
228
|
+
* outside of a React component.
|
|
229
|
+
*
|
|
230
|
+
* Useful for prefetching data in React Router or similar.
|
|
231
|
+
*/
|
|
232
|
+
const getAccessToken = async () => {
|
|
233
|
+
const user = await userManager.getUser();
|
|
234
|
+
if (!user) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return user.access_token;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
AuthContext,
|
|
242
|
+
AuthProvider,
|
|
243
|
+
useAuthContext,
|
|
244
|
+
useAuthState,
|
|
245
|
+
useSigninCallback,
|
|
246
|
+
getAccessToken,
|
|
247
|
+
};
|
|
248
|
+
};
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
6
|
+
"allowJs": false,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"esModuleInterop": false,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"module": "ESNext",
|
|
13
|
+
"moduleResolution": "Node",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx"
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"],
|
|
20
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
21
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react-swc";
|
|
3
|
+
import dts from "vite-plugin-dts";
|
|
4
|
+
|
|
5
|
+
// https://vitejs.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
build: {
|
|
8
|
+
lib: {
|
|
9
|
+
entry: [
|
|
10
|
+
"src/lib/index.ts",
|
|
11
|
+
"src/lib/oidc/oidc.tsx",
|
|
12
|
+
"src/lib/hooks/hooks.ts",
|
|
13
|
+
],
|
|
14
|
+
name: "fe-lib",
|
|
15
|
+
fileName: (format, entryname) => `${entryname}.${format}.js`,
|
|
16
|
+
},
|
|
17
|
+
rollupOptions: {
|
|
18
|
+
external: ["react", "react-dom"],
|
|
19
|
+
output: {
|
|
20
|
+
globals: {
|
|
21
|
+
react: "React",
|
|
22
|
+
"react-dom": "ReactDOM",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
plugins: [
|
|
28
|
+
react(),
|
|
29
|
+
dts({
|
|
30
|
+
insertTypesEntry: true,
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
});
|