@welshare/react 0.3.0 → 0.4.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.
Files changed (50) hide show
  1. package/README.md +85 -44
  2. package/dist/esm/components/connect-button.d.ts.map +1 -1
  3. package/dist/esm/components/connect-button.js +1 -4
  4. package/dist/esm/components/welshare-logo.d.ts +1 -1
  5. package/dist/esm/hooks/use-binary-uploads.d.ts +59 -0
  6. package/dist/esm/hooks/use-binary-uploads.d.ts.map +1 -0
  7. package/dist/esm/hooks/use-binary-uploads.js +93 -0
  8. package/dist/esm/hooks/use-welshare.d.ts.map +1 -1
  9. package/dist/esm/hooks/use-welshare.js +11 -8
  10. package/dist/esm/index.d.ts +2 -1
  11. package/dist/esm/index.d.ts.map +1 -1
  12. package/dist/esm/index.js +2 -1
  13. package/dist/esm/lib/encryption.d.ts.map +1 -1
  14. package/dist/esm/lib/encryption.js +3 -13
  15. package/dist/esm/lib/uploads.d.ts.map +1 -1
  16. package/dist/esm/lib/uploads.js +1 -3
  17. package/dist/esm/types.d.ts.map +1 -1
  18. package/dist/esm/utils.d.ts.map +1 -1
  19. package/dist/node_modules/@welshare/react/.turbo/turbo-lint.log +14 -0
  20. package/dist/node_modules/@welshare/react/README.md +85 -44
  21. package/dist/node_modules/@welshare/react/dist/esm/components/connect-button.d.ts.map +1 -1
  22. package/dist/node_modules/@welshare/react/dist/esm/components/connect-button.js +1 -4
  23. package/dist/node_modules/@welshare/react/dist/esm/components/welshare-logo.d.ts +1 -1
  24. package/dist/node_modules/@welshare/react/dist/esm/hooks/use-binary-uploads.d.ts +59 -0
  25. package/dist/node_modules/@welshare/react/dist/esm/hooks/use-binary-uploads.d.ts.map +1 -0
  26. package/dist/node_modules/@welshare/react/dist/esm/hooks/use-binary-uploads.js +93 -0
  27. package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.d.ts.map +1 -1
  28. package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.js +11 -8
  29. package/dist/node_modules/@welshare/react/dist/esm/index.d.ts +2 -1
  30. package/dist/node_modules/@welshare/react/dist/esm/index.d.ts.map +1 -1
  31. package/dist/node_modules/@welshare/react/dist/esm/index.js +2 -1
  32. package/dist/node_modules/@welshare/react/dist/esm/lib/encryption.d.ts.map +1 -1
  33. package/dist/node_modules/@welshare/react/dist/esm/lib/encryption.js +3 -13
  34. package/dist/node_modules/@welshare/react/dist/esm/lib/uploads.d.ts.map +1 -1
  35. package/dist/node_modules/@welshare/react/dist/esm/lib/uploads.js +1 -3
  36. package/dist/node_modules/@welshare/react/dist/esm/types.d.ts.map +1 -1
  37. package/dist/node_modules/@welshare/react/dist/esm/utils.d.ts.map +1 -1
  38. package/dist/node_modules/@welshare/react/eslint.config.mjs +2 -2
  39. package/dist/node_modules/@welshare/react/package.json +1 -1
  40. package/dist/node_modules/@welshare/react/src/components/connect-button.tsx +1 -4
  41. package/dist/node_modules/@welshare/react/src/components/welshare-logo.tsx +1 -1
  42. package/dist/node_modules/@welshare/react/src/hooks/use-binary-uploads.ts +181 -0
  43. package/dist/node_modules/@welshare/react/src/hooks/use-welshare.ts +18 -10
  44. package/dist/node_modules/@welshare/react/src/index.ts +6 -1
  45. package/dist/node_modules/@welshare/react/src/lib/encryption.ts +3 -13
  46. package/dist/node_modules/@welshare/react/src/lib/uploads.ts +2 -4
  47. package/dist/node_modules/@welshare/react/src/types.ts +11 -10
  48. package/dist/node_modules/@welshare/react/src/utils.ts +1 -5
  49. package/dist/node_modules/@welshare/react/tsconfig.json +3 -12
  50. package/package.json +2 -2
