@plainkey/browser 0.19.0 → 0.20.0

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from "@simplewebauthn/browser";
1
+ import { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialCreationOptionsJSON as PublicKeyCredentialCreationOptionsJSON$1, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from "@simplewebauthn/browser";
2
2
 
3
3
  //#region src/types.d.ts
4
4
  type UserIdentifier = {
@@ -85,6 +85,16 @@ declare class PlainKey {
85
85
  * @param userName - A unique identifier for the user. If not provided, the user's stored userName will be used.
86
86
  */
87
87
  addPasskey(authenticationToken: string, userName?: string): Promise<AddPasskeyResult>;
88
+ /**
89
+ * Completes a server-initiated passkey registration. Use this when your backend has already called
90
+ * beginCredentialRegistration() via the Server SDK (or the associated endpoint via REST API), and passed the options and
91
+ * authenticationToken to the frontend.
92
+ *
93
+ * @param authenticationToken - The short-lived token returned alongside the options by beginCredentialRegistration().
94
+ * @param options - The WebAuthn creation options returned by the server's beginCredentialRegistration().
95
+ * Do NOT store it in local storage, database, etc. Always keep it in memory.
96
+ */
97
+ completePasskeyRegistration(authenticationToken: string, options: PublicKeyCredentialCreationOptionsJSON$1): Promise<AddPasskeyResult>;
88
98
  /**
89
99
  * Updates a passkey label. Any passkey registered to the user can be updated.
90
100
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/plainKey.ts"],"sourcesContent":[],"mappings":";;;KAAY,cAAA;;;AAAZ,CAAA;AAKY,KAAA,mBAAA,GAAmB;EAKnB,KAAA,EAAA,MAAA;EAOK,SAAA,EAAA,MAAA;AAQjB,CAAA;AAUiB,KAzBL,mBAAA,GA4BI;EAMC,EAAA,EAAA,MAAA;;;;ACXjB,CAAA;AAiD0D,UDjEzC,kBAAA,CCiEyC;EAAR,OAAA,EAAA,OAAA;EAyD0B,IAAA,CAAA,EAAA;IAAR,mBAAA,EDvH3C,mBCuH2C;EA+DvD,CAAA;EAAR,KAAA,CAAA,EAAA;IAiCiC,OAAA,EAAA,MAAA;EAAyB,CAAA;;AAAD,UDlN7C,2BAAA,CCkN6C;;;;gBD9M9C;yBACS;;;;;;UAKR,gBAAA;;;gBAGD;yBACS;;;;;;UAKR,wBAAA;;;;;;;;;;AA5CjB;AAKA;AAKA;AAOA;AAQA;AAUA;AASiB,cCXJ,QAAA,CDWI;;;;ECXJ;;;;EA0GuD,QAAA,aAAA;EA+DvD;;;;;;4CAxHqC,QAAQ;;;;;;;;8DAyDU,QAAQ;;;;;;;;;wFA+DvE,QAAQ;;;;;;;;gCAiCyB,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/plainKey.ts"],"sourcesContent":[],"mappings":";;;KAAY,cAAA;;;AAAZ,CAAA;AAKY,KAAA,mBAAA,GAAmB;EAKnB,KAAA,EAAA,MAAA;EAOK,SAAA,EAAA,MAAA;AAQjB,CAAA;AAUiB,KAzBL,mBAAA,GA4BI;EAMC,EAAA,EAAA,MAAA;;;;ACXjB,CAAA;AAiD0D,UDjEzC,kBAAA,CCiEyC;EAAR,OAAA,EAAA,OAAA;EAwD0B,IAAA,CAAA,EAAA;IAAR,mBAAA,EDtH3C,mBCsH2C;EA8DvD,CAAA;EACA,KAAA,CAAA,EAAA;IAAR,OAAA,EAAA,MAAA;EAkDQ,CAAA;;AAiCyB,UDnQrB,2BAAA,CCmQqB;EAAyB,OAAA,EAAA,OAAA;EAAR,IAAA,CAAA,EAAA;IAAO,MAAA,EAAA,MAAA;gBD/P9C;yBACS;;;;;;UAKR,gBAAA;;;gBAGD;yBACS;;;;;;UAKR,wBAAA;;;;;;;;;AA5CjB;AAKA;AAKA;AAOA;AAQA;AAUA;AASA;cCXa,QAAA;;;EAAA,WAAQ,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA;EAiDqC;;;;EAsH7C,QAAA,aAAA;EACA;;;;;;EAmF0C,qBAAA,CAAA,QAAA,CAAA,EAAA,MAAA,CAAA,EA1ML,OA0MK,CA1MG,2BA0MH,CAAA;EAAO;;;;;;;8DAlJM,QAAQ;;;;;;;;;;oEA8D/D,2CACR,QAAQ;;;;;;;;;wFAkDR,QAAQ;;;;;;;;gCAiCyB,iBAAiB,QAAQ"}
package/dist/index.js CHANGED
@@ -137,6 +137,47 @@ var PlainKey = class {
137
137
  }
138
138
  }
139
139
  /**
140
+ * Completes a server-initiated passkey registration. Use this when your backend has already called
141
+ * beginCredentialRegistration() via the Server SDK (or the associated endpoint via REST API), and passed the options and
142
+ * authenticationToken to the frontend.
143
+ *
144
+ * @param authenticationToken - The short-lived token returned alongside the options by beginCredentialRegistration().
145
+ * @param options - The WebAuthn creation options returned by the server's beginCredentialRegistration().
146
+ * Do NOT store it in local storage, database, etc. Always keep it in memory.
147
+ */
148
+ async completePasskeyRegistration(authenticationToken, options) {
149
+ if (!authenticationToken) throw new Error("Authentication token is required");
150
+ if (!options) throw new Error("Options are required");
151
+ try {
152
+ const credential = await startRegistration({ optionsJSON: options });
153
+ const completeResponse = await fetch(`${this.baseUrl}/user/credential/complete`, {
154
+ method: "POST",
155
+ headers: {
156
+ "Content-Type": "application/json",
157
+ "x-project-id": this.projectId
158
+ },
159
+ body: JSON.stringify({
160
+ authenticationToken,
161
+ credential
162
+ })
163
+ });
164
+ const completeData = await this.parseResponse(completeResponse);
165
+ if (!completeData.success) throw new Error("Server could not complete passkey registration");
166
+ return {
167
+ success: true,
168
+ data: {
169
+ authenticationToken: completeData.authenticationToken,
170
+ credential: completeData.credential
171
+ }
172
+ };
173
+ } catch (error) {
174
+ return {
175
+ success: false,
176
+ error: { message: error instanceof Error ? error.message : "Unknown error" }
177
+ };
178
+ }
179
+ }
180
+ /**
140
181
  * Updates a passkey label. Any passkey registered to the user can be updated.
141
182
  *
142
183
  * @param authenticationToken - The user authentication token, returned from .authenticate() or createUserWithPasskey().
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["bodyText: string","json: any","body: UpdateCredentialLabelBody"],"sources":["../src/plainKey.ts"],"sourcesContent":["import {\n startAuthentication,\n startRegistration,\n type PublicKeyCredentialCreationOptionsJSON,\n type PublicKeyCredentialRequestOptionsJSON\n} from \"@simplewebauthn/browser\"\n\nimport type {\n AuthenticateResult,\n AddPasskeyResult,\n CreateUserWithPasskeyResult,\n UpdatePasskeyLabelResult,\n UserIdentifier\n} from \"./types\"\n\nimport type {\n BeginUserRegistration200,\n CompleteUserRegistration200,\n BeginCredentialRegistration200,\n CompleteCredentialRegistration200,\n BeginAuthentication200,\n CompleteAuthentication200,\n UpdateCredentialLabelBody\n} from \"./generated/api\"\n\n/**\n * PlainKey client for the browser. Used to register new users, add passkeys to existing users, and log users in.\n *\n * Docs: https://plainkey.io/docs\n *\n * @param projectId - Your PlainKey project ID. You can find it in the PlainKey admin dashboard.\n * @param baseUrl - Set by default to https://api.plainkey.io/browser. Change only for development purposes.\n */\nexport class PlainKey {\n private readonly projectId: string\n private readonly baseUrl: string\n\n constructor(projectId: string, baseUrl: string = \"https://api.plainkey.io/browser\") {\n if (!projectId) throw new Error(\"Project ID is required\")\n if (!baseUrl) throw new Error(\"Base URL is required\")\n\n this.projectId = projectId\n this.baseUrl = baseUrl.replace(/\\/$/, \"\") // Remove trailing slash\n }\n\n /**\n * Helper to parse response JSON.\n * Throws error if status code is not 200 OK or if the response is not valid JSON.\n */\n private async parseResponse<T = any>(response: Response): Promise<T> {\n let bodyText: string\n\n // Read as text first to avoid JSON.parse errors on any HTML/plaintext error responses.\n try {\n bodyText = await response.text()\n } catch {\n throw new Error(\"Network error while reading server response\")\n }\n\n let json: any\n\n try {\n json = bodyText ? JSON.parse(bodyText) : {}\n } catch {\n if (!response.ok) throw new Error(\"Server returned an invalid JSON error response\")\n throw new Error(\"Invalid JSON received from server\")\n }\n\n if (!response.ok) {\n const message = json && typeof json.error === \"string\" ? json.error : \"Unknown server error\"\n throw new Error(message)\n }\n\n return json as T\n }\n\n /**\n * Registration of a new user with a passkey. Will require user interaction to create a passkey.\n *\n * @param userName - A unique identifier for the user, like an email address or username.\n * Can be empty for usernameless authentication.\n */\n async createUserWithPasskey(userName?: string): Promise<CreateUserWithPasskeyResult> {\n try {\n // Step 1: Get registration options from server\n const beginResponse = await fetch(`${this.baseUrl}/user/register/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ userName })\n })\n\n const { userId, options } =\n await this.parseResponse<BeginUserRegistration200>(beginResponse)\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential = await startRegistration({\n optionsJSON: options as unknown as PublicKeyCredentialCreationOptionsJSON\n })\n\n // Step 3: Send credential to server for verification\n const completeResponse = await fetch(`${this.baseUrl}/user/register/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ userId, credential })\n })\n\n const completeData = await this.parseResponse<CompleteUserRegistration200>(completeResponse)\n\n if (!completeData.success) throw new Error(\"Server could not complete registration\")\n\n return {\n success: true,\n data: {\n userId: completeData.userId,\n authenticationToken: completeData.authenticationToken,\n credential: completeData.credential\n }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Adds a passkey to an existing user. Will require user interaction to create a passkey.\n *\n * @param authenticationToken - The user authentication token, returned from .authenticate() or createUserWithPasskey().\n * Do NOT store it in local storage, database, etc. Always keep it in memory.\n * @param userName - A unique identifier for the user. If not provided, the user's stored userName will be used.\n */\n async addPasskey(authenticationToken: string, userName?: string): Promise<AddPasskeyResult> {\n if (!authenticationToken) throw new Error(\"Authentication token is required\")\n\n try {\n // Step 1: Get credential registration options from server\n const beginResponse = await fetch(`${this.baseUrl}/user/credential/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ authenticationToken, userName })\n })\n\n const { options } = await this.parseResponse<BeginCredentialRegistration200>(beginResponse)\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential = await startRegistration({\n optionsJSON: options as unknown as PublicKeyCredentialCreationOptionsJSON\n })\n\n // Step 3: Send credential to server for verification\n const completeResponse = await fetch(`${this.baseUrl}/user/credential/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ authenticationToken, credential })\n })\n\n const completeData =\n await this.parseResponse<CompleteCredentialRegistration200>(completeResponse)\n\n if (!completeData.success) throw new Error(\"Server could not complete passkey registration\")\n\n return {\n success: true,\n data: {\n authenticationToken: completeData.authenticationToken,\n credential: completeData.credential\n }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Updates a passkey label. Any passkey registered to the user can be updated.\n *\n * @param authenticationToken - The user authentication token, returned from .authenticate() or createUserWithPasskey().\n * Do NOT store it in local storage, database, etc. Always keep it in memory.\n * @param credentialId - The ID of the passkey to update, returned from createUserWithPasskey() or addPasskey().\n * @param label - The new label for the passkey.\n */\n async updatePasskeyLabel(\n authenticationToken: string,\n credentialId: string,\n label: string\n ): Promise<UpdatePasskeyLabelResult> {\n if (!authenticationToken) throw new Error(\"Authentication token is required\")\n if (!credentialId) throw new Error(\"Credential ID is required\")\n\n try {\n const body: UpdateCredentialLabelBody = { authenticationToken, label }\n const response = await fetch(`${this.baseUrl}/credential/${credentialId}/label`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify(body)\n })\n\n if (!response.ok) throw new Error(\"Failed to update passkey label\")\n\n return { success: true }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Authenticates a user. Can be used for login, verification, 2FA, etc.\n * Will require user interaction to authenticate.\n *\n * @param userIdentifier - Optional. Identify the user by their PlainKey user ID or userName.\n * Not required for usernameless authentication.\n */\n async authenticate(userIdentifier?: UserIdentifier): Promise<AuthenticateResult> {\n try {\n // Step 1: Get authentication options from server\n const beginResponse = await fetch(`${this.baseUrl}/authenticate/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ userIdentifier })\n })\n\n const beginData = await this.parseResponse<BeginAuthentication200>(beginResponse)\n\n if (!beginData.options) throw new Error(\"Server returned no options in authentication begin response\")\n\n // Step 2: Pass options to the authenticator and wait for response\n const authenticationResponse = await startAuthentication({\n optionsJSON: beginData.options as unknown as PublicKeyCredentialRequestOptionsJSON\n })\n\n if (!authenticationResponse) throw new Error(\"No authentication response from browser\")\n\n // Step 3: POST the response to the server\n const completeResponse = await fetch(`${this.baseUrl}/authenticate/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({\n loginSessionId: beginData.loginSession.id,\n authenticationResponse\n })\n })\n\n const completeData = await this.parseResponse<CompleteAuthentication200>(completeResponse)\n\n return {\n success: true,\n data: { authenticationToken: completeData.authenticationToken }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["bodyText: string","json: any","body: UpdateCredentialLabelBody"],"sources":["../src/plainKey.ts"],"sourcesContent":["import {\n startAuthentication,\n startRegistration,\n type PublicKeyCredentialCreationOptionsJSON,\n type PublicKeyCredentialRequestOptionsJSON\n} from \"@simplewebauthn/browser\"\n\nimport type {\n AuthenticateResult,\n AddPasskeyResult,\n CreateUserWithPasskeyResult,\n UpdatePasskeyLabelResult,\n UserIdentifier\n} from \"./types\"\n\nimport type {\n BeginUserRegistration200,\n CompleteUserRegistration200,\n BeginCredentialRegistration200,\n CompleteCredentialRegistration200,\n BeginAuthentication200,\n CompleteAuthentication200,\n UpdateCredentialLabelBody\n} from \"./generated/api\"\n\n/**\n * PlainKey client for the browser. Used to register new users, add passkeys to existing users, and log users in.\n *\n * Docs: https://plainkey.io/docs\n *\n * @param projectId - Your PlainKey project ID. You can find it in the PlainKey admin dashboard.\n * @param baseUrl - Set by default to https://api.plainkey.io/browser. Change only for development purposes.\n */\nexport class PlainKey {\n private readonly projectId: string\n private readonly baseUrl: string\n\n constructor(projectId: string, baseUrl: string = \"https://api.plainkey.io/browser\") {\n if (!projectId) throw new Error(\"Project ID is required\")\n if (!baseUrl) throw new Error(\"Base URL is required\")\n\n this.projectId = projectId\n this.baseUrl = baseUrl.replace(/\\/$/, \"\") // Remove trailing slash\n }\n\n /**\n * Helper to parse response JSON.\n * Throws error if status code is not 200 OK or if the response is not valid JSON.\n */\n private async parseResponse<T = any>(response: Response): Promise<T> {\n let bodyText: string\n\n // Read as text first to avoid JSON.parse errors on any HTML/plaintext error responses.\n try {\n bodyText = await response.text()\n } catch {\n throw new Error(\"Network error while reading server response\")\n }\n\n let json: any\n\n try {\n json = bodyText ? JSON.parse(bodyText) : {}\n } catch {\n if (!response.ok) throw new Error(\"Server returned an invalid JSON error response\")\n throw new Error(\"Invalid JSON received from server\")\n }\n\n if (!response.ok) {\n const message = json && typeof json.error === \"string\" ? json.error : \"Unknown server error\"\n throw new Error(message)\n }\n\n return json as T\n }\n\n /**\n * Registration of a new user with a passkey. Will require user interaction to create a passkey.\n *\n * @param userName - A unique identifier for the user, like an email address or username.\n * Can be empty for usernameless authentication.\n */\n async createUserWithPasskey(userName?: string): Promise<CreateUserWithPasskeyResult> {\n try {\n // Step 1: Get registration options from server\n const beginResponse = await fetch(`${this.baseUrl}/user/register/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ userName })\n })\n\n const { userId, options } = await this.parseResponse<BeginUserRegistration200>(beginResponse)\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential = await startRegistration({\n optionsJSON: options as unknown as PublicKeyCredentialCreationOptionsJSON\n })\n\n // Step 3: Send credential to server for verification\n const completeResponse = await fetch(`${this.baseUrl}/user/register/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ userId, credential })\n })\n\n const completeData = await this.parseResponse<CompleteUserRegistration200>(completeResponse)\n\n if (!completeData.success) throw new Error(\"Server could not complete registration\")\n\n return {\n success: true,\n data: {\n userId: completeData.userId,\n authenticationToken: completeData.authenticationToken,\n credential: completeData.credential\n }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Adds a passkey to an existing user. Will require user interaction to create a passkey.\n *\n * @param authenticationToken - The user authentication token, returned from .authenticate() or createUserWithPasskey().\n * Do NOT store it in local storage, database, etc. Always keep it in memory.\n * @param userName - A unique identifier for the user. If not provided, the user's stored userName will be used.\n */\n async addPasskey(authenticationToken: string, userName?: string): Promise<AddPasskeyResult> {\n if (!authenticationToken) throw new Error(\"Authentication token is required\")\n\n try {\n // Step 1: Get credential registration options from server\n const beginResponse = await fetch(`${this.baseUrl}/user/credential/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ authenticationToken, userName })\n })\n\n const { options } = await this.parseResponse<BeginCredentialRegistration200>(beginResponse)\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential = await startRegistration({\n optionsJSON: options as unknown as PublicKeyCredentialCreationOptionsJSON\n })\n\n // Step 3: Send credential to server for verification\n const completeResponse = await fetch(`${this.baseUrl}/user/credential/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ authenticationToken, credential })\n })\n\n const completeData =\n await this.parseResponse<CompleteCredentialRegistration200>(completeResponse)\n\n if (!completeData.success) throw new Error(\"Server could not complete passkey registration\")\n\n return {\n success: true,\n data: {\n authenticationToken: completeData.authenticationToken,\n credential: completeData.credential\n }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Completes a server-initiated passkey registration. Use this when your backend has already called\n * beginCredentialRegistration() via the Server SDK (or the associated endpoint via REST API), and passed the options and\n * authenticationToken to the frontend.\n *\n * @param authenticationToken - The short-lived token returned alongside the options by beginCredentialRegistration().\n * @param options - The WebAuthn creation options returned by the server's beginCredentialRegistration().\n * Do NOT store it in local storage, database, etc. Always keep it in memory.\n */\n async completePasskeyRegistration(\n authenticationToken: string,\n options: PublicKeyCredentialCreationOptionsJSON\n ): Promise<AddPasskeyResult> {\n if (!authenticationToken) throw new Error(\"Authentication token is required\")\n if (!options) throw new Error(\"Options are required\")\n\n try {\n // Step 1: Complete the WebAuthn ceremony using the server-provided options\n const credential = await startRegistration({ optionsJSON: options })\n\n // Step 2: Send credential to server for verification\n const completeResponse = await fetch(`${this.baseUrl}/user/credential/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ authenticationToken, credential })\n })\n\n const completeData =\n await this.parseResponse<CompleteCredentialRegistration200>(completeResponse)\n\n if (!completeData.success) throw new Error(\"Server could not complete passkey registration\")\n\n return {\n success: true,\n data: {\n authenticationToken: completeData.authenticationToken,\n credential: completeData.credential\n }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Updates a passkey label. Any passkey registered to the user can be updated.\n *\n * @param authenticationToken - The user authentication token, returned from .authenticate() or createUserWithPasskey().\n * Do NOT store it in local storage, database, etc. Always keep it in memory.\n * @param credentialId - The ID of the passkey to update, returned from createUserWithPasskey() or addPasskey().\n * @param label - The new label for the passkey.\n */\n async updatePasskeyLabel(\n authenticationToken: string,\n credentialId: string,\n label: string\n ): Promise<UpdatePasskeyLabelResult> {\n if (!authenticationToken) throw new Error(\"Authentication token is required\")\n if (!credentialId) throw new Error(\"Credential ID is required\")\n\n try {\n const body: UpdateCredentialLabelBody = { authenticationToken, label }\n const response = await fetch(`${this.baseUrl}/credential/${credentialId}/label`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify(body)\n })\n\n if (!response.ok) throw new Error(\"Failed to update passkey label\")\n\n return { success: true }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n\n /**\n * Authenticates a user. Can be used for login, verification, 2FA, etc.\n * Will require user interaction to authenticate.\n *\n * @param userIdentifier - Optional. Identify the user by their PlainKey user ID or userName.\n * Not required for usernameless authentication.\n */\n async authenticate(userIdentifier?: UserIdentifier): Promise<AuthenticateResult> {\n try {\n // Step 1: Get authentication options from server\n const beginResponse = await fetch(`${this.baseUrl}/authenticate/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({ userIdentifier })\n })\n\n const beginData = await this.parseResponse<BeginAuthentication200>(beginResponse)\n\n if (!beginData.options)\n throw new Error(\"Server returned no options in authentication begin response\")\n\n // Step 2: Pass options to the authenticator and wait for response\n const authenticationResponse = await startAuthentication({\n optionsJSON: beginData.options as unknown as PublicKeyCredentialRequestOptionsJSON\n })\n\n if (!authenticationResponse) throw new Error(\"No authentication response from browser\")\n\n // Step 3: POST the response to the server\n const completeResponse = await fetch(`${this.baseUrl}/authenticate/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify({\n loginSessionId: beginData.loginSession.id,\n authenticationResponse\n })\n })\n\n const completeData = await this.parseResponse<CompleteAuthentication200>(completeResponse)\n\n return {\n success: true,\n data: { authenticationToken: completeData.authenticationToken }\n }\n } catch (error) {\n return {\n success: false,\n error: { message: error instanceof Error ? error.message : \"Unknown error\" }\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAiCA,IAAa,WAAb,MAAsB;CAIpB,YAAY,WAAmB,UAAkB,mCAAmC;AAClF,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,yBAAyB;AACzD,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,uBAAuB;AAErD,OAAK,YAAY;AACjB,OAAK,UAAU,QAAQ,QAAQ,OAAO,GAAG;;;;;;CAO3C,MAAc,cAAuB,UAAgC;EACnE,IAAIA;AAGJ,MAAI;AACF,cAAW,MAAM,SAAS,MAAM;UAC1B;AACN,SAAM,IAAI,MAAM,8CAA8C;;EAGhE,IAAIC;AAEJ,MAAI;AACF,UAAO,WAAW,KAAK,MAAM,SAAS,GAAG,EAAE;UACrC;AACN,OAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,iDAAiD;AACnF,SAAM,IAAI,MAAM,oCAAoC;;AAGtD,MAAI,CAAC,SAAS,IAAI;GAChB,MAAM,UAAU,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AACtE,SAAM,IAAI,MAAM,QAAQ;;AAG1B,SAAO;;;;;;;;CAST,MAAM,sBAAsB,UAAyD;AACnF,MAAI;GAEF,MAAM,gBAAgB,MAAM,MAAM,GAAG,KAAK,QAAQ,uBAAuB;IACvE,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;IACnC,CAAC;GAEF,MAAM,EAAE,QAAQ,YAAY,MAAM,KAAK,cAAwC,cAAc;GAG7F,MAAM,aAAa,MAAM,kBAAkB,EACzC,aAAa,SACd,CAAC;GAGF,MAAM,mBAAmB,MAAM,MAAM,GAAG,KAAK,QAAQ,0BAA0B;IAC7E,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU;KAAE;KAAQ;KAAY,CAAC;IAC7C,CAAC;GAEF,MAAM,eAAe,MAAM,KAAK,cAA2C,iBAAiB;AAE5F,OAAI,CAAC,aAAa,QAAS,OAAM,IAAI,MAAM,yCAAyC;AAEpF,UAAO;IACL,SAAS;IACT,MAAM;KACJ,QAAQ,aAAa;KACrB,qBAAqB,aAAa;KAClC,YAAY,aAAa;KAC1B;IACF;WACM,OAAO;AACd,UAAO;IACL,SAAS;IACT,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;IAC7E;;;;;;;;;;CAWL,MAAM,WAAW,qBAA6B,UAA8C;AAC1F,MAAI,CAAC,oBAAqB,OAAM,IAAI,MAAM,mCAAmC;AAE7E,MAAI;GAEF,MAAM,gBAAgB,MAAM,MAAM,GAAG,KAAK,QAAQ,yBAAyB;IACzE,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU;KAAE;KAAqB;KAAU,CAAC;IACxD,CAAC;GAEF,MAAM,EAAE,YAAY,MAAM,KAAK,cAA8C,cAAc;GAG3F,MAAM,aAAa,MAAM,kBAAkB,EACzC,aAAa,SACd,CAAC;GAGF,MAAM,mBAAmB,MAAM,MAAM,GAAG,KAAK,QAAQ,4BAA4B;IAC/E,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU;KAAE;KAAqB;KAAY,CAAC;IAC1D,CAAC;GAEF,MAAM,eACJ,MAAM,KAAK,cAAiD,iBAAiB;AAE/E,OAAI,CAAC,aAAa,QAAS,OAAM,IAAI,MAAM,iDAAiD;AAE5F,UAAO;IACL,SAAS;IACT,MAAM;KACJ,qBAAqB,aAAa;KAClC,YAAY,aAAa;KAC1B;IACF;WACM,OAAO;AACd,UAAO;IACL,SAAS;IACT,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;IAC7E;;;;;;;;;;;;CAaL,MAAM,4BACJ,qBACA,SAC2B;AAC3B,MAAI,CAAC,oBAAqB,OAAM,IAAI,MAAM,mCAAmC;AAC7E,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,uBAAuB;AAErD,MAAI;GAEF,MAAM,aAAa,MAAM,kBAAkB,EAAE,aAAa,SAAS,CAAC;GAGpE,MAAM,mBAAmB,MAAM,MAAM,GAAG,KAAK,QAAQ,4BAA4B;IAC/E,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU;KAAE;KAAqB;KAAY,CAAC;IAC1D,CAAC;GAEF,MAAM,eACJ,MAAM,KAAK,cAAiD,iBAAiB;AAE/E,OAAI,CAAC,aAAa,QAAS,OAAM,IAAI,MAAM,iDAAiD;AAE5F,UAAO;IACL,SAAS;IACT,MAAM;KACJ,qBAAqB,aAAa;KAClC,YAAY,aAAa;KAC1B;IACF;WACM,OAAO;AACd,UAAO;IACL,SAAS;IACT,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;IAC7E;;;;;;;;;;;CAYL,MAAM,mBACJ,qBACA,cACA,OACmC;AACnC,MAAI,CAAC,oBAAqB,OAAM,IAAI,MAAM,mCAAmC;AAC7E,MAAI,CAAC,aAAc,OAAM,IAAI,MAAM,4BAA4B;AAE/D,MAAI;GACF,MAAMC,OAAkC;IAAE;IAAqB;IAAO;AAUtE,OAAI,EATa,MAAM,MAAM,GAAG,KAAK,QAAQ,cAAc,aAAa,SAAS;IAC/E,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU,KAAK;IAC3B,CAAC,EAEY,GAAI,OAAM,IAAI,MAAM,iCAAiC;AAEnE,UAAO,EAAE,SAAS,MAAM;WACjB,OAAO;AACd,UAAO;IACL,SAAS;IACT,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;IAC7E;;;;;;;;;;CAWL,MAAM,aAAa,gBAA8D;AAC/E,MAAI;GAEF,MAAM,gBAAgB,MAAM,MAAM,GAAG,KAAK,QAAQ,sBAAsB;IACtE,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU,EAAE,gBAAgB,CAAC;IACzC,CAAC;GAEF,MAAM,YAAY,MAAM,KAAK,cAAsC,cAAc;AAEjF,OAAI,CAAC,UAAU,QACb,OAAM,IAAI,MAAM,8DAA8D;GAGhF,MAAM,yBAAyB,MAAM,oBAAoB,EACvD,aAAa,UAAU,SACxB,CAAC;AAEF,OAAI,CAAC,uBAAwB,OAAM,IAAI,MAAM,0CAA0C;GAGvF,MAAM,mBAAmB,MAAM,MAAM,GAAG,KAAK,QAAQ,yBAAyB;IAC5E,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,gBAAgB,KAAK;KACtB;IACD,MAAM,KAAK,UAAU;KACnB,gBAAgB,UAAU,aAAa;KACvC;KACD,CAAC;IACH,CAAC;AAIF,UAAO;IACL,SAAS;IACT,MAAM,EAAE,sBAJW,MAAM,KAAK,cAAyC,iBAAiB,EAI9C,qBAAqB;IAChE;WACM,OAAO;AACd,UAAO;IACL,SAAS;IACT,OAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB;IAC7E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainkey/browser",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "PlainKey browser SDK for passkey registration, login and credential management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",