@welshare/react 0.0.1-alpha.2 → 0.2.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/README.md +44 -0
- package/dist/esm/hooks/use-welshare.d.ts +5 -0
- package/dist/esm/hooks/use-welshare.d.ts.map +1 -1
- package/dist/esm/hooks/use-welshare.js +125 -7
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/lib/encryption.d.ts +19 -0
- package/dist/esm/lib/encryption.d.ts.map +1 -0
- package/dist/esm/lib/encryption.js +61 -0
- package/dist/esm/lib/uploads.d.ts +3 -0
- package/dist/esm/lib/uploads.d.ts.map +1 -0
- package/dist/esm/lib/uploads.js +17 -0
- package/dist/esm/types.d.ts +35 -3
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/node_modules/@welshare/react/.DS_Store +0 -0
- package/dist/node_modules/@welshare/react/README.md +44 -0
- package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.d.ts +5 -0
- package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.d.ts.map +1 -1
- package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.js +125 -7
- package/dist/node_modules/@welshare/react/dist/esm/index.d.ts +3 -0
- package/dist/node_modules/@welshare/react/dist/esm/index.d.ts.map +1 -1
- package/dist/node_modules/@welshare/react/dist/esm/index.js +4 -1
- package/dist/node_modules/@welshare/react/dist/esm/lib/encryption.d.ts +19 -0
- package/dist/node_modules/@welshare/react/dist/esm/lib/encryption.d.ts.map +1 -0
- package/dist/node_modules/@welshare/react/dist/esm/lib/encryption.js +61 -0
- package/dist/node_modules/@welshare/react/dist/esm/lib/uploads.d.ts +3 -0
- package/dist/node_modules/@welshare/react/dist/esm/lib/uploads.d.ts.map +1 -0
- package/dist/node_modules/@welshare/react/dist/esm/lib/uploads.js +17 -0
- package/dist/node_modules/@welshare/react/dist/esm/types.d.ts +35 -3
- package/dist/node_modules/@welshare/react/dist/esm/types.d.ts.map +1 -1
- package/dist/node_modules/@welshare/react/package.json +2 -2
- package/dist/node_modules/@welshare/react/src/hooks/use-welshare.ts +167 -7
- package/dist/node_modules/@welshare/react/src/index.ts +13 -5
- package/dist/node_modules/@welshare/react/src/lib/encryption.ts +110 -0
- package/dist/node_modules/@welshare/react/src/lib/uploads.ts +29 -0
- package/dist/node_modules/@welshare/react/src/types.ts +41 -4
- package/package.json +2 -2
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BinaryFileSubmissionPayload,
|
|
2
3
|
DialogMessage,
|
|
4
|
+
RequestUploadCredentialsPayload,
|
|
5
|
+
RunningFileUpload,
|
|
3
6
|
SubmissionPayload,
|
|
4
7
|
SubmissionSchemaId,
|
|
8
|
+
UploadCredentials,
|
|
5
9
|
WelshareConnectionOptions,
|
|
6
10
|
} from "@/types.js";
|
|
7
|
-
import { useEffect, useState } from "react";
|
|
11
|
+
import { useEffect, useRef, useState } from "react";
|
|
12
|
+
import { encryptAndUploadFile } from "../lib/uploads.js";
|
|
8
13
|
|
|
9
14
|
export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
10
15
|
const [storageKey, setStorageKey] = useState<string>();
|
|
@@ -12,9 +17,14 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
12
17
|
const [dialogWindow, setDialogWindow] = useState<Window | null>(null);
|
|
13
18
|
const [messageIdCounter, setMessageIdCounter] = useState(0);
|
|
14
19
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
20
|
+
const [uploadState, setUploadState] = useState<
|
|
21
|
+
"preparing" | "uploading" | "anchoring" | "finished" | "error"
|
|
22
|
+
>();
|
|
23
|
+
|
|
24
|
+
// (claude opus) Use ref to control current upload in effect without triggering re-renders
|
|
25
|
+
const currentUploadRef = useRef<RunningFileUpload>(null);
|
|
15
26
|
|
|
16
27
|
const options: WelshareConnectionOptions = {
|
|
17
|
-
environment: "development",
|
|
18
28
|
apiBaseUrl: "https://wallet.welshare.app",
|
|
19
29
|
...props,
|
|
20
30
|
};
|
|
@@ -22,7 +32,7 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
22
32
|
const WELSHARE_WALLET_URL = `${options.apiBaseUrl}/wallet-external`;
|
|
23
33
|
|
|
24
34
|
useEffect(() => {
|
|
25
|
-
const handleMessage = (event: MessageEvent<DialogMessage>) => {
|
|
35
|
+
const handleMessage = async (event: MessageEvent<DialogMessage>) => {
|
|
26
36
|
// Verify origin for security
|
|
27
37
|
if (event.origin !== new URL(WELSHARE_WALLET_URL).origin) {
|
|
28
38
|
return;
|
|
@@ -35,6 +45,14 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
35
45
|
errorMessage = message.payload.error || "An unknown error occurred";
|
|
36
46
|
console.error("Welshare Wallet sent an error:", errorMessage);
|
|
37
47
|
setIsSubmitting(false);
|
|
48
|
+
|
|
49
|
+
// Reject the promise if there's an ongoing upload
|
|
50
|
+
if (currentUploadRef.current?.uploadPromise) {
|
|
51
|
+
currentUploadRef.current.uploadPromise.reject(
|
|
52
|
+
new Error(errorMessage)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
options.callbacks.onError?.(errorMessage);
|
|
39
57
|
break;
|
|
40
58
|
case "DIALOG_READY":
|
|
@@ -61,6 +79,14 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
61
79
|
console.debug("Welshare Wallet Dialog is closing");
|
|
62
80
|
setStorageKey(undefined);
|
|
63
81
|
setIsDialogOpen(false);
|
|
82
|
+
|
|
83
|
+
// Reject any pending upload promises
|
|
84
|
+
if (currentUploadRef.current?.uploadPromise) {
|
|
85
|
+
currentUploadRef.current.uploadPromise.reject(
|
|
86
|
+
new Error("Dialog closed before upload completed")
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
options.callbacks.onDialogClosing?.();
|
|
65
91
|
break;
|
|
66
92
|
|
|
@@ -69,6 +95,79 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
69
95
|
setIsSubmitting(false);
|
|
70
96
|
options.callbacks.onUploaded?.(message.payload);
|
|
71
97
|
break;
|
|
98
|
+
|
|
99
|
+
//todo: make this work for batches, too
|
|
100
|
+
case "UPLOAD_CREDENTIALS_CREATED":
|
|
101
|
+
const credentials: UploadCredentials = message.payload;
|
|
102
|
+
console.debug("upload credentials created", credentials);
|
|
103
|
+
|
|
104
|
+
if (!currentUploadRef.current) {
|
|
105
|
+
throw new Error("No upload in progress");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!dialogWindow) {
|
|
109
|
+
currentUploadRef.current.uploadPromise.reject(
|
|
110
|
+
new Error("(UPLOAD_CREDENTIALS_CREATED) Dialog window not open")
|
|
111
|
+
);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
setUploadState("uploading");
|
|
116
|
+
const encodedEncryptionKey = await encryptAndUploadFile(
|
|
117
|
+
currentUploadRef.current.file,
|
|
118
|
+
credentials.presignedUrl
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
setUploadState("anchoring");
|
|
122
|
+
const binarySubmissionPayload: BinaryFileSubmissionPayload = {
|
|
123
|
+
timestamp: currentUploadRef.current.timestamp,
|
|
124
|
+
applicationId: options.applicationId,
|
|
125
|
+
reference: currentUploadRef.current.reference,
|
|
126
|
+
fileName: currentUploadRef.current.fileName,
|
|
127
|
+
fileType: currentUploadRef.current.fileType,
|
|
128
|
+
encryptionKey: encodedEncryptionKey,
|
|
129
|
+
fileSize: currentUploadRef.current.file.size,
|
|
130
|
+
url: `welshare://${credentials.uploadKey}`,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
//write to Nillion
|
|
134
|
+
const message: DialogMessage = {
|
|
135
|
+
type: "SUBMIT_BINARY_DATA",
|
|
136
|
+
id: String(messageIdCounter),
|
|
137
|
+
payload: binarySubmissionPayload,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
dialogWindow.postMessage(message, WELSHARE_WALLET_URL);
|
|
141
|
+
setMessageIdCounter((prev) => prev + 1);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("error uploading file", error);
|
|
144
|
+
// Update status to error
|
|
145
|
+
currentUploadRef.current.uploadPromise.reject(error as Error);
|
|
146
|
+
setUploadState("error");
|
|
147
|
+
|
|
148
|
+
// Call error callback
|
|
149
|
+
const errorMsg =
|
|
150
|
+
error instanceof Error ? error.message : "Upload failed";
|
|
151
|
+
options.callbacks.onError?.(errorMsg);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
//{insertetUid, errors}
|
|
155
|
+
case "BINARY_DATA_SUBMITTED":
|
|
156
|
+
if (!currentUploadRef.current) {
|
|
157
|
+
throw new Error("No upload in progress");
|
|
158
|
+
}
|
|
159
|
+
currentUploadRef.current.uploadPromise.resolve({
|
|
160
|
+
url: message.payload.url,
|
|
161
|
+
binaryFileUid: message.payload.insertedUid,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
setUploadState("finished");
|
|
165
|
+
options.callbacks.onFileUploaded?.(
|
|
166
|
+
message.payload.insertedUid,
|
|
167
|
+
message.payload.url
|
|
168
|
+
);
|
|
169
|
+
break;
|
|
170
|
+
|
|
72
171
|
default:
|
|
73
172
|
console.log(
|
|
74
173
|
"Received unexpected message from Welshare Wallet:",
|
|
@@ -82,7 +181,54 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
82
181
|
return () => {
|
|
83
182
|
window.removeEventListener("message", handleMessage);
|
|
84
183
|
};
|
|
85
|
-
}, [WELSHARE_WALLET_URL, options.callbacks]);
|
|
184
|
+
}, [WELSHARE_WALLET_URL, dialogWindow, messageIdCounter, options.applicationId, options.callbacks]);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Starts a file upload and returns a promise that resolves with the uploaded file URL
|
|
188
|
+
* @param file The file to upload
|
|
189
|
+
* @param reference string A reference identifier for the upload
|
|
190
|
+
* @returns Promise<{ url: string; binaryFileUid: string }> url usually starts with welshare:// and resolves to an encrypted storage location
|
|
191
|
+
*/
|
|
192
|
+
const uploadFile = (
|
|
193
|
+
file: File,
|
|
194
|
+
reference: string
|
|
195
|
+
): Promise<{ url: string; binaryFileUid: string }> => {
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
if (!dialogWindow) {
|
|
198
|
+
reject(new Error("(uploadFile) Dialog window not open"));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const payload: RequestUploadCredentialsPayload = {
|
|
203
|
+
timestamp: new Date(),
|
|
204
|
+
applicationId: options.applicationId,
|
|
205
|
+
reference: reference,
|
|
206
|
+
fileName: file.name,
|
|
207
|
+
fileType: file.type,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
setUploadState("preparing");
|
|
211
|
+
// (claude opus) Store the upload with the file and promise handlers on a ref
|
|
212
|
+
currentUploadRef.current = {
|
|
213
|
+
...payload,
|
|
214
|
+
file,
|
|
215
|
+
reference,
|
|
216
|
+
uploadPromise: { resolve, reject },
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const message: DialogMessage = {
|
|
220
|
+
type: "REQUEST_UPLOAD_CREDENTIALS",
|
|
221
|
+
id: String(messageIdCounter),
|
|
222
|
+
payload: {
|
|
223
|
+
...payload,
|
|
224
|
+
applicationId: options.applicationId,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
dialogWindow.postMessage(message, WELSHARE_WALLET_URL);
|
|
229
|
+
setMessageIdCounter((prev) => prev + 1);
|
|
230
|
+
});
|
|
231
|
+
};
|
|
86
232
|
|
|
87
233
|
/**
|
|
88
234
|
* @param schemaType a welshare schema type uid
|
|
@@ -93,7 +239,7 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
93
239
|
submission: SubmissionPayload<T>
|
|
94
240
|
) => {
|
|
95
241
|
if (!dialogWindow) {
|
|
96
|
-
throw new Error("Dialog window not open");
|
|
242
|
+
throw new Error("(submitData) Dialog window not open");
|
|
97
243
|
}
|
|
98
244
|
|
|
99
245
|
const submissionPayload = {
|
|
@@ -122,12 +268,24 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
122
268
|
|
|
123
269
|
const openWallet = () => {
|
|
124
270
|
const width = 800;
|
|
125
|
-
const height =
|
|
271
|
+
const height = 700;
|
|
126
272
|
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
127
273
|
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
128
274
|
|
|
275
|
+
let socialParams = "";
|
|
276
|
+
if (options.interpolateSocials) {
|
|
277
|
+
const socialEntries = Object.entries(options.interpolateSocials)
|
|
278
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
279
|
+
.map(([key, value]) => `social.${key}=${encodeURIComponent(String(value))}`);
|
|
280
|
+
|
|
281
|
+
if (socialEntries.length > 0) {
|
|
282
|
+
socialParams = `&${socialEntries.join('&')}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const walletUrl = `${WELSHARE_WALLET_URL}?applicationId=${options.applicationId}${socialParams}`;
|
|
129
287
|
const newWindow = window.open(
|
|
130
|
-
|
|
288
|
+
walletUrl,
|
|
131
289
|
"Welshare Wallet",
|
|
132
290
|
`width=${width},height=${height},left=${left},top=${top}`
|
|
133
291
|
);
|
|
@@ -142,7 +300,9 @@ export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
|
142
300
|
storageKey,
|
|
143
301
|
openWallet,
|
|
144
302
|
isDialogOpen,
|
|
303
|
+
uploadFile,
|
|
145
304
|
submitData,
|
|
305
|
+
uploadState,
|
|
146
306
|
isSubmitting,
|
|
147
307
|
};
|
|
148
308
|
};
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
//import { QuestionnaireResponseSchema, ReflexSubmissionSchema } from "@welshare/sdk";
|
|
3
3
|
export { ConnectWelshareButton } from "./components/connect-button.js";
|
|
4
4
|
// ---- hooks ----
|
|
5
|
-
export {
|
|
6
|
-
useWelshare
|
|
7
|
-
} from "./hooks/use-welshare.js";
|
|
5
|
+
export { useWelshare } from "./hooks/use-welshare.js";
|
|
8
6
|
|
|
7
|
+
export {
|
|
8
|
+
decodeEncryptionKey,
|
|
9
|
+
decrypt,
|
|
10
|
+
encodeEncryptionKey,
|
|
11
|
+
encryptFile,
|
|
12
|
+
generateRandomAESKey,
|
|
13
|
+
type EncryptionKey
|
|
14
|
+
} from "./lib/encryption.js";
|
|
15
|
+
export { encryptAndUploadFile } from "./lib/uploads.js";
|
|
9
16
|
//todo: import them from the SDK or a dedicated SDK constants export
|
|
10
17
|
export const Schemas = {
|
|
11
18
|
QuestionnaireResponse: "b14b538f-7de3-4767-ad77-464d755d78bd", //QuestionnaireResponseSchema.schemaUid,
|
|
12
|
-
ReflexSubmission: "f5cf2d8a-1f78-4f21-b4bd-082e983b830c" //ReflexSubmissionSchema.schemaUid,
|
|
13
|
-
|
|
19
|
+
ReflexSubmission: "f5cf2d8a-1f78-4f21-b4bd-082e983b830c", //ReflexSubmissionSchema.schemaUid,
|
|
20
|
+
BinaryFile: "9d696baf-483f-4cc0-b748-23a22c1705f5" //BinaryFilesSchema.schemaUid,
|
|
21
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export const ALGORITHM = "AES-GCM";
|
|
2
|
+
export type Algorithm = "AES-GCM";
|
|
3
|
+
|
|
4
|
+
export const generateRandomAESKey = async (): Promise<CryptoKey> => {
|
|
5
|
+
// Generate a 256-bit AES-GCM key for file encryption
|
|
6
|
+
return window.crypto.subtle.generateKey(
|
|
7
|
+
{
|
|
8
|
+
name: ALGORITHM,
|
|
9
|
+
length: 256, // 256-bit key
|
|
10
|
+
},
|
|
11
|
+
true, // Key is extractable (needed for storage/transmission)
|
|
12
|
+
["encrypt", "decrypt"] // Key usage
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/// also Generates random IV (12 bytes for AES-GCM)
|
|
17
|
+
/// @return {arraybuffer ciphertext, uint8array iv}
|
|
18
|
+
export const encryptFile = async (
|
|
19
|
+
file: File,
|
|
20
|
+
key: CryptoKey
|
|
21
|
+
): Promise<{ encryptedData: ArrayBuffer; iv: Uint8Array }> => {
|
|
22
|
+
// Read file as ArrayBuffer
|
|
23
|
+
const fileData = await file.arrayBuffer();
|
|
24
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
25
|
+
|
|
26
|
+
// Encrypt the file data
|
|
27
|
+
const encryptedData = await window.crypto.subtle.encrypt(
|
|
28
|
+
{
|
|
29
|
+
name: ALGORITHM,
|
|
30
|
+
iv: iv,
|
|
31
|
+
},
|
|
32
|
+
key,
|
|
33
|
+
fileData
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return { encryptedData, iv };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type EncryptionKey = {
|
|
40
|
+
algorithm: Algorithm;
|
|
41
|
+
key: string;
|
|
42
|
+
iv: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const encodeEncryptionKey = async (
|
|
46
|
+
key: CryptoKey,
|
|
47
|
+
iv: Uint8Array
|
|
48
|
+
): Promise<EncryptionKey> => {
|
|
49
|
+
// Export the key as raw bytes
|
|
50
|
+
const exportedKey = await window.crypto.subtle.exportKey("raw", key);
|
|
51
|
+
const keyHex = Array.from(new Uint8Array(exportedKey))
|
|
52
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
53
|
+
.join("");
|
|
54
|
+
|
|
55
|
+
const ivHex = Array.from(iv)
|
|
56
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
57
|
+
.join("");
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
algorithm: ALGORITHM,
|
|
61
|
+
key: keyHex,
|
|
62
|
+
iv: ivHex,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const decodeEncryptionKey = (
|
|
67
|
+
encryptionKey: EncryptionKey
|
|
68
|
+
): { key: Uint8Array<ArrayBuffer>; iv: Uint8Array<ArrayBuffer> } => {
|
|
69
|
+
const keyBytes = new Uint8Array(
|
|
70
|
+
encryptionKey.key
|
|
71
|
+
.match(/.{1,2}/g)!
|
|
72
|
+
.map((byte: string) => parseInt(byte, 16))
|
|
73
|
+
);
|
|
74
|
+
const ivBytes = new Uint8Array(
|
|
75
|
+
encryptionKey.iv.match(/.{1,2}/g)!.map((byte: string) => parseInt(byte, 16))
|
|
76
|
+
);
|
|
77
|
+
return { key: keyBytes, iv: ivBytes };
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Helper function to decrypt a file using encoded encryption key
|
|
81
|
+
export const decrypt = async (
|
|
82
|
+
encryptedData: ArrayBuffer,
|
|
83
|
+
encryptionKey: EncryptionKey
|
|
84
|
+
): Promise<ArrayBuffer | null> => {
|
|
85
|
+
try {
|
|
86
|
+
const { key: keyBytes, iv } = decodeEncryptionKey(encryptionKey);
|
|
87
|
+
|
|
88
|
+
const key = await window.crypto.subtle.importKey(
|
|
89
|
+
"raw",
|
|
90
|
+
keyBytes,
|
|
91
|
+
{ name: ALGORITHM },
|
|
92
|
+
false,
|
|
93
|
+
["decrypt"]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const decryptedData = await window.crypto.subtle.decrypt(
|
|
97
|
+
{
|
|
98
|
+
name: ALGORITHM,
|
|
99
|
+
iv,
|
|
100
|
+
},
|
|
101
|
+
key,
|
|
102
|
+
encryptedData
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return decryptedData;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error("Failed to decrypt file:", error);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeEncryptionKey,
|
|
3
|
+
encryptFile,
|
|
4
|
+
EncryptionKey,
|
|
5
|
+
generateRandomAESKey,
|
|
6
|
+
} from "./encryption.js";
|
|
7
|
+
|
|
8
|
+
export const encryptAndUploadFile = async (
|
|
9
|
+
file: File,
|
|
10
|
+
presignedUrl: string
|
|
11
|
+
): Promise<EncryptionKey> => {
|
|
12
|
+
const encryptionKey = await generateRandomAESKey();
|
|
13
|
+
const { encryptedData, iv } = await encryptFile(file, encryptionKey);
|
|
14
|
+
|
|
15
|
+
// Upload encrypted file to S3
|
|
16
|
+
const uploadResponse = await fetch(presignedUrl, {
|
|
17
|
+
method: "PUT",
|
|
18
|
+
body: encryptedData,
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": file.type,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!uploadResponse.ok) {
|
|
25
|
+
throw new Error(`Failed to upload file ${uploadResponse.status}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return encodeEncryptionKey(encryptionKey, iv);
|
|
29
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { EncryptionKey } from "./lib/encryption.js";
|
|
2
|
+
|
|
1
3
|
export interface DialogMessage {
|
|
2
4
|
type: string;
|
|
3
5
|
payload?: any;
|
|
4
6
|
id?: string;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
export type WelshareEnvironment = "development" | "production";
|
|
8
|
-
|
|
9
9
|
/**
|
|
10
10
|
* a welshare schema type uid
|
|
11
11
|
*/
|
|
@@ -18,16 +18,53 @@ export interface SubmissionPayload<T> {
|
|
|
18
18
|
submission: T;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export type UploadCredentials = {
|
|
22
|
+
presignedUrl: string;
|
|
23
|
+
uploadKey: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RequestUploadCredentialsPayload {
|
|
27
|
+
timestamp?: Date;
|
|
28
|
+
applicationId: string;
|
|
29
|
+
reference: string;
|
|
30
|
+
fileName: string;
|
|
31
|
+
fileType: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BinaryFileSubmissionPayload extends RequestUploadCredentialsPayload {
|
|
35
|
+
encryptionKey: EncryptionKey;
|
|
36
|
+
/// in bytes
|
|
37
|
+
fileSize: number;
|
|
38
|
+
url: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RunningFileUpload extends RequestUploadCredentialsPayload {
|
|
42
|
+
credentials?: UploadCredentials;
|
|
43
|
+
file: File;
|
|
44
|
+
reference: string;
|
|
45
|
+
uploadPromise: {
|
|
46
|
+
resolve: (result: { url: string; binaryFileUid: string }) => void;
|
|
47
|
+
reject: (error: Error) => void;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
21
51
|
export interface DataSubmissionDialogMessage extends DialogMessage {
|
|
22
|
-
payload:
|
|
52
|
+
payload:
|
|
53
|
+
| SubmissionPayload<unknown>
|
|
54
|
+
| BinaryFileSubmissionPayload
|
|
55
|
+
| RequestUploadCredentialsPayload;
|
|
23
56
|
}
|
|
24
57
|
|
|
25
58
|
export interface WelshareConnectionOptions {
|
|
26
59
|
applicationId: string;
|
|
60
|
+
interpolateSocials?: {
|
|
61
|
+
emailAddress?: string;
|
|
62
|
+
privy?: string;
|
|
63
|
+
}
|
|
27
64
|
//todo: must go into build config, not supposed to be used by users
|
|
28
65
|
apiBaseUrl?: string;
|
|
29
|
-
environment?: WelshareEnvironment;
|
|
30
66
|
callbacks: {
|
|
67
|
+
onFileUploaded?: (insertedUid: string, url: string) => void;
|
|
31
68
|
onUploaded?: (payload: SubmissionPayload<unknown>) => void;
|
|
32
69
|
onError?: (error: string) => void;
|
|
33
70
|
onSessionReady?: (sessionPubKey: string) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@welshare/react",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React library for integrating with Welshare's sovereign data sharing platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"node": ">=20.0.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
|
-
"lint": "eslint . --max-warnings
|
|
80
|
+
"lint": "eslint . --max-warnings 25",
|
|
81
81
|
"build": "tshy",
|
|
82
82
|
"build:clean": "rm -rf ./dist",
|
|
83
83
|
"test": "vitest",
|