@@ -0,0 +1,181 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { decrypt } from "../lib/encryption.js";
3
+ import { type EncryptionKey } from "../utils.js";
4
+ import type {
5
+ RequestUploadCredentialsPayload,
6
+ UploadCredentials,
7
+ } from "../types.js";
8
+ import {
9
+ Nillion,
10
+ WelshareApi,
11
+ resolveEnvironment,
12
+ type WelshareApiEnvironment,
13
+ type WelshareEnvironmentName,
14
+ } from "@welshare/sdk";
15
+
16
+ export interface UseBinaryUploadsOptions {
17
+ /**
18
+ * The user's session keypair for authentication.
19
+ */
20
+ keypair: Nillion.Keypair | null | undefined;
21
+ /**
22
+ * The Welshare environment to use for API calls.
23
+ */
24
+ environment: WelshareApiEnvironment | WelshareEnvironmentName;
25
+ }
26
+
27
+ export interface UseBinaryUploadsResult {
28
+ /**
29
+ * Request upload credentials (presigned URL) for uploading an encrypted file.
30
+ */
31
+ createUploadCredentials: (
32
+ payload: RequestUploadCredentialsPayload
33
+ ) => Promise<UploadCredentials>;
34
+
35
+ /**
36
+ * Download and decrypt a file by its document ID.
37
+ * Only works for files owned by the current user.
38
+ */
39
+ downloadAndDecryptFile: (documentId: string) => Promise<File | undefined>;
40
+ /**
41
+ * Whether an operation is currently running.
42
+ */
43
+ isRunning: boolean;
44
+ /**
45
+ * Error message if the last operation failed.
46
+ */
47
+ error: string | null;
48
+ }
49
+
50
+ /**
51
+ * Hook for managing binary file uploads and downloads with Welshare.
52
+ *
53
+ * This hook provides functionality to:
54
+ * - Request presigned URLs for uploading encrypted files to S3
55
+ * - Download and decrypt files from Nillion storage
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * const { createUploadCredentials, downloadAndDecryptFile, isRunning, error } = useBinaryUploads({
60
+ * keypair: sessionKeyPair,
61
+ * environment: 'production',
62
+ * });
63
+ *
64
+ * // Upload flow: get credentials, encrypt, upload to S3, then submit metadata via submitBinaryData
65
+ * const credentials = await createUploadCredentials({
66
+ * applicationId: 'my-app',
67
+ * reference: 'user-photo',
68
+ * fileName: 'photo.jpg',
69
+ * fileType: 'image/jpeg',
70
+ * });
71
+ *
72
+ * // Download and decrypt
73
+ * const file = await downloadAndDecryptFile(documentId);
74
+ * ```
75
+ */
76
+ export const useBinaryUploads = (
77
+ options: UseBinaryUploadsOptions
78
+ ): UseBinaryUploadsResult => {
79
+ const [isRunning, setIsRunning] = useState(false);
80
+ const [error, setError] = useState<string | null>(null);
81
+ const mountedRef = useRef(true);
82
+
83
+ useEffect(() => {
84
+ mountedRef.current = true;
85
+ return () => {
86
+ mountedRef.current = false;
87
+ };
88
+ }, []);
89
+
90
+ const resolvedEnvironment = useMemo(
91
+ () => resolveEnvironment(options.environment),
92
+ [options.environment]
93
+ );
94
+ const { keypair } = options;
95
+
96
+ const createUploadCredentials = useCallback(
97
+ async (
98
+ payload: RequestUploadCredentialsPayload
99
+ ): Promise<UploadCredentials> => {
100
+ if (!keypair) {
101
+ throw new Error("No keypair available");
102
+ }
103
+
104
+ const { reference, fileName, fileType } = payload;
105
+
106
+ const { presignedUrl, uploadKey } =
107
+ await WelshareApi.fetchS3WriteDelegation(
108
+ keypair,
109
+ { reference, fileName, fileType },
110
+ resolvedEnvironment
111
+ );
112
+
113
+ return { presignedUrl, uploadKey };
114
+ },
115
+ [keypair, resolvedEnvironment]
116
+ );
117
+
118
+ /**
119
+ * Downloads and decrypts a file by its document ID from Nillion.
120
+ */
121
+ const downloadAndDecryptFile = useCallback(
122
+ async (documentId: string): Promise<File | undefined> => {
123
+ if (!keypair) {
124
+ throw new Error("No keypair available");
125
+ }
126
+
127
+ try {
128
+ setIsRunning(true);
129
+ setError(null);
130
+
131
+ const { binaryFile, data: downloadResponse } =
132
+ await WelshareApi.fetchBinaryData(
133
+ keypair,
134
+ resolvedEnvironment,
135
+ documentId
136
+ );
137
+
138
+ const encodedEncryptionKey: EncryptionKey = JSON.parse(
139
+ binaryFile.encryption_key
140
+ );
141
+
142
+ const decryptedData = await decrypt(
143
+ await downloadResponse,
144
+ encodedEncryptionKey
145
+ );
146
+
147
+ if (!decryptedData) {
148
+ throw new Error("Failed to decrypt file (received null)");
149
+ }
150
+
151
+ const decryptedFile = new File([decryptedData], binaryFile.file_name, {
152
+ type: binaryFile.file_type,
153
+ });
154
+
155
+ return decryptedFile;
156
+ } catch (err) {
157
+ console.error("Error during file download/decryption:", err);
158
+ const errorMessage =
159
+ err instanceof Error
160
+ ? err.message
161
+ : "Failed to download/decrypt file";
162
+ if (mountedRef.current) {
163
+ setError(errorMessage);
164
+ }
165
+ return undefined;
166
+ } finally {
167
+ if (mountedRef.current) {
168
+ setIsRunning(false);
169
+ }
170
+ }
171
+ },
172
+ [keypair, resolvedEnvironment]
173
+ );
174
+
175
+ return {
176
+ createUploadCredentials,
177
+ downloadAndDecryptFile,
178
+ isRunning,
179
+ error,
180
+ };
181
+ };
@@ -8,7 +8,10 @@ import {
8
8
  UploadCredentials,
9
9
  WelshareConnectionOptions,
10
10
  } from "@/types.js";
