The magic is in creating the AuthClient in authClients where the actual typing for the specific wrapping of the vendor client is lost.
In AuthProvider, methods on AuthClient are being called, and the vendor client itself, with the specific AuthClient is exposed, that, by itself seems very much OK to me.
However, by having to pass down the AuthClient (for custom) OR vendor client + type into AuthProvider and even createAuthClient, type safety is lost, and it’s rather non-trivial to get it back. The login | signUp | getUserMetadata | etc. inputs and returns can’t be easily defined.
One way to remedy this issue with only type change would be to trust the user with providing generic into useAuth, as it doesn’t really matter if the AuthProvider is more loosely typed.
The proposal is extracting and exporting the createAuthClient logic, so AuthProvider can use and surface the real AuthClient, even if it’s custom, making AuthClient properly typed, and keep the vendor client wrappers, and their relevant types (that Redwood, or any 3rd party provides) separate from the main package. This would enable AuthProvider to type rwClient as <AppAuthClient extends AuthClient> and bind it to the client property, so the types are more visible, and work inside the AuthProvider can be safer with unknowns instead of anys
It would also help establish the other creators’ role as simple wrappers that are provided by RW, and "feel free to change or shape however you see fit as long as the return type extends AuthClient"
import { AuthProvider, createAuth0Client, RwAuth0Client } from '@redwoodjs/auth'
import { Auth0Client } from '@auth0/auth0-spa-js'
const auth0 = new Auth0Client({
domain: process.env.AUTH0_DOMAIN,
client_id: process.env.AUTH0_CLIENT_ID,
redirect_uri: 'http://localhost:8910/',
cacheLocation: 'localstorage',
audience: process.env.AUTH0_AUDIENCE,
})
interface MyAuth0Client extends {
foo(): Promise<void>
}
function createMyAuth0Client(client: Auth0Client): MyAuth0Client {
const baseClient = createAuth0Client(client)
return {
...baseClient,
wipeData() {
return Promise.all([client.foo(), client.bar()])
}
}
const authClient = createMyAuth0Client(auth0)
ReactDOM.render(
<AuthProvider client={createAuth0Client(auth0)}>
)
const auth = useAuth<RwAuth0Client>()
Going further on the change, a typing extension could also be made for AuthClient, so that useAuth would also be type-safe even without passing any generics.
declare module '@redwoodjs/auth' {
export interface AuthClient extends RwAuth0Client {}
}
Sorry, this might be a bit all over the place, let me know if any more clarification would be beneficial.
ps.
Another option that may be worth considering, but may limit the flexibility of auth module too much, is to use a creator, passing the AuthClient, yielding const { AuthProvider, useAuth } = createRwAuth(createAuth0Client(auth0))