@idos-network/client 1.0.0-rc.1.0 → 1.0.0-rc.1.1

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/package.json CHANGED
@@ -1,23 +1,25 @@
1
1
  {
2
2
  "name": "@idos-network/client",
3
3
  "description": "idOS Client JavaScript SDK for browser environments",
4
- "version": "1.0.0-rc.1.0",
4
+ "version": "1.0.0-rc.1.1",
5
5
  "clean-package": {
6
6
  "remove": [
7
- "devDependencies.@idos-network/core"
7
+ "devDependencies.@idos-network/core",
8
+ "devDependencies.@idos-network/credentials"
8
9
  ]
9
10
  },
10
11
  "dependencies": {
11
- "@kwilteam/kwil-js": "^0.9.4",
12
- "es-toolkit": "^1.38.0",
13
- "tiny-invariant": "^1.3.3"
12
+ "@gemwallet/api": "catalog:core",
13
+ "@idos-network/kwil-js": "catalog:core",
14
+ "@idos-network/utils": "workspace:*",
15
+ "es-toolkit": "catalog:utils"
14
16
  },
15
17
  "devDependencies": {
16
18
  "clean-package": "^2.2.0",
17
19
  "release-it": "^19.0.2",
18
20
  "release-it-pnpm": "^4.6.5",
19
- "tsdown": "^0.11.12",
20
- "typescript": "^5.8.3"
21
+ "tiny-invariant": "catalog:core",
22
+ "tsdown": "catalog:build"
21
23
  },
22
24
  "engines": {
23
25
  "node": ">=20.0.0"
@@ -35,6 +37,9 @@
35
37
  "homepage": "https://idos.network",
36
38
  "license": "MIT",
37
39
  "module": "dist/index.js",
40
+ "peerDependencies": {
41
+ "typescript": "catalog:build"
42
+ },
38
43
  "publishConfig": {
39
44
  "access": "public"
40
45
  },
@@ -0,0 +1,25 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: any is fine here. */
2
+ import type { BaseProvider } from "@idos-network/utils/enclave";
3
+
4
+ type BaseProviderInstance = typeof BaseProvider.prototype & {
5
+ // Custom method from enclave to ensure <-> iframe communication
6
+ signTypedDataResponse: (signature: string) => Promise<void>;
7
+ };
8
+
9
+ // Gets the method argument type
10
+ export type MethodArg<T> = T extends (arg: infer A) => any ? A : never;
11
+
12
+ // Gets the method return type
13
+ export type MethodReturn<T> = T extends (...args: any[]) => infer R ? R : never;
14
+
15
+ export type BaseProviderMethodArgs = {
16
+ [K in keyof BaseProviderInstance as BaseProviderInstance[K] extends (...args: any[]) => any
17
+ ? K
18
+ : never]: Parameters<BaseProviderInstance[K]>;
19
+ };
20
+
21
+ export type BaseProviderMethodReturn = {
22
+ [K in keyof BaseProviderInstance as BaseProviderInstance[K] extends (...args: any[]) => any
23
+ ? K
24
+ : never]: ReturnType<BaseProviderInstance[K]>;
25
+ };
@@ -1,103 +1,129 @@
1
- import { base64Encode, type idOSCredential } from "@idos-network/core";
1
+ import type { idOSCredential } from "@idos-network/credentials";
2
+ import {
3
+ BaseProvider,
4
+ type EnclaveOptions,
5
+ type PublicEncryptionProfile,
6
+ } from "@idos-network/utils/enclave";
7
+ import type { BaseProviderMethodArgs, BaseProviderMethodReturn } from "./helpers";
8
+
9
+ export interface IframeEnclaveOptions extends EnclaveOptions {
10
+ container: string;
11
+ url?: string;
12
+ throwOnUserCancelUnlock?: boolean;
13
+ }
2
14
 
3
- import type {
4
- DiscoverUserEncryptionPublicKeyResponse,
5
- EnclaveOptions,
6
- EnclaveProvider,
7
- StoredData,
8
- } from "./types";
15
+ export class IframeEnclave extends BaseProvider<IframeEnclaveOptions> {
16
+ private container: string;
17
+ private iframe: HTMLIFrameElement;
18
+ private hostUrl: URL;
9
19
 
10
- export class IframeEnclave implements EnclaveProvider {
11
- options: Omit<EnclaveOptions, "container" | "url">;
12
- container: string;
13
- iframe: HTMLIFrameElement;
14
- hostUrl: URL;
20
+ constructor(options: IframeEnclaveOptions) {
21
+ super(options);
15
22
 
16
- constructor(options: EnclaveOptions) {
17
- const { container, ...other } = options;
23
+ if (!this.options.container) {
24
+ throw new Error("container is required");
25
+ }
18
26
 
19
- this.container = container;
20
- this.options = other;
21
- this.hostUrl = new URL(other.url ?? "https://enclave.idos.network");
27
+ this.container = this.options.container;
28
+ this.hostUrl = new URL(this.options.url ?? "https://enclave.idos.network");
22
29
  this.iframe = document.createElement("iframe");
23
30
  this.iframe.id = "idos-enclave-iframe";
24
31
  }
25
32
 
33
+ /** @see parent method */
26
34
  async load(): Promise<void> {
27
- await this.loadEnclave();
28
- await this.requestToEnclave({ configure: this.options });
29
- }
30
-
31
- async reconfigure(options: Omit<EnclaveOptions, "container" | "url">): Promise<void> {
32
- Object.assign(this.options, options);
33
- await this.requestToEnclave({ configure: this.options });
34
- }
35
-
36
- async ready(userId: string, expectedUserEncryptionPublicKey?: string): Promise<Uint8Array> {
37
- let { encryptionPublicKey: userEncryptionPublicKey } = (await this.requestToEnclave({
38
- storage: {
39
- userId,
40
- expectedUserEncryptionPublicKey,
41
- },
42
- })) as StoredData;
43
-
44
- while (!userEncryptionPublicKey) {
45
- this.showEnclave();
46
- try {
47
- userEncryptionPublicKey = (await this.requestToEnclave({
48
- keys: {},
49
- })) as Uint8Array;
50
- } catch (e) {
51
- if (this.options.throwOnUserCancelUnlock) throw e;
52
- } finally {
53
- this.hideEnclave();
54
- }
55
- }
35
+ // First we have to create and load the iframe
36
+ await this.createAndLoadIframe();
37
+
38
+ // Load the enclave from the store (trigger load() method in the iframe)
39
+ await this.requestToEnclave("load");
40
+
41
+ // Pass current options
42
+ await this.reconfigure();
56
43
 
57
- return userEncryptionPublicKey;
44
+ // Bind the message listener to the iframe (in case iframe -> enclave comm)
45
+ await this.bindMessageListener();
58
46
  }
59
47
 
48
+ /** @override parent method to call iframe */
60
49
  async reset(): Promise<void> {
61
- this.requestToEnclave({ reset: {} });
50
+ this.requestToEnclave("reset");
62
51
  }
63
52
 
53
+ /** @override parent method to call iframe */
54
+ async reconfigure(options: Omit<IframeEnclaveOptions, "container" | "url"> = {}): Promise<void> {
55
+ super.reconfigure(options);
56
+ await this.requestToEnclave("reconfigure", this.options);
57
+ }
58
+
59
+ /** @override parent method to call iframe */
64
60
  async confirm(message: string): Promise<boolean> {
65
61
  this.showEnclave();
66
62
 
67
- return this.requestToEnclave({ confirm: { message } }).then((response) => {
63
+ try {
64
+ return await this.requestToEnclave("confirm", message);
65
+ } catch (error) {
66
+ console.error(error);
67
+ return false;
68
+ } finally {
68
69
  this.hideEnclave();
69
- return response as boolean;
70
- });
70
+ }
71
71
  }
72
72
 
73
+ /** @override parent method to call iframe */
74
+ async filterCredentials(
75
+ credentials: idOSCredential[],
76
+ privateFieldFilters: { pick: Record<string, unknown[]>; omit: Record<string, unknown[]> },
77
+ ): Promise<idOSCredential[]> {
78
+ return await this.requestToEnclave("filterCredentials", credentials, privateFieldFilters);
79
+ }
80
+
81
+ /** @override parent method to call iframe */
73
82
  async encrypt(
74
83
  message: Uint8Array,
75
84
  receiverPublicKey: Uint8Array,
76
85
  ): Promise<{ content: Uint8Array; encryptorPublicKey: Uint8Array }> {
77
- return this.requestToEnclave({
78
- encrypt: { message, receiverPublicKey },
79
- }) as Promise<{ content: Uint8Array; encryptorPublicKey: Uint8Array }>;
86
+ return this.requestToEnclave("encrypt", message, receiverPublicKey);
80
87
  }
81
88
 
82
- async decrypt(message: Uint8Array, senderPublicKey: Uint8Array): Promise<Uint8Array> {
83
- return this.requestToEnclave({
84
- decrypt: { fullMessage: message, senderPublicKey },
85
- }) as Promise<Uint8Array>;
89
+ /** @override parent method to call iframe */
90
+ async decrypt(
91
+ message: Uint8Array,
92
+ senderPublicKey: Uint8Array,
93
+ ): Promise<Uint8Array<ArrayBufferLike>> {
94
+ return this.requestToEnclave("decrypt", message, senderPublicKey);
86
95
  }
87
96
 
88
- filterCredentials(
89
- credentials: idOSCredential[],
90
- privateFieldFilters: { pick: Record<string, unknown[]>; omit: Record<string, unknown[]> },
91
- ): Promise<idOSCredential[]> {
92
- return this.requestToEnclave({
93
- filterCredentials: { credentials, privateFieldFilters },
94
- }) as Promise<idOSCredential[]>;
97
+ /** @override parent method to call iframe */
98
+ async backupUserEncryptionProfile(): Promise<void> {
99
+ this.showEnclave();
100
+
101
+ try {
102
+ await this.requestToEnclave("backupUserEncryptionProfile");
103
+ } catch (error) {
104
+ console.error(error);
105
+ } finally {
106
+ this.hideEnclave();
107
+ }
108
+ }
109
+
110
+ /** @override parent method to call iframe */
111
+ async ensureUserEncryptionProfile(): Promise<PublicEncryptionProfile> {
112
+ this.showEnclave();
113
+
114
+ try {
115
+ return await this.requestToEnclave("ensureUserEncryptionProfile");
116
+ } finally {
117
+ this.hideEnclave();
118
+ }
95
119
  }
96
120
 
97
- async loadEnclave(): Promise<void> {
98
- const container =
99
- document.querySelector(this.container) ||
100
- throwNewError(Error, `Can't find container with selector ${this.container}`);
121
+ private async createAndLoadIframe(): Promise<void> {
122
+ const container = document.querySelector(this.container);
123
+
124
+ if (!container) {
125
+ throw new Error(`Can't find container with selector ${this.container}`);
126
+ }
101
127
 
102
128
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#directives
103
129
  const permissionsPolicies = ["publickey-credentials-get", "storage-access"];
@@ -150,18 +176,20 @@ export class IframeEnclave implements EnclaveProvider {
150
176
  );
151
177
  }
152
178
 
153
- showEnclave(): void {
179
+ private showEnclave(): void {
154
180
  // biome-ignore lint/style/noNonNullAssertion: Make the explosion visible.
155
181
  this.iframe.parentElement!.classList.add("visible");
156
182
  }
157
183
 
158
- hideEnclave(): void {
184
+ private hideEnclave(): void {
159
185
  // biome-ignore lint/style/noNonNullAssertion: Make the explosion visible.
160
186
  this.iframe.parentElement!.classList.remove("visible");
161
187
  }
162
188
 
163
- // biome-ignore lint/suspicious/noExplicitAny: `any` is fine here. We will type it properly later.
164
- async requestToEnclave(request: any): Promise<any> {
189
+ private async requestToEnclave<TMethod extends keyof BaseProviderMethodArgs>(
190
+ method: TMethod,
191
+ ...args: BaseProviderMethodArgs[TMethod]
192
+ ): Promise<BaseProviderMethodReturn[TMethod]> {
165
193
  return new Promise((resolve, reject) => {
166
194
  const { port1, port2 } = new MessageChannel();
167
195
 
@@ -171,67 +199,28 @@ export class IframeEnclave implements EnclaveProvider {
171
199
  };
172
200
 
173
201
  // biome-ignore lint/style/noNonNullAssertion: Make the explosion visible.
174
- this.iframe.contentWindow!.postMessage(request, this.hostUrl.origin, [port2]);
202
+ this.iframe.contentWindow!.postMessage(
203
+ {
204
+ method,
205
+ data: args,
206
+ },
207
+ this.hostUrl.origin,
208
+ [port2],
209
+ );
175
210
  });
176
211
  }
177
212
 
178
- async backupPasswordOrSecret(): Promise<void> {
179
- const abortController = new AbortController();
180
- this.showEnclave();
181
-
182
- window.addEventListener(
183
- "message",
184
- async (event) => {
185
- if (event.data.type !== "idOS:store" || event.origin !== this.hostUrl.origin) return;
186
-
187
- let status = "";
188
-
189
- try {
190
- status = "success";
191
- this.hideEnclave();
192
- } catch (_) {
193
- status = "failure";
194
- this.hideEnclave();
195
- }
196
-
197
- event.ports[0].postMessage({
198
- result: {
199
- type: "idOS:store",
200
- status,
201
- },
202
- });
203
- event.ports[0].close();
204
- abortController.abort();
205
- },
206
- { signal: abortController.signal },
207
- );
208
-
209
- try {
210
- await this.requestToEnclave({
211
- backupPasswordOrSecret: {},
212
- });
213
- } catch (error) {
214
- console.error(error);
215
- } finally {
216
- this.hideEnclave();
217
- }
213
+ private bindMessageListener(): void {
214
+ window.addEventListener("message", this.onMessage.bind(this));
218
215
  }
219
216
 
220
- async discoverUserEncryptionPublicKey(
221
- userId: string,
222
- ): Promise<DiscoverUserEncryptionPublicKeyResponse> {
223
- if (this.options.mode !== "new")
224
- throw new Error("You can only call `discoverUserEncryptionPublicKey` when mode is `new`.");
217
+ private async onMessage(message: MessageEvent): Promise<void> {
218
+ if (message.data.type !== "idOS:signTypedData" || message.origin !== this.hostUrl.origin)
219
+ return;
225
220
 
226
- const userEncryptionPublicKey = await this.ready(userId);
221
+ const payload = message.data.payload;
222
+ const signature = await this.signTypedData(payload.domain, payload.types, payload.value);
227
223
 
228
- return {
229
- userId,
230
- userEncryptionPublicKey: base64Encode(userEncryptionPublicKey),
231
- };
224
+ await this.requestToEnclave("signTypedDataResponse", signature);
232
225
  }
233
226
  }
234
-
235
- function throwNewError(ErrorClass: ErrorConstructor, ...args: Parameters<ErrorConstructor>): never {
236
- throw new ErrorClass(...args);
237
- }