11
- import { getBaseUrl, WELSHARE_API_ENVIRONMENT } from "@welshare/sdk/environment";
11
+ import {
12
+ getBaseUrl,
13
+ WELSHARE_API_ENVIRONMENT,
14
+ } from "@welshare/sdk/environment";
12
15
  import { useEffect, useRef, useState } from "react";
13
16
  import { encryptAndUploadFile } from "../lib/uploads.js";
14
17
 
@@ -28,7 +31,7 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
28
31
  // Resolve the base URL from environment or apiBaseUrl
29
32
  const resolvedBaseUrl = props.environment
30
33
  ? getBaseUrl(props.environment)
31
- : props.apiBaseUrl ?? getBaseUrl(WELSHARE_API_ENVIRONMENT.production);
34
+ : (props.apiBaseUrl ?? getBaseUrl(WELSHARE_API_ENVIRONMENT.production));
32
35
 
33
36
  const options: WelshareConnectionOptions = {
34
37
  ...props,
@@ -187,7 +190,13 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
187
190
  return () => {
188
191
  window.removeEventListener("message", handleMessage);
189
192
  };
190
- }, [WELSHARE_WALLET_URL, dialogWindow, messageIdCounter, options.applicationId, options.callbacks]);
193
+ }, [
194
+ WELSHARE_WALLET_URL,
195
+ dialogWindow,
196
+ messageIdCounter,
197
+ options.applicationId,
198
+ options.callbacks,
199
+ ]);
191
200
 
