@plainkey/browser 0.4.1 → 0.6.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,30 +1,48 @@
1
- import { LoginBeginRequest, LoginCompleteResponse, RegistrationBeginRequest, RegistrationCompleteResponse, UserCredentialBeginRequest, UserCredentialCompleteResponse } from "@plainkey/types";
1
+ import { AddPasskeyResult, AuthenticateResult, CreateUserWithPasskeyResult, UserIdentifier } from "@plainkey/types";
2
2
 
3
- //#region src/plainkey-client.d.ts
4
- type PlainKeyClientParams = {
5
- projectId: string;
6
- baseUrl?: string;
7
- };
8
- declare class PlainKeyClient {
3
+ //#region src/plainKey.d.ts
4
+
5
+ /**
6
+ * PlainKey client for the browser. Used to register new users, add passkeys to existing users, and log users in.
7
+ *
8
+ * Docs: https://plainkey.io/docs
9
+ *
10
+ * @param projectId - Your PlainKey project ID. You can find it in the PlainKey admin dashboard.
11
+ * @param baseUrl - Set by default to https://api.plainkey.io/api. Change only for development purposes.
12
+ */
13
+ declare class PlainKey {
9
14
  private readonly projectId;
10
15
  private readonly baseUrl;
11
- constructor(clientParams: PlainKeyClientParams);
16
+ constructor(projectId: string, baseUrl?: string);
17
+ /**
18
+ * Helper to parse response JSON.
19
+ * Throws error if status code is not 200 OK, if the response is not valid JSON.
20
+ */
21
+ private parseResponse;
12
22
  /**
13
- * Registration of a new user with passkey.
14
- * Creates a new user and adds a credential to it.
23
+ * Registration of a new user with a passkey. Will require user interaction to create a passkey.
24
+ *
25
+ * @param userName - A stable unique identifier for the user, like an email address or username.
26
+ * Can be empty for usernameless login.
15
27
  */
16
- Registration(beginParams: RegistrationBeginRequest): Promise<RegistrationCompleteResponse>;
28
+ createUserWithPasskey(userName?: string): Promise<CreateUserWithPasskeyResult>;
17
29
  /**
18
- * Add credential to existing user.
19
- * Requires a valid user authentication token passed in beginParams, which will be sent in the request body.
20
- * However, do not store the token in local storage, database, etc. Always keep it in memory.
30
+ * Adds a passkey to an existing user. Will require user interaction to create a passkey.
31
+ *
32
+ * @param userToken - The user authentication token, obtained from login.
33
+ * Do NOT store it in local storage, database, etc. Always keep it in memory.
34
+ *
35
+ * @param userIdentifier - An object with either the user's PlainKey User ID or their userName.
21
36
  */
22
- AddCredential(beginParams: UserCredentialBeginRequest): Promise<UserCredentialCompleteResponse>;
37
+ addPasskey(userToken: string, userIdentifier: UserIdentifier): Promise<AddPasskeyResult>;
23
38
  /**
24
- * Performs a login ceremony.
39
+ * Authenticates a user. Can be used for login, verification, 2FA, etc.
40
+ * Will require user interaction to authenticate.
41
+ *
42
+ * @param userIdentifier - An object with either the user's PlainKey User ID or their userName.
25
43
  */
26
- Login(beginParams: LoginBeginRequest): Promise<LoginCompleteResponse>;
44
+ authenticate(userIdentifier: UserIdentifier): Promise<AuthenticateResult>;
27
45
  }
28
46
  //#endregion
29
- export { PlainKeyClient, PlainKeyClientParams };
47
+ export { PlainKey };
30
48
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plainkey-client.ts"],"sourcesContent":[],"mappings":";;;KAsBY,oBAAA;EAAA,SAAA,EAAA,MAAA;EAMC,OAAA,CAAA,EAAA,MAAA;CAIe;AAWM,cAfrB,cAAA,CAeqB;EAAmC,iBAAA,SAAA;EAAR,iBAAA,OAAA;EAwD5C,WAAA,CAAA,YAAA,EAnEW,oBAmEX;EACJ;;;;EAuDkC,YAAA,CAAA,WAAA,EAhHb,wBAgHa,CAAA,EAhHc,OAgHd,CAhHsB,4BAgHtB,CAAA;EAAO;;;;;6BAxDrC,6BACZ,QAAQ;;;;qBAuDc,oBAAoB,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plainKey.ts"],"sourcesContent":[],"mappings":";;;;;;AAiCA;;;;;;AAuMqC,cAvMxB,QAAA,CAuMwB;EAAyB,iBAAA,SAAA;EAAR,iBAAA,OAAA;EAAO,WAAA,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA;;;;;;;;;;;;4CApJX,QAAQ;;;;;;;;;gDAuEJ,iBAAiB,QAAQ;;;;;;;+BA6E1C,iBAAiB,QAAQ"}
package/dist/index.js CHANGED
@@ -1,134 +1,205 @@
1
1
  import { startAuthentication, startRegistration } from "@simplewebauthn/browser";
2
2
 
3
- //#region src/plainkey-client.ts
4
- var PlainKeyClient = class {
5
- constructor(clientParams) {
6
- const { projectId, baseUrl = "https://api.plainkey.io/api" } = clientParams;
3
+ //#region src/plainKey.ts
4
+ /**
5
+ * PlainKey client for the browser. Used to register new users, add passkeys to existing users, and log users in.
6
+ *
7
+ * Docs: https://plainkey.io/docs
8
+ *
9
+ * @param projectId - Your PlainKey project ID. You can find it in the PlainKey admin dashboard.
10
+ * @param baseUrl - Set by default to https://api.plainkey.io/api. Change only for development purposes.
11
+ */
12
+ var PlainKey = class {
13
+ constructor(projectId, baseUrl = "https://api.plainkey.io/api") {
14
+ if (!projectId) throw new Error("Project ID is required");
15
+ if (!baseUrl) throw new Error("Base URL is required");
7
16
  this.projectId = projectId;
8
17
  this.baseUrl = baseUrl.replace(/\/$/, "");
9
18
  }
10
19
  /**
11
- * Registration of a new user with passkey.
12
- * Creates a new user and adds a credential to it.
20
+ * Helper to parse response JSON.
21
+ * Throws error if status code is not 200 OK, if the response is not valid JSON.
13
22
  */
14
- async Registration(beginParams) {
15
- const beginResponse = await fetch(`${this.baseUrl}/user/register/begin`, {
16
- method: "POST",
17
- headers: {
18
- "Content-Type": "application/json",
19
- "x-project-id": this.projectId
20
- },
21
- body: JSON.stringify(beginParams)
22
- });
23
- if (!beginResponse.ok) {
24
- const errorData = await beginResponse.json();
25
- throw new Error(errorData.error);
23
+ async parseResponse(response) {
24
+ let bodyText;
25
+ try {
26
+ bodyText = await response.text();
27
+ } catch {
28
+ throw new Error("Network error while reading server response");
26
29
  }
27
- const { options, user } = await beginResponse.json();
28
- const credential = await startRegistration({ optionsJSON: options });
29
- const completeParams = {
30
- userIdentifier: { userId: user.id },
31
- credential
32
- };
33
- const completeResponse = await fetch(`${this.baseUrl}/user/register/complete`, {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/json",
37
- "x-project-id": this.projectId
38
- },
39
- body: JSON.stringify(completeParams)
40
- });
41
- if (!completeResponse.ok) {
42
- const errorData = await completeResponse.json();
43
- throw new Error(errorData.error);
30
+ let json;
31
+ try {
32
+ json = bodyText ? JSON.parse(bodyText) : {};
33
+ } catch {
34
+ if (!response.ok) throw new Error("Server returned an invalid JSON error response");
35
+ throw new Error("Invalid JSON received from server");
44
36
  }
45
- const response = await completeResponse.json();
46
- if (!response) throw new Error("No registration response from server");
47
- return response;
37
+ if (!response.ok) {
38
+ const message = json && typeof json.error === "string" ? json.error : "Unknown server error";
39
+ throw new Error(message);
40
+ }
41
+ return json;
48
42
  }
49
43
  /**
50
- * Add credential to existing user.
51
- * Requires a valid user authentication token passed in beginParams, which will be sent in the request body.
52
- * However, do not store the token in local storage, database, etc. Always keep it in memory.
44
+ * Registration of a new user with a passkey. Will require user interaction to create a passkey.
45
+ *
46
+ * @param userName - A stable unique identifier for the user, like an email address or username.
47
+ * Can be empty for usernameless login.
53
48
  */
54
- async AddCredential(beginParams) {
55
- const beginResponse = await fetch(`${this.baseUrl}/user/credential/begin`, {
56
- method: "POST",
57
- headers: {
58
- "Content-Type": "application/json",
59
- "x-project-id": this.projectId
60
- },
61
- body: JSON.stringify(beginParams)
62
- });
63
- if (!beginResponse.ok) {
64
- const errorData = await beginResponse.json();
65
- throw new Error(errorData.error);
66
- }
67
- const { options, user } = await beginResponse.json();
68
- const credential = await startRegistration({ optionsJSON: options });
69
- const completeParams = {
70
- userToken: beginParams.userToken,
71
- userIdentifier: { userId: user.id },
72
- credential
73
- };
74
- const completeResponse = await fetch(`${this.baseUrl}/user/credential/complete`, {
75
- method: "POST",
76
- headers: {
77
- "Content-Type": "application/json",
78
- "x-project-id": this.projectId
79
- },
80
- body: JSON.stringify(completeParams)
81
- });
82
- if (!completeResponse.ok) {
83
- const errorData = await completeResponse.json();
84
- throw new Error(errorData.error);
49
+ async createUserWithPasskey(userName) {
50
+ try {
51
+ const beginParams = { userName };
52
+ const beginResponse = await fetch(`${this.baseUrl}/user/register/begin`, {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ "x-project-id": this.projectId
57
+ },
58
+ body: JSON.stringify(beginParams)
59
+ });
60
+ const { options, user } = await this.parseResponse(beginResponse);
61
+ const credential = await startRegistration({ optionsJSON: options });
62
+ const completeParams = {
63
+ userIdentifier: { userId: user.id },
64
+ credential
65
+ };
66
+ const completeResponse = await fetch(`${this.baseUrl}/user/register/complete`, {
67
+ method: "POST",
68
+ headers: {
69
+ "Content-Type": "application/json",
70
+ "x-project-id": this.projectId
71
+ },
72
+ body: JSON.stringify(completeParams)
73
+ });
74
+ const completeResponseData = await this.parseResponse(completeResponse);
75
+ if (!completeResponseData.success) throw new Error("Server could not complete registration");
76
+ return {
77
+ success: completeResponseData.success,
78
+ data: {
79
+ user: completeResponseData.user,
80
+ token: completeResponseData.token,
81
+ credential: completeResponseData.credential,
82
+ session: completeResponseData.session
83
+ }
84
+ };
85
+ } catch (error) {
86
+ return {
87
+ success: false,
88
+ error: { message: error instanceof Error ? error.message : "Unknown error" }
89
+ };
85
90
  }
86
- const response = await completeResponse.json();
87
- if (!response) throw new Error("No credential registration response from server");
88
- return response;
89
91
  }
90
92
  /**
91
- * Performs a login ceremony.
93
+ * Adds a passkey to an existing user. Will require user interaction to create a passkey.
94
+ *
95
+ * @param userToken - The user authentication token, obtained from login.
96
+ * Do NOT store it in local storage, database, etc. Always keep it in memory.
97
+ *
98
+ * @param userIdentifier - An object with either the user's PlainKey User ID or their userName.
92
99
  */
93
- async Login(beginParams) {
94
- const beginResponse = await fetch(`${this.baseUrl}/login/begin`, {
95
- method: "POST",
96
- headers: {
97
- "Content-Type": "application/json",
98
- "x-project-id": this.projectId
99
- },
100
- body: JSON.stringify(beginParams)
101
- });
102
- if (!beginResponse.ok) {
103
- const errorData = await beginResponse.json();
104
- throw new Error(errorData.error);
100
+ async addPasskey(userToken, userIdentifier) {
101
+ try {
102
+ if (!userIdentifier) throw new Error("User identifier is required");
103
+ if (!userIdentifier.userId && !userIdentifier.userName) throw new Error("Either a userId or a userName is required");
104
+ const beginParams = {
105
+ userToken,
106
+ userIdentifier
107
+ };
108
+ const beginResponse = await fetch(`${this.baseUrl}/user/credential/begin`, {
109
+ method: "POST",
110
+ headers: {
111
+ "Content-Type": "application/json",
112
+ "x-project-id": this.projectId
113
+ },
114
+ body: JSON.stringify(beginParams)
115
+ });
116
+ const { options, user } = await this.parseResponse(beginResponse);
117
+ const credential = await startRegistration({ optionsJSON: options });
118
+ const completeParams = {
119
+ userToken: beginParams.userToken,
120
+ userIdentifier: { userId: user.id },
121
+ credential
122
+ };
123
+ const completeResponse = await fetch(`${this.baseUrl}/user/credential/complete`, {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ "x-project-id": this.projectId
128
+ },
129
+ body: JSON.stringify(completeParams)
130
+ });
131
+ const completeResponseData = await this.parseResponse(completeResponse);
132
+ if (!completeResponseData.success) throw new Error("Server could not complete passkey registration");
133
+ return {
134
+ success: completeResponseData.success,
135
+ data: {
136
+ user: completeResponseData.user,
137
+ token: completeResponseData.token,
138
+ credential: completeResponseData.credential,
139
+ session: completeResponseData.session
140
+ }
141
+ };
142
+ } catch (error) {
143
+ return {
144
+ success: false,
145
+ error: { message: error instanceof Error ? error.message : "Unknown error" }
146
+ };
105
147
  }
106
- const beginResponseData = await beginResponse.json();
107
- if (!beginResponseData.options) throw new Error("No options found in login begin response");
108
- const authenticationResponse = await startAuthentication({ optionsJSON: beginResponseData.options });
109
- if (!authenticationResponse) throw new Error("No authentication response from browser");
110
- const completeParams = {
111
- loginSessionId: beginResponseData.loginSession.id,
112
- authenticationResponse
113
- };
114
- const verificationResponse = await fetch(`${this.baseUrl}/login/complete`, {
115
- method: "POST",
116
- headers: {
117
- "Content-Type": "application/json",
118
- "x-project-id": this.projectId
119
- },
120
- body: JSON.stringify(completeParams)
121
- });
122
- if (!verificationResponse.ok) {
123
- const errorData = await verificationResponse.json();
124
- throw new Error(errorData.error);
148
+ }
149
+ /**
150
+ * Authenticates a user. Can be used for login, verification, 2FA, etc.
151
+ * Will require user interaction to authenticate.
152
+ *
153
+ * @param userIdentifier - An object with either the user's PlainKey User ID or their userName.
154
+ */
155
+ async authenticate(userIdentifier) {
156
+ if (!userIdentifier) throw new Error("User identifier is required");
157
+ if (!userIdentifier.userId && !userIdentifier.userName) throw new Error("Either a userId or a userName is required");
158
+ try {
159
+ const beginParams = { userIdentifier };
160
+ const beginResponse = await fetch(`${this.baseUrl}/login/begin`, {
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ "x-project-id": this.projectId
165
+ },
166
+ body: JSON.stringify(beginParams)
167
+ });
168
+ const beginResponseData = await this.parseResponse(beginResponse);
169
+ if (!beginResponseData.options) throw new Error("Server returned no options in login begin response");
170
+ const authenticationResponse = await startAuthentication({ optionsJSON: beginResponseData.options });
171
+ if (!authenticationResponse) throw new Error("No authentication response from browser");
172
+ const completeParams = {
173
+ loginSessionId: beginResponseData.loginSession.id,
174
+ authenticationResponse
175
+ };
176
+ const verificationResponse = await fetch(`${this.baseUrl}/login/complete`, {
177
+ method: "POST",
178
+ headers: {
179
+ "Content-Type": "application/json",
180
+ "x-project-id": this.projectId
181
+ },
182
+ body: JSON.stringify(completeParams)
183
+ });
184
+ const verificationResponseData = await this.parseResponse(verificationResponse);
185
+ if (!verificationResponseData.verified) throw new Error("Server could not verify login");
186
+ return {
187
+ success: verificationResponseData.verified,
188
+ data: {
189
+ user: verificationResponseData.user,
190
+ token: verificationResponseData.token,
191
+ session: verificationResponseData.session
192
+ }
193
+ };
194
+ } catch (error) {
195
+ return {
196
+ success: false,
197
+ error: { message: error instanceof Error ? error.message : "Unknown error" }
198
+ };
125
199
  }
126
- const verificationResponseData = await verificationResponse.json();
127
- if (!verificationResponseData) throw new Error("No login verification response from server");
128
- return verificationResponseData;
129
200
  }
130
201
  };
131
202
 
132
203
  //#endregion
133
- export { PlainKeyClient };
204
+ export { PlainKey };
134
205
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["errorData: ErrorResponse","credential: RegistrationResponseJSON","completeParams: RegistrationCompleteRequest","response: RegistrationCompleteResponse","completeParams: UserCredentialCompleteRequest","response: UserCredentialCompleteResponse","beginResponseData: LoginBeginResponse","authenticationResponse: AuthenticationResponseJSON","completeParams: LoginCompleteRequest","verificationResponseData: LoginCompleteResponse"],"sources":["../src/plainkey-client.ts"],"sourcesContent":["import { startAuthentication, startRegistration } from \"@simplewebauthn/browser\"\nimport { RegistrationResponseJSON, AuthenticationResponseJSON } from \"@simplewebauthn/browser\"\n\nimport type {\n RegistrationBeginRequest,\n RegistrationCompleteRequest,\n UserCredentialBeginRequest,\n UserCredentialCompleteRequest,\n LoginBeginRequest,\n LoginCompleteRequest\n} from \"@plainkey/types\"\n\nimport type {\n RegistrationBeginResponse,\n RegistrationCompleteResponse,\n UserCredentialBeginResponse,\n UserCredentialCompleteResponse,\n LoginBeginResponse,\n LoginCompleteResponse,\n ErrorResponse\n} from \"@plainkey/types\"\n\nexport type PlainKeyClientParams = {\n projectId: string\n baseUrl?: string\n}\n\n// TODO: Account for errors like this: \"Unexpected token 'R', \"Response v\"... is not valid JSON\"\nexport class PlainKeyClient {\n private readonly projectId: string\n private readonly baseUrl: string\n\n constructor(clientParams: PlainKeyClientParams) {\n const { projectId, baseUrl = \"https://api.plainkey.io/api\" } = clientParams\n\n this.projectId = projectId\n this.baseUrl = baseUrl.replace(/\\/$/, \"\") // Remove trailing slash\n }\n\n /**\n * Registration of a new user with passkey.\n * Creates a new user and adds a credential to it.\n */\n async Registration(beginParams: RegistrationBeginRequest): Promise<RegistrationCompleteResponse> {\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(beginParams)\n })\n\n if (!beginResponse.ok) {\n const errorData: ErrorResponse = await beginResponse.json()\n throw new Error(errorData.error)\n }\n\n const { options, user } = (await beginResponse.json()) as RegistrationBeginResponse\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential: RegistrationResponseJSON = await startRegistration({\n optionsJSON: options\n })\n\n // Step 3: Send credential to server for verification\n const completeParams: RegistrationCompleteRequest = {\n userIdentifier: { userId: user.id },\n credential\n }\n\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(completeParams)\n })\n\n if (!completeResponse.ok) {\n const errorData: ErrorResponse = await completeResponse.json()\n throw new Error(errorData.error)\n }\n\n // Intentionally not throwing errors on verification failure - UI should handle this.\n const response: RegistrationCompleteResponse = await completeResponse.json()\n if (!response) throw new Error(\"No registration response from server\")\n\n return response\n }\n\n /**\n * Add credential to existing user.\n * Requires a valid user authentication token passed in beginParams, which will be sent in the request body.\n * However, do not store the token in local storage, database, etc. Always keep it in memory.\n */\n async AddCredential(\n beginParams: UserCredentialBeginRequest\n ): Promise<UserCredentialCompleteResponse> {\n // Step 1: Get credential registration options from server\n\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(beginParams)\n })\n\n if (!beginResponse.ok) {\n const errorData: ErrorResponse = await beginResponse.json()\n throw new Error(errorData.error)\n }\n\n const { options, user } = (await beginResponse.json()) as UserCredentialBeginResponse\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential: RegistrationResponseJSON = await startRegistration({\n optionsJSON: options\n })\n\n // Step 3: Send credential to server for verification\n const completeParams: UserCredentialCompleteRequest = {\n userToken: beginParams.userToken,\n userIdentifier: { userId: user.id },\n credential\n }\n\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(completeParams)\n })\n\n if (!completeResponse.ok) {\n const errorData: ErrorResponse = await completeResponse.json()\n throw new Error(errorData.error)\n }\n\n // Intentionally not throwing errors on verification failure - UI should handle this.\n const response: UserCredentialCompleteResponse = await completeResponse.json()\n if (!response) throw new Error(\"No credential registration response from server\")\n\n return response\n }\n\n /**\n * Performs a login ceremony.\n */\n async Login(beginParams: LoginBeginRequest): Promise<LoginCompleteResponse> {\n // Step 1: Get authentication options from server\n const beginResponse = await fetch(`${this.baseUrl}/login/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify(beginParams)\n })\n\n if (!beginResponse.ok) {\n const errorData: ErrorResponse = await beginResponse.json()\n throw new Error(errorData.error)\n }\n\n const beginResponseData: LoginBeginResponse = await beginResponse.json()\n if (!beginResponseData.options) {\n throw new Error(\"No options found in login begin response\")\n }\n\n // Step 2: Pass options to the authenticator and wait for response\n const authenticationResponse: AuthenticationResponseJSON = await startAuthentication({\n optionsJSON: beginResponseData.options\n })\n\n if (!authenticationResponse) {\n throw new Error(\"No authentication response from browser\")\n }\n\n // Step 3: POST the response to the server\n // This uses the login session ID from the begin response - always in JS memory.\n // Do not store it in local storage, database, etc.\n const completeParams: LoginCompleteRequest = {\n loginSessionId: beginResponseData.loginSession.id,\n authenticationResponse\n }\n\n const verificationResponse = await fetch(`${this.baseUrl}/login/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify(completeParams)\n })\n\n if (!verificationResponse.ok) {\n const errorData: ErrorResponse = await verificationResponse.json()\n throw new Error(errorData.error)\n }\n\n // Intentionally not throwing errors on verification failure - UI should handle this.\n const verificationResponseData: LoginCompleteResponse = await verificationResponse.json()\n if (!verificationResponseData) throw new Error(\"No login verification response from server\")\n\n return verificationResponseData\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["bodyText: string","json: any","beginParams: RegistrationBeginRequest","credential: RegistrationResponseJSON","completeParams: RegistrationCompleteRequest","completeResponseData: RegistrationCompleteResponse","beginParams: UserCredentialBeginRequest","completeParams: UserCredentialCompleteRequest","completeResponseData: UserCredentialCompleteResponse","beginParams: LoginBeginRequest","beginResponseData: LoginBeginResponse","authenticationResponse: AuthenticationResponseJSON","completeParams: LoginCompleteRequest","verificationResponseData: LoginCompleteResponse"],"sources":["../src/plainKey.ts"],"sourcesContent":["import { startAuthentication, startRegistration } from \"@simplewebauthn/browser\"\nimport { RegistrationResponseJSON, AuthenticationResponseJSON } from \"@simplewebauthn/browser\"\n\nimport type {\n RegistrationBeginRequest,\n RegistrationCompleteRequest,\n UserCredentialBeginRequest,\n UserCredentialCompleteRequest,\n LoginBeginRequest,\n LoginCompleteRequest,\n UserIdentifier,\n CreateUserWithPasskeyResult,\n AddPasskeyResult,\n AuthenticateResult\n} from \"@plainkey/types\"\n\nimport type {\n RegistrationBeginResponse,\n RegistrationCompleteResponse,\n UserCredentialBeginResponse,\n UserCredentialCompleteResponse,\n LoginBeginResponse,\n LoginCompleteResponse\n} from \"@plainkey/types\"\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/api. 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/api\") {\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, 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 // Parse the response text as JSON.\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 // Server should return { error: string }\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 stable unique identifier for the user, like an email address or username.\n * Can be empty for usernameless login.\n */\n async createUserWithPasskey(userName?: string): Promise<CreateUserWithPasskeyResult> {\n try {\n // Step 1: Get registration options from server\n const beginParams: RegistrationBeginRequest = { userName }\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(beginParams)\n })\n\n // Parse response JSON\n const { options, user } = await this.parseResponse<RegistrationBeginResponse>(beginResponse)\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential: RegistrationResponseJSON = await startRegistration({\n optionsJSON: options\n })\n\n // Step 3: Send credential to server for verification\n const completeParams: RegistrationCompleteRequest = {\n userIdentifier: { userId: user.id },\n credential\n }\n\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(completeParams)\n })\n\n // Parse response JSON\n const completeResponseData: RegistrationCompleteResponse =\n await this.parseResponse<RegistrationCompleteResponse>(completeResponse)\n\n if (!completeResponseData.success) throw new Error(\"Server could not complete registration\")\n\n // Return success\n return {\n success: completeResponseData.success,\n data: {\n user: completeResponseData.user,\n token: completeResponseData.token,\n credential: completeResponseData.credential,\n session: completeResponseData.session\n }\n }\n } catch (error) {\n // Return error\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : \"Unknown error\"\n }\n }\n }\n }\n\n /**\n * Adds a passkey to an existing user. Will require user interaction to create a passkey.\n *\n * @param userToken - The user authentication token, obtained from login.\n * Do NOT store it in local storage, database, etc. Always keep it in memory.\n *\n * @param userIdentifier - An object with either the user's PlainKey User ID or their userName.\n */\n async addPasskey(userToken: string, userIdentifier: UserIdentifier): Promise<AddPasskeyResult> {\n try {\n // Validate user identifier\n if (!userIdentifier) throw new Error(\"User identifier is required\")\n if (!userIdentifier.userId && !userIdentifier.userName)\n throw new Error(\"Either a userId or a userName is required\")\n\n // Step 1: Get credential registration options from server\n const beginParams: UserCredentialBeginRequest = { userToken, userIdentifier }\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(beginParams)\n })\n\n // Parse response JSON\n const { options, user }: UserCredentialBeginResponse =\n await this.parseResponse<UserCredentialBeginResponse>(beginResponse)\n\n // Step 2: Create credential using browser's WebAuthn API\n const credential: RegistrationResponseJSON = await startRegistration({\n optionsJSON: options\n })\n\n // Step 3: Send credential to server for verification\n const completeParams: UserCredentialCompleteRequest = {\n userToken: beginParams.userToken,\n userIdentifier: { userId: user.id },\n credential\n }\n\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(completeParams)\n })\n\n // Parse response JSON\n const completeResponseData: UserCredentialCompleteResponse =\n await this.parseResponse<UserCredentialCompleteResponse>(completeResponse)\n\n if (!completeResponseData.success)\n throw new Error(\"Server could not complete passkey registration\")\n\n // Return success\n return {\n success: completeResponseData.success,\n data: {\n user: completeResponseData.user,\n token: completeResponseData.token,\n credential: completeResponseData.credential,\n session: completeResponseData.session\n }\n }\n } catch (error) {\n // Return error\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : \"Unknown error\"\n }\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 - An object with either the user's PlainKey User ID or their userName.\n */\n async authenticate(userIdentifier: UserIdentifier): Promise<AuthenticateResult> {\n // Validate user identifier\n if (!userIdentifier) throw new Error(\"User identifier is required\")\n if (!userIdentifier.userId && !userIdentifier.userName)\n throw new Error(\"Either a userId or a userName is required\")\n\n try {\n // Step 1: Get authentication options from server\n const beginParams: LoginBeginRequest = { userIdentifier }\n const beginResponse = await fetch(`${this.baseUrl}/login/begin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify(beginParams)\n })\n\n // Parse response JSON\n const beginResponseData: LoginBeginResponse =\n await this.parseResponse<LoginBeginResponse>(beginResponse)\n\n if (!beginResponseData.options)\n throw new Error(\"Server returned no options in login begin response\")\n\n // Step 2: Pass options to the authenticator and wait for response\n const authenticationResponse: AuthenticationResponseJSON = await startAuthentication({\n optionsJSON: beginResponseData.options\n })\n\n if (!authenticationResponse) throw new Error(\"No authentication response from browser\")\n\n // Step 3: POST the response to the server\n // This uses the login session ID from the begin response - always in JS memory.\n // Do not store it in local storage, database, etc.\n const completeParams: LoginCompleteRequest = {\n loginSessionId: beginResponseData.loginSession.id,\n authenticationResponse\n }\n\n const verificationResponse = await fetch(`${this.baseUrl}/login/complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-project-id\": this.projectId\n },\n body: JSON.stringify(completeParams)\n })\n\n const verificationResponseData: LoginCompleteResponse =\n await this.parseResponse<LoginCompleteResponse>(verificationResponse)\n\n if (!verificationResponseData.verified) throw new Error(\"Server could not verify login\")\n\n // Return success\n return {\n success: verificationResponseData.verified,\n data: {\n user: verificationResponseData.user,\n token: verificationResponseData.token,\n session: verificationResponseData.session\n }\n }\n } catch (error) {\n // Return error\n return {\n success: false,\n error: {\n message: error instanceof Error ? error.message : \"Unknown error\"\n }\n }\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainkey/browser",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "PlainKey browser SDK for passkey registration, login and credential management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",