-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add web credential, and expo web compat
- Loading branch information
Showing
8 changed files
with
406 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,96 @@ | ||
import { requireNativeModule } from 'expo-modules-core'; | ||
import { | ||
type EventTypeMap, | ||
type PublicKeyCredential, | ||
type AuthenticatorAttestationResponse, | ||
type AuthenticatorAssertionResponse, | ||
type AttestationOptions, | ||
type AssertionOptions, | ||
} from "@vaariance/shared-types"; | ||
import { | ||
requireNativeModule, | ||
NativeModulesProxy, | ||
EventEmitter, | ||
} from "expo-modules-core"; | ||
|
||
// It loads the native module object from the JSI or falls back to | ||
// the bridge module (from NativeModulesProxy) if the remote debugger is on. | ||
export default requireNativeModule('Cosmr1CredentialHandler'); | ||
const nativeModule = requireNativeModule("Cosmr1CredentialHandler"); | ||
|
||
const emitter = new EventEmitter( | ||
nativeModule ?? NativeModulesProxy.Cosmr1CredentialHandler, | ||
); | ||
|
||
const moduleObjects = { | ||
defaultConfiguraton: { | ||
TIMEOUT: nativeModule.TIMEOUT, | ||
ATTESTATION: nativeModule.ATTESTATION as AttestationConveyancePreference, | ||
AUTHENTICATOR_ATTACHMENT: | ||
nativeModule.AUTHENTICATOR_ATTACHMENT as AuthenticatorAttachment, | ||
REQUIRE_RESIDENT_KEY: nativeModule.REQUIRE_RESIDENT_KEY as boolean, | ||
RESIDENT_KEY: nativeModule.RESIDENT_KEY as ResidentKeyRequirement, | ||
USER_VERIFICATION: | ||
nativeModule.USER_VERIFICATION as UserVerificationRequirement, | ||
PUB_KEY_CRED_PARAM: | ||
nativeModule.PUB_KEY_CRED_PARAM as PublicKeyCredentialParameters, | ||
}, | ||
|
||
events: { | ||
onRegistrationStarted: ( | ||
callback: (event: EventTypeMap["onRegistrationStarted"]) => void, | ||
) => emitter.addListener("onRegistrationStarted", callback), | ||
onRegistrationFailed: ( | ||
callback: (event: EventTypeMap["onRegistrationFailed"]) => void, | ||
) => emitter.addListener("onRegistrationFailed", callback), | ||
onRegistrationComplete: ( | ||
callback: (event: EventTypeMap["onRegistrationComplete"]) => void, | ||
) => emitter.addListener("onRegistrationComplete", callback), | ||
onAuthenticationStarted: ( | ||
callback: (event: EventTypeMap["onAuthenticationStarted"]) => void, | ||
) => emitter.addListener("onAuthenticationStarted", callback), | ||
onAuthenticationFailed: ( | ||
callback: (event: EventTypeMap["onAuthenticationFailed"]) => void, | ||
) => emitter.addListener("onAuthenticationFailed", callback), | ||
onAuthenticationSuccess: ( | ||
callback: (event: EventTypeMap["onAuthenticationSuccess"]) => void, | ||
) => emitter.addListener("onAuthenticationSuccess", callback), | ||
}, | ||
}; | ||
|
||
const mainFunctions = { | ||
async register( | ||
args: AttestationOptions<string>, | ||
): Promise<PublicKeyCredential< | ||
string, | ||
AuthenticatorAttestationResponse<string> | ||
> | null> { | ||
const credential = await nativeModule.register( | ||
args.preferImmediatelyAvailableCred ?? false, | ||
args.challenge, | ||
args.rp, | ||
args.user, | ||
args.timeout, | ||
args.attestation, | ||
args.excludeCredentials, | ||
args.authenticatorSelection, | ||
); | ||
return JSON.parse(credential); | ||
}, | ||
|
||
async authenticate( | ||
args: AssertionOptions<string>, | ||
): Promise<PublicKeyCredential< | ||
string, | ||
AuthenticatorAssertionResponse<string> | ||
> | null> { | ||
const credential = await nativeModule.authenticate( | ||
args.challenge, | ||
args.timeout, | ||
args.rpId, | ||
args.userVerification, | ||
args.allowCredentials, | ||
); | ||
return JSON.parse(credential); | ||
}, | ||
}; | ||
|
||
export default Object.assign(moduleObjects, mainFunctions); |
13 changes: 4 additions & 9 deletions
13
packages/cred-native/src/Cosmr1CredentialHandlerModule.web.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,8 @@ | ||
import Cosmr1CredentialHandlerModule from "@vaariance/cred-web"; | ||
import { EventEmitter } from "expo-modules-core"; | ||
|
||
const emitter = new EventEmitter({} as any); | ||
|
||
export default { | ||
PI: Math.PI, | ||
async setValueAsync(value: string): Promise<void> { | ||
emitter.emit("onChange", { value }); | ||
}, | ||
hello() { | ||
return "Hello world! 👋"; | ||
}, | ||
}; | ||
const webModule = Cosmr1CredentialHandlerModule(emitter); | ||
|
||
export default webModule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,5 @@ | ||
import { | ||
NativeModulesProxy, | ||
EventEmitter, | ||
Subscription, | ||
} from "expo-modules-core"; | ||
|
||
// Import the native module. On web, it will be resolved to Cosmr1CredentialHandler.web.ts | ||
// and on native platforms to Cosmr1CredentialHandler.ts | ||
//import { ChangeEventPayload } from "shared-types"; | ||
|
||
import Cosmr1CredentialHandlerModule from "./Cosmr1CredentialHandlerModule"; | ||
|
||
// Get the native constant value. | ||
export const PI = Cosmr1CredentialHandlerModule.PI; | ||
|
||
export function hello(): string { | ||
return Cosmr1CredentialHandlerModule.hello(); | ||
} | ||
|
||
export async function setValueAsync(value: string) { | ||
return await Cosmr1CredentialHandlerModule.setValueAsync(value); | ||
} | ||
|
||
const emitter = new EventEmitter( | ||
Cosmr1CredentialHandlerModule ?? NativeModulesProxy.Cosmr1CredentialHandler, | ||
); | ||
|
||
// export function addChangeListener( | ||
// listener: (event: ChangeEventPayload) => void, | ||
// ): Subscription { | ||
// return emitter.addListener<ChangeEventPayload>("onChange", listener); | ||
// } | ||
export default Cosmr1CredentialHandlerModule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,192 @@ | ||
console.log("Hello via Bun!"); | ||
import { | ||
type AssertionOptions, | ||
type AttestationOptions, | ||
type CreateCredentialOptions, | ||
type GetCredentialOptions, | ||
type PublicKeyCredential, | ||
type AuthenticatorAttestationResponse, | ||
type AuthenticatorAssertionResponse, | ||
type Cosmr1CredentialHandlerModuleEvents, | ||
type Subscription, | ||
type EventEmitter, | ||
type EventTypeMap, | ||
Constants, | ||
} from "@vaariance/shared-types"; | ||
|
||
const EventEmitter = (): EventEmitter => { | ||
const listeners = < | ||
{ | ||
[K in keyof EventTypeMap]: Set<(event: EventTypeMap[K]) => void>; | ||
} | ||
>{}; | ||
|
||
return { | ||
addListener<T extends Cosmr1CredentialHandlerModuleEvents>( | ||
eventName: T, | ||
listener: (event: EventTypeMap[T]) => void, | ||
): Subscription { | ||
if (!listeners[eventName]) { | ||
listeners[eventName] = new Set<any>(); | ||
} | ||
listeners[eventName].add(listener); | ||
|
||
return { | ||
remove: () => { | ||
listeners[eventName].delete(listener); | ||
}, | ||
}; | ||
}, | ||
|
||
removeAllListeners(eventName: Cosmr1CredentialHandlerModuleEvents): void { | ||
delete listeners[eventName]; | ||
}, | ||
|
||
removeSubscription(subscription: Subscription): void { | ||
subscription.remove(); | ||
}, | ||
|
||
emit<T extends Cosmr1CredentialHandlerModuleEvents>( | ||
eventName: T, | ||
params: EventTypeMap[T], | ||
): void { | ||
const callbacks = listeners[eventName]; | ||
if (callbacks) { | ||
callbacks.forEach((callback) => callback(params)); | ||
} | ||
}, | ||
}; | ||
}; | ||
|
||
const Cosmr1CredentialHandlerModule = ( | ||
eventEmitter: EventEmitter = EventEmitter(), | ||
) => { | ||
const moduleObject = { | ||
// Constants | ||
defaultConfiguraton: { ...Constants }, | ||
|
||
// Events | ||
events: { | ||
onRegistrationStarted: ( | ||
callback: (event: EventTypeMap["onRegistrationStarted"]) => void, | ||
) => eventEmitter.addListener("onRegistrationStarted", callback), | ||
onRegistrationFailed: ( | ||
callback: (event: EventTypeMap["onRegistrationFailed"]) => void, | ||
) => eventEmitter.addListener("onRegistrationFailed", callback), | ||
onRegistrationComplete: ( | ||
callback: (event: EventTypeMap["onRegistrationComplete"]) => void, | ||
) => eventEmitter.addListener("onRegistrationComplete", callback), | ||
onAuthenticationStarted: ( | ||
callback: (event: EventTypeMap["onAuthenticationStarted"]) => void, | ||
) => eventEmitter.addListener("onAuthenticationStarted", callback), | ||
onAuthenticationFailed: ( | ||
callback: (event: EventTypeMap["onAuthenticationFailed"]) => void, | ||
) => eventEmitter.addListener("onAuthenticationFailed", callback), | ||
onAuthenticationSuccess: ( | ||
callback: (event: EventTypeMap["onAuthenticationSuccess"]) => void, | ||
) => eventEmitter.addListener("onAuthenticationSuccess", callback), | ||
}, | ||
}; | ||
|
||
// Helper functions | ||
const helpers = { | ||
parseAssertionOptions: ( | ||
args: AssertionOptions<BufferSource>, | ||
): GetCredentialOptions => { | ||
return { | ||
...args, | ||
allowCredentials: args.allowCredentials?.items ?? [], | ||
timeout: args.timeout ?? Constants.TIMEOUT, | ||
userVerification: args.userVerification ?? Constants.USER_VERIFICATION, | ||
}; | ||
}, | ||
|
||
parseAttestationOptions: ( | ||
args: AttestationOptions<BufferSource>, | ||
): CreateCredentialOptions => { | ||
return { | ||
...args, | ||
pubKeyCredParams: [Constants.PUB_KEY_CRED_PARAM], | ||
timeout: args.timeout ?? Constants.TIMEOUT, | ||
attestation: args.attestation ?? Constants.ATTESTATION, | ||
excludeCredentials: args.excludeCredentials?.items ?? [], | ||
authenticatorSelection: args.authenticatorSelection ?? { | ||
authenticatorAttachment: Constants.AUTHENTICATOR_ATTACHMENT, | ||
requireResidentKey: Constants.REQUIRE_RESIDENT_KEY, | ||
residentKey: Constants.RESIDENT_KEY, | ||
userVerification: Constants.USER_VERIFICATION, | ||
}, | ||
}; | ||
}, | ||
_emitEvent: eventEmitter.emit.bind(eventEmitter), | ||
}; | ||
|
||
const mainFunctions = { | ||
async register( | ||
this: typeof helpers, | ||
args: AttestationOptions<BufferSource>, | ||
) { | ||
if (!navigator.credentials) { | ||
throw new Error( | ||
"Web Authentication API is not supported in this environment.", | ||
); | ||
} | ||
try { | ||
const createOptions = this.parseAttestationOptions(args); | ||
this._emitEvent("onRegistrationStarted", createOptions); | ||
const credential = await internalFunctions.register(createOptions); | ||
this._emitEvent("onRegistrationComplete", credential); | ||
return credential; | ||
} catch (error: unknown) { | ||
this._emitEvent("onRegistrationFailed", error); | ||
throw error; | ||
} | ||
}, | ||
|
||
async authenticate( | ||
this: typeof helpers, | ||
args: AssertionOptions<BufferSource>, | ||
) { | ||
if (!navigator.credentials) { | ||
throw new Error( | ||
"Web Authentication API is not supported in this environment.", | ||
); | ||
} | ||
try { | ||
const getOptions = this.parseAssertionOptions(args); | ||
this._emitEvent("onAuthenticationStarted", getOptions); | ||
const credential = await internalFunctions.authenticate(getOptions); | ||
this._emitEvent("onAuthenticationSuccess", credential); | ||
return credential; | ||
} catch (error: unknown) { | ||
this._emitEvent("onAuthenticationFailed", error); | ||
throw error; | ||
} | ||
}, | ||
}; | ||
|
||
const internalFunctions = { | ||
register: async ( | ||
request: CreateCredentialOptions, | ||
): Promise<PublicKeyCredential< | ||
BufferSource, | ||
AuthenticatorAttestationResponse<BufferSource> | ||
> | null> => | ||
navigator.credentials.create({ | ||
publicKey: request, | ||
}), | ||
|
||
authenticate: async ( | ||
request: GetCredentialOptions, | ||
): Promise<PublicKeyCredential< | ||
BufferSource, | ||
AuthenticatorAssertionResponse<BufferSource> | ||
> | null> => | ||
navigator.credentials.get({ | ||
publicKey: request, | ||
}), | ||
}; | ||
|
||
return Object.assign(moduleObject, mainFunctions); | ||
}; | ||
|
||
export default Cosmr1CredentialHandlerModule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.