192
201
  /**
193
202
  * Starts a file upload and returns a promise that resolves with the uploaded file URL
@@ -225,10 +234,7 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
225
234
  const message: DialogMessage = {
226
235
  type: "REQUEST_UPLOAD_CREDENTIALS",
227
236
  id: String(messageIdCounter),
228
- payload: {
229
- ...payload,
230
- applicationId: options.applicationId,
231
- },
237
+ payload: { ...payload, applicationId: options.applicationId },
232
238
  };
233
239
 
234
240
  dialogWindow.postMessage(message, WELSHARE_WALLET_URL);
@@ -282,10 +288,12 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
282
288
  if (options.interpolateSocials) {
283
289
  const socialEntries = Object.entries(options.interpolateSocials)
284
290
  .filter(([_, value]) => value !== undefined && value !== null)
285
- .map(([key, value]) => `social.${key}=${encodeURIComponent(String(value))}`);
286
-
291
+ .map(
292
+ ([key, value]) => `social.${key}=${encodeURIComponent(String(value))}`
293
+ );
294
+
287
295
  if (socialEntries.length > 0) {
288
- socialParams = `&${socialEntries.join('&')}`;
296
+ socialParams = `&${socialEntries.join("&")}`;
289
297
  }
290
298
  }
291
299
 
@@ -4,6 +4,11 @@ export { WelshareLogo } from "./components/welshare-logo.js";
4
4
 
5
5
  // ---- Hooks ----
6
6
  export { useWelshare } from "./hooks/use-welshare.js";
7
+ export {
8
+ useBinaryUploads,
9
+ type UseBinaryUploadsOptions,
10
+ type UseBinaryUploadsResult,
11
+ } from "./hooks/use-binary-uploads.js";
7
12
 
8
13
  // ---- Environment (re-exported from @welshare/sdk) ----
9
14
  export {
@@ -20,7 +25,7 @@ export {
20
25
  decrypt,
21
26
  encodeEncryptionKey,
22
27
  encryptFile,
23
- generateRandomAESKey
28
+ generateRandomAESKey,
24
29
  } from "./lib/encryption.js";
25
30
  export { decodeEncryptionKey, type EncryptionKey } from "./utils.js";
26
31
 
@@ -24,10 +24,7 @@ export const encryptFile = async (
24
24
 
25
25
  // Encrypt the file data
26
26
  const encryptedData = await window.crypto.subtle.encrypt(
27
- {
28
- name: ALGORITHM,
29
- iv: iv,
30
- },
27
+ { name: ALGORITHM, iv: iv },
31
28
  key,
32
29
  fileData
33
30
  );
@@ -49,11 +46,7 @@ export const encodeEncryptionKey = async (
49
46
  .map((b) => b.toString(16).padStart(2, "0"))
50
47
  .join("");
51
48
 
52
- return {
53
- algorithm: ALGORITHM,
54
- key: keyHex,
55
- iv: ivHex,
56
- };
49
+ return { algorithm: ALGORITHM, key: keyHex, iv: ivHex };
57
50
  };
58
51
 
59
52
  // Helper function to decrypt a file using encoded encryption key
@@ -73,10 +66,7 @@ export const decrypt = async (
73
66
  );
74
67
 
75
68
  const decryptedData = await window.crypto.subtle.decrypt(
76
- {
77
- name: ALGORITHM,
78
- iv: iv as BufferSource,
79
- },
69
+ { name: ALGORITHM, iv: iv as BufferSource },
80
70
  key,
81
71
  encryptedData
82
72
  );
@@ -16,9 +16,7 @@ export const encryptAndUploadFile = async (
16
16
  const uploadResponse = await fetch(presignedUrl, {
17
17
  method: "PUT",
18
18
  body: encryptedData,
19
- headers: {
20
- "Content-Type": file.type,
21
- },
19
+ headers: { "Content-Type": file.type },
22
20
  });
23
21
 
24
22
  if (!uploadResponse.ok) {
@@ -37,4 +35,4 @@ export const browserDownload = (decryptedFile: File) => {
37
35
  a.click();
38
36
  document.body.removeChild(a);
39
37
  window.URL.revokeObjectURL(downloadUrl);
40
- }
38
+ };
@@ -1,5 +1,8 @@
1
1
  import { EncryptionKey } from "./utils.js";
2
- import type { WelshareApiEnvironment, WelshareEnvironmentName } from "@welshare/sdk/environment";
2
+ import type {
3
+ WelshareApiEnvironment,
4
+ WelshareEnvironmentName,
5
+ } from "@welshare/sdk/environment";
3
6
 
4
7
  export interface DialogMessage {
5
8
  type: string;
@@ -10,7 +13,7 @@ export interface DialogMessage {
10
13
  /**
11
14
  * a welshare schema type uid
12
15
  */
13
- export type SubmissionSchemaId = string
16
+ export type SubmissionSchemaId = string;
14
17
 
15
18
  export interface SubmissionPayload<T> {
16
19
  applicationId: string;
@@ -19,10 +22,7 @@ export interface SubmissionPayload<T> {
19
22
  submission: T;
20
23
  }
21
24
 
22
- export type UploadCredentials = {
23
- presignedUrl: string;
24
- uploadKey: string;
25
- }
25
+ export type UploadCredentials = { presignedUrl: string; uploadKey: string };
26
26
 
27
27
  export interface RequestUploadCredentialsPayload {
28
28
  timestamp?: Date;
@@ -32,7 +32,8 @@ export interface RequestUploadCredentialsPayload {
32
32
  fileType: string;
33
33
  }
34
34
 
35
- export interface BinaryFileSubmissionPayload extends RequestUploadCredentialsPayload {
35
+ export interface BinaryFileSubmissionPayload
36
+ extends RequestUploadCredentialsPayload {
36
37
  encryptionKey: EncryptionKey;
37
38
  /// in bytes
38
39
  fileSize: number;
@@ -50,8 +51,8 @@ export interface RunningFileUpload extends RequestUploadCredentialsPayload {
50
51
  }
51
52
 
52
53
  export interface DataSubmissionDialogMessage extends DialogMessage {
53
- payload:
54
- | SubmissionPayload<unknown>
54
+ payload:
55
+ | SubmissionPayload<unknown>
55
56
  | BinaryFileSubmissionPayload
56
57
  | RequestUploadCredentialsPayload;
57
58
  }
@@ -62,7 +63,7 @@ export interface WelshareConnectionOptions {
62
63
  emailAddress?: string;
63
64
  privy?: string;
64
65
  twitter?: string;
65
- }
66
+ };
66
67
  /**
67
68
  * The Welshare environment to connect to.
68
69
  * Can be an environment name ('production', 'staging', etc.) or a full environment object.
@@ -1,11 +1,7 @@
1
1
  export const ALGORITHM = "AES-GCM";
2
2
  export type Algorithm = "AES-GCM";
3
3
 
4
- export type EncryptionKey = {
5
- algorithm: Algorithm;
6
- key: string;
7
- iv: string;
8
- };
4
+ export type EncryptionKey = { algorithm: Algorithm; key: string; iv: string };
9
5
 
10
6
  export const decodeEncryptionKey = (
11
7
  encryptionKey: EncryptionKey
@@ -2,20 +2,11 @@
2
2
  "extends": "@workspace/typescript-config/react-library.json",
3
3
  "compilerOptions": {
4
4
  "baseUrl": ".",
5
- "paths": {
6
- "@/*": ["./src/*"],
7
- "@/types/*": ["./types/*"],
8
- },
9
- "plugins": [
10
- ],
5
+ "paths": { "@/*": ["./src/*"], "@/types/*": ["./types/*"] },
6
+ "plugins": [],
11
7
  "allowJs": true,
12
8
  "jsx": "react-jsx"
13
9
  },
14
- "include": [
15
- "next-env.d.ts",
16
- "next.config.ts",
17
- "**/*.ts",
18
- "**/*.tsx",
19
- ],
10
+ "include": ["next-env.d.ts", "next.config.ts", "**/*.ts", "**/*.tsx"],
20
11
  "exclude": ["node_modules"]
21
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@welshare/react",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "React library for integrating with Welshare's sovereign data sharing platform",
5
5
  "keywords": [
6
6
  "react",
@@ -23,7 +23,7 @@
23
23
  "homepage": "https://welshare.health",
24
24
  "type": "module",
25
25
  "dependencies": {
26
- "@welshare/sdk": "0.1.4"
26
+ "@welshare/sdk": "0.2.0"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^19",