@cooperation/vc-storage 1.0.34 → 1.0.40

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cooperation/vc-storage
2
2
 
3
- **Version**: 1.0.0
3
+ **Version**: 1.0.35
4
4
 
5
5
  ## Overview
6
6
 
package/app.config.js ADDED
@@ -0,0 +1 @@
1
+ export const WAS_BASE_URL = 'http://localhost:4444';
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './models/GoogleDriveStorage.js';
2
2
  export * from './models/CredentialEngine.js';
3
3
  export * from './utils/google.js';
4
- export * from './utils/presentation.js';
5
4
  export * from './models/Resume.js';
6
5
  export * from './models/ResumeVC.js';
6
+ export * from './models/WASStorage.js';
7
+ export * from './utils/createWASSpace.js';
8
+ export * from './utils/getOrCreateAppDID.js';
@@ -126,7 +126,7 @@ export class CredentialEngine {
126
126
  }
127
127
  /**
128
128
  * Sign a Verifiable Credential (VC)
129
- * @param {'VC' | 'RECOMMENDATION'} type - The signature type.
129
+ * @param {'VC' | 'RECOMMENDATION' | 'EMPLOYMENT' | 'VOLUNTEERING' | 'PERFORMANCE_REVIEW'} type - The signature type.
130
130
  * @param {string} issuerId - The ID of the issuer [currently we put it as the did id]
131
131
  * @param {KeyPair} keyPair - The key pair to use for signing.
132
132
  * @param {FormDataI | RecommendationFormDataI} formData - The form data to include in the VC.
@@ -269,7 +269,7 @@ export class CredentialEngine {
269
269
  throw new Error('SEED environment variable not set. Cannot generate or use any DID.');
270
270
  }
271
271
  // Use deterministic keys from environment seed
272
- const { getDidFromEnvSeed } = await import('../utils/decodedSeed');
272
+ const { getDidFromEnvSeed } = await import('../utils/decodedSeed.js');
273
273
  const result = await getDidFromEnvSeed(encodedSeed);
274
274
  keyPair = result.keyPair;
275
275
  didDocument = result.didDocument;
@@ -2,21 +2,25 @@
2
2
  * @class GoogleDriveStorage
3
3
  * @description Class to interact with Google Drive API
4
4
  * @param accessToken - Access token to authenticate with Google Drive API
5
- * @method createFolder - Create a new folder in Google Drive
6
- * @method save - Save data to Google Drive
7
- * @method addCommentToFile - Add a comment to a file in Google Drive
8
- * @method addCommenterRoleToFile - Add commenter role to a file in Google Drive
5
+ * @method saveFile - Save a file to Google Drive
9
6
  * @method retrieve - Retrieve a file from Google Drive
10
- * @method findFolders - Find folders in Google Drive
11
- * @method findLastFile - Find the last file in a folder
12
- * @method getAllVCs - Get all verifiable credentials from Google Drive
13
- * @method getAllSessions - Get all sessions from Google Drive
7
+ * @method createFolder - Create a new folder in Google Drive
8
+ * @method getOrCreateMediaFolder - Get the ID of the MEDIAs folder
9
+ * @method uploadBinaryFile - Upload a binary file to Google Drive
10
+ * @method updateFileData - Update the data of a file
11
+ * @method updateRelationsFile - Update the relations file
14
12
  * @method delete - Delete a file from Google Drive
13
+ * @method checkEmailExists - Check if an email VC exists and return its content
14
+ * @method findFolders - Find folders in Google Drive
15
+ * @method findFolderFiles - Find files in a folder
15
16
  */
16
17
  export class GoogleDriveStorage {
17
18
  accessToken;
18
- folderCache = {};
19
- fileIdsCache = null;
19
+ static folderCache = {};
20
+ static fileIdsCache = null;
21
+ constructor(accessToken) {
22
+ this.accessToken = accessToken;
23
+ }
20
24
  async updateFileIdsJson(newFileId) {
21
25
  const constructUrl = () => {
22
26
  const baseUrl = 'https://www.googleapis.com/drive/v3/files';
@@ -29,28 +33,28 @@ export class GoogleDriveStorage {
29
33
  };
30
34
  try {
31
35
  // ✅ Fetch `file_ids.json` ID once per session (cached)
32
- if (!this.fileIdsCache) {
36
+ if (!GoogleDriveStorage.fileIdsCache) {
33
37
  const existingFile = await this.fetcher({
34
38
  method: 'GET',
35
39
  headers: {},
36
40
  url: constructUrl(),
37
41
  });
38
42
  if (existingFile.files.length > 0) {
39
- this.fileIdsCache = existingFile.files[0].id;
43
+ GoogleDriveStorage.fileIdsCache = existingFile.files[0].id;
40
44
  }
41
45
  else {
42
46
  console.log('No existing file_ids.json found, creating a new one.');
43
- this.fileIdsCache = null;
47
+ GoogleDriveStorage.fileIdsCache = null;
44
48
  }
45
49
  }
46
50
  let existingFileIds = [];
47
51
  // ✅ Fetch existing file IDs **only if `file_ids.json` exists**
48
- if (this.fileIdsCache) {
52
+ if (GoogleDriveStorage.fileIdsCache) {
49
53
  try {
50
54
  const fileContent = await this.fetcher({
51
55
  method: 'GET',
52
56
  headers: {},
53
- url: `https://www.googleapis.com/drive/v3/files/${this.fileIdsCache}?alt=media`,
57
+ url: `https://www.googleapis.com/drive/v3/files/${GoogleDriveStorage.fileIdsCache}?alt=media`,
54
58
  });
55
59
  existingFileIds = fileContent;
56
60
  }
@@ -60,16 +64,13 @@ export class GoogleDriveStorage {
60
64
  }
61
65
  // ✅ Append the new file ID to the list
62
66
  existingFileIds.push(newFileId);
63
- console.log('File ID saved to appDataFolder.', this.fileIdsCache);
67
+ console.log('File ID saved to appDataFolder.', GoogleDriveStorage.fileIdsCache);
64
68
  }
65
69
  catch (error) {
66
70
  console.error('Error updating file_ids.json:', error.message);
67
71
  throw error;
68
72
  }
69
73
  }
70
- constructor(accessToken) {
71
- this.accessToken = accessToken;
72
- }
73
74
  async fetcher({ method, headers, body, url }) {
74
75
  try {
75
76
  const res = await fetch(url, {
@@ -120,6 +121,43 @@ export class GoogleDriveStorage {
120
121
  });
121
122
  return result.files || [];
122
123
  }
124
+ async getOrCreateMediaFolder() {
125
+ if (GoogleDriveStorage.folderCache['MEDIAs']) {
126
+ return GoogleDriveStorage.folderCache['MEDIAs'];
127
+ }
128
+ const rootFolders = await this.findFolders();
129
+ let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
130
+ if (!credentialsFolder) {
131
+ credentialsFolder = await this.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
132
+ }
133
+ const subfolders = await this.findFolders(credentialsFolder.id);
134
+ let mediasFolder = subfolders.find((f) => f.name === 'MEDIAs');
135
+ if (!mediasFolder) {
136
+ mediasFolder = await this.createFolder({ folderName: 'MEDIAs', parentFolderId: credentialsFolder.id });
137
+ }
138
+ GoogleDriveStorage.folderCache['MEDIAs'] = mediasFolder.id;
139
+ return mediasFolder.id;
140
+ }
141
+ async findFolderFiles(folderId) {
142
+ if (!folderId)
143
+ throw new Error('Folder ID is required');
144
+ const files = await this.searchFiles(`'${folderId}' in parents`);
145
+ if (files.length === 0) {
146
+ console.log('No files found in the folder.');
147
+ return [];
148
+ }
149
+ const filesWithContent = await Promise.all(files.map(async (file) => {
150
+ try {
151
+ const content = await this.getFileContent(file.id);
152
+ return { ...file, content };
153
+ }
154
+ catch (error) {
155
+ console.error(`Error fetching content for file "${file.name}" (ID: ${file.id}):`, error);
156
+ return { ...file, content: null };
157
+ }
158
+ }));
159
+ return filesWithContent;
160
+ }
123
161
  async createFolder({ folderName, parentFolderId }) {
124
162
  if (!parentFolderId) {
125
163
  throw new Error(`Parent folder ID must be provided when creating folder "${folderName}".`);
@@ -143,42 +181,29 @@ export class GoogleDriveStorage {
143
181
  body: JSON.stringify({ role: 'reader', type: 'anyone' }),
144
182
  });
145
183
  // Invalidate cache for this parent folder
146
- if (this.folderCache[parentFolderId]) {
147
- delete this.folderCache[parentFolderId];
184
+ if (GoogleDriveStorage.folderCache[parentFolderId]) {
185
+ delete GoogleDriveStorage.folderCache[parentFolderId];
148
186
  }
149
187
  // Also clear 'root' cache if parent is root
150
- if (parentFolderId === 'root' && this.folderCache['root']) {
151
- delete this.folderCache['root'];
188
+ if (parentFolderId === 'root' && GoogleDriveStorage.folderCache['root']) {
189
+ delete GoogleDriveStorage.folderCache['root'];
152
190
  }
153
191
  return folder;
154
192
  }
193
+ /**
194
+ * Get the ID of the MEDIAs folder (public wrapper for getOrCreateMediaFolder)
195
+ * @returns The folder ID for the MEDIAs folder
196
+ */
155
197
  async getMediaFolderId() {
156
- if (this.folderCache['MEDIAs']) {
157
- return this.folderCache['MEDIAs'];
158
- }
159
- const rootFolders = await this.findFolders();
160
- let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
161
- if (!credentialsFolder) {
162
- credentialsFolder = await this.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
163
- }
164
- const credentialsFolderId = credentialsFolder.id;
165
- const subfolders = await this.findFolders(credentialsFolder.id);
166
- let mediasFolder = subfolders.find((f) => f.name === 'MEDIAs');
167
- if (!mediasFolder) {
168
- mediasFolder = await this.createFolder({ folderName: 'MEDIAs', parentFolderId: credentialsFolderId });
169
- }
170
- const mediasFolderId = mediasFolder.id;
171
- this.folderCache['MEDIAs'] = mediasFolderId;
172
- return mediasFolderId;
198
+ return await this.getOrCreateMediaFolder();
173
199
  }
174
200
  async uploadBinaryFile({ file }) {
175
201
  try {
176
- const accessToken = this.accessToken; // Ensure access token is available
202
+ const accessToken = this.accessToken;
177
203
  if (!accessToken) {
178
204
  throw new Error('Missing Google OAuth access token.');
179
205
  }
180
- const folderId = await this.getMediaFolderId(); // Ensure folderId is correct
181
- // ✅ Correct metadata for Google Drive API
206
+ const folderId = await this.getOrCreateMediaFolder();
182
207
  const metadata = {
183
208
  name: file.name,
184
209
  mimeType: file.type,
@@ -209,14 +234,20 @@ export class GoogleDriveStorage {
209
234
  throw error;
210
235
  }
211
236
  }
212
- async saveFile({ data, folderId }) {
237
+ async saveFile({ data, folderId, fileId }) {
213
238
  console.log('🚀 ~ GoogleDriveStorage ~ saveFile ~ data:', data);
214
239
  try {
240
+ // If fileId is provided, update the existing file instead of creating a new one
241
+ if (fileId) {
242
+ console.log(`Updating existing file with ID: ${fileId}`);
243
+ return await this.updateFileContent({ fileId, data });
244
+ }
245
+ // For new files, folderId is required
215
246
  if (!folderId) {
216
- throw new Error('Folder ID is required to save a file.');
247
+ throw new Error('Folder ID is required to save a new file.');
217
248
  }
218
249
  const fileMetadata = {
219
- name: data.fileName || data.name + '.json' || data.credentialSubject.person.name.formattedName + '.json' || 'Untitled file.json',
250
+ name: data.fileName || data.name + '.json' || data.credentialSubject?.person?.name?.formattedName + '.json' || 'Untitled file.json',
220
251
  parents: [folderId],
221
252
  mimeType: data.mimeType || 'application/json',
222
253
  };
@@ -248,11 +279,6 @@ export class GoogleDriveStorage {
248
279
  throw error;
249
280
  }
250
281
  }
251
- /**
252
- * Get file from google drive by id
253
- * @param id
254
- * @returns file content
255
- */
256
282
  async retrieve(id) {
257
283
  const dataUrl = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
258
284
  try {
@@ -293,63 +319,53 @@ export class GoogleDriveStorage {
293
319
  return null;
294
320
  }
295
321
  }
296
- /**
297
- * Get folder by folderId, if folderId == null you will have them all
298
- * @param folderId [Optional]
299
- * @returns
300
- */
301
322
  async findFolders(folderId) {
302
323
  const cacheKey = folderId || 'root';
303
- if (this.folderCache[cacheKey]) {
304
- return this.folderCache[cacheKey];
324
+ if (GoogleDriveStorage.folderCache[cacheKey]) {
325
+ return GoogleDriveStorage.folderCache[cacheKey];
305
326
  }
306
327
  const query = folderId
307
328
  ? `'${folderId}' in parents and mimeType='application/vnd.google-apps.folder'`
308
329
  : `'root' in parents and mimeType='application/vnd.google-apps.folder'`;
309
330
  const folders = await this.searchFiles(query);
310
- this.folderCache[cacheKey] = folders;
331
+ GoogleDriveStorage.folderCache[cacheKey] = folders;
311
332
  return folders;
312
333
  }
313
- /**
314
- * Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
315
- * @param type
316
- * @returns
317
- */
318
334
  async getAllFilesByType(type) {
319
335
  try {
320
- if (!this.folderCache['Credentials']) {
336
+ if (!GoogleDriveStorage.folderCache['Credentials']) {
321
337
  const rootFolders = await this.findFolders();
322
- this.folderCache['Credentials'] = rootFolders;
338
+ GoogleDriveStorage.folderCache['Credentials'] = rootFolders;
323
339
  }
324
- const credentialsFolder = this.folderCache['Credentials'].find((f) => f.name === 'Credentials');
340
+ const credentialsFolder = GoogleDriveStorage.folderCache['Credentials'].find((f) => f.name === 'Credentials');
325
341
  if (!credentialsFolder) {
326
342
  console.error('Credentials folder not found.');
327
343
  return [];
328
344
  }
329
345
  if (type === 'VCs') {
330
- if (!this.folderCache['VCs']) {
346
+ if (!GoogleDriveStorage.folderCache['VCs']) {
331
347
  const vcSubfolder = await this.findFolders(credentialsFolder.id);
332
348
  const vcsFolder = vcSubfolder.find((f) => f.name === 'VCs');
333
349
  const vcSubFolders = await this.findFolders(vcsFolder.id);
334
- this.folderCache['VCs'] = vcSubFolders.filter((folder) => folder.name.startsWith('VC-'));
350
+ GoogleDriveStorage.folderCache['VCs'] = vcSubFolders.filter((folder) => folder.name.startsWith('VC-'));
335
351
  }
336
- const vcSubfolders = this.folderCache['VCs'];
352
+ const vcSubfolders = GoogleDriveStorage.folderCache['VCs'];
337
353
  if (!vcSubfolders.length) {
338
354
  console.error(`No subfolders found for type: ${type}`);
339
355
  return [];
340
356
  }
341
- const allFilesNested = await Promise.all(vcSubfolders.map(async (folder) => await this.findFilesUnderFolder(folder.id)));
357
+ const allFilesNested = await Promise.all(vcSubfolders.map(async (folder) => await this.findFolderFiles(folder.id)));
342
358
  const allVcJsonFiles = allFilesNested.flat().filter((file) => file.mimeType === 'application/json');
343
359
  const fileContentsResults = await Promise.allSettled(allVcJsonFiles.map((file) => this.retrieve(file.id)));
344
360
  const validFileContents = fileContentsResults.filter((result) => result.status === 'fulfilled').map((result) => result.value);
345
361
  return validFileContents.filter((file) => file.data.fileName !== 'RELATIONS');
346
362
  }
347
- if (!this.folderCache[type]) {
363
+ if (!GoogleDriveStorage.folderCache[type]) {
348
364
  const subfolders = await this.findFolders(credentialsFolder.id);
349
365
  const targetFolder = subfolders.find((f) => f.name === type);
350
- this.folderCache[type] = targetFolder ? targetFolder.id : null;
366
+ GoogleDriveStorage.folderCache[type] = targetFolder ? targetFolder.id : null;
351
367
  }
352
- const targetFolderId = this.folderCache[type];
368
+ const targetFolderId = GoogleDriveStorage.folderCache[type];
353
369
  if (!targetFolderId) {
354
370
  console.error(`Folder for type ${type} not found.`);
355
371
  return [];
@@ -369,70 +385,52 @@ export class GoogleDriveStorage {
369
385
  return [];
370
386
  }
371
387
  }
372
- /**
373
- * Update the name of a file in Google Drive
374
- * @param fileId - The ID of the file to update
375
- * @param newFileName - The new name for the file
376
- * @returns The updated file metadata, including the new name
377
- */
378
- async updateFileName(fileId, newFileName) {
388
+ async updateFileData(fileId, data) {
379
389
  try {
380
- const metadata = { name: newFileName };
390
+ const updateUrl = `https://www.googleapis.com/drive/v3/files/${fileId}`;
381
391
  const updatedFile = await this.fetcher({
382
392
  method: 'PATCH',
383
393
  headers: { 'Content-Type': 'application/json' },
384
- body: JSON.stringify(metadata),
385
- url: `https://www.googleapis.com/drive/v3/files/${fileId}`,
394
+ body: JSON.stringify({ name: data.fileName }),
395
+ url: updateUrl,
386
396
  });
387
- console.log('File name updated successfully:', updatedFile.name);
397
+ console.log('File renamed successfully:', updatedFile);
388
398
  return updatedFile;
389
399
  }
390
400
  catch (error) {
391
- console.error('Error updating file name:', error.message);
392
401
  throw error;
393
402
  }
394
403
  }
395
- async findFileByName(name) {
396
- const rootFolders = await this.findFolders();
397
- const credentialsFolderId = rootFolders.find((f) => f.name === 'Credentials')?.id;
398
- if (!credentialsFolderId)
399
- throw new Error('Credentials folder not found');
400
- const files = await this.searchFiles(`'${credentialsFolderId}' in parents and name='${name}'`);
401
- return files[0];
402
- }
403
- async findFilesUnderFolder(folderId) {
404
- if (!folderId)
405
- throw new Error('Folder ID is required');
406
- const files = await this.searchFiles(`'${folderId}' in parents`);
407
- if (files.length === 0) {
408
- console.log('No files found in the folder.');
409
- return [];
410
- }
411
- const filesWithContent = await Promise.all(files.map(async (file) => {
412
- try {
413
- const content = await this.getFileContent(file.id);
414
- return { ...file, content };
415
- }
416
- catch (error) {
417
- console.error(`Error fetching content for file "${file.name}" (ID: ${file.id}):`, error);
418
- return { ...file, content: null };
419
- }
420
- }));
421
- return filesWithContent;
422
- }
423
- async updateFileData(fileId, data) {
404
+ /**
405
+ * Update the content of an existing file in Google Drive
406
+ * @param fileId - The ID of the file to update
407
+ * @param data - The new content for the file
408
+ * @returns The updated file metadata
409
+ */
410
+ async updateFileContent({ fileId, data }) {
424
411
  try {
425
- const updateUrl = `https://www.googleapis.com/drive/v3/files/${fileId}`;
426
- const updatedFile = await this.fetcher({
412
+ if (!fileId) {
413
+ throw new Error('File ID is required to update a file.');
414
+ }
415
+ // Convert data to JSON blob
416
+ const fileBlob = new Blob([JSON.stringify(data)], { type: 'application/json' });
417
+ // Update the file content using media upload
418
+ // Note: When using uploadType=media, the response is the updated file resource
419
+ const response = await this.fetcher({
427
420
  method: 'PATCH',
428
- headers: { 'Content-Type': 'application/json' },
429
- body: JSON.stringify({ name: data.fileName }),
430
- url: updateUrl,
421
+ headers: {
422
+ 'Content-Type': 'application/json'
423
+ },
424
+ body: fileBlob,
425
+ url: `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`,
431
426
  });
432
- console.log('✅ File renamed successfully:', updatedFile);
433
- return updatedFile;
427
+ console.log(`✅ File content updated successfully: ${fileId}`);
428
+ // Return a consistent response format with id and data
429
+ // The response from media upload might be the file metadata or empty
430
+ return { id: fileId, data, ...response };
434
431
  }
435
432
  catch (error) {
433
+ console.error('Error updating file content:', error);
436
434
  throw error;
437
435
  }
438
436
  }
@@ -460,12 +458,10 @@ export class GoogleDriveStorage {
460
458
  return updateResponse;
461
459
  }
462
460
  async createRelationsFile({ vcFolderId }) {
463
- const files = await this.findFilesUnderFolder(vcFolderId);
461
+ const files = await this.findFolderFiles(vcFolderId);
464
462
  const vcFile = files.find((file) => file.name === 'VC');
465
463
  const vcContent = await this.getFileContent(vcFile.id);
466
- console.log('🚀 ~ GoogleDriveStorage ~ createRelationsFile ~ vcContent:', vcContent);
467
464
  const subject = JSON.parse(vcContent.body).credentialSubject;
468
- console.log('🚀 ~ GoogleDriveStorage ~ createRelationsFile ~ subject:', subject);
469
465
  const relationsFile = await this.saveFile({
470
466
  data: {
471
467
  fileName: 'RELATIONS',
@@ -483,66 +479,6 @@ export class GoogleDriveStorage {
483
479
  await this.updateFileIdsJson(relationsFile.id);
484
480
  return relationsFile;
485
481
  }
486
- async updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }) {
487
- try {
488
- const relationFileName = 'relations.json';
489
- // Step 1: Check if `relations.json` exists in RESUMES_AUTHOR/
490
- const existingRelationFiles = await this.searchFiles(`name='${relationFileName}' and '${authorFolderId}' in parents`);
491
- let relationFileId = null;
492
- let existingRelations = {};
493
- if (existingRelationFiles.length > 0) {
494
- relationFileId = existingRelationFiles[0].id;
495
- existingRelations = await this.getFileContent(relationFileId);
496
- }
497
- else {
498
- console.log('relations.json does not exist. Will create a new one.');
499
- }
500
- // Step 2: Update relations object
501
- existingRelations[draftFileId] = signedFileId;
502
- // Step 3: Create or update the file on Drive
503
- const fileBlob = new Blob([JSON.stringify(existingRelations, null, 2)], {
504
- type: 'application/json',
505
- });
506
- const formData = new FormData();
507
- const metadata = {
508
- name: relationFileName,
509
- parents: [authorFolderId],
510
- mimeType: 'application/json',
511
- };
512
- formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
513
- formData.append('file', fileBlob);
514
- let uploadedFile;
515
- if (relationFileId) {
516
- // Update existing file
517
- uploadedFile = await this.fetcher({
518
- method: 'PATCH',
519
- headers: {},
520
- body: formData,
521
- url: `https://www.googleapis.com/upload/drive/v3/files/${relationFileId}?uploadType=multipart&fields=id,parents`,
522
- });
523
- }
524
- else {
525
- // Create new file
526
- uploadedFile = await this.fetcher({
527
- method: 'POST',
528
- headers: {},
529
- body: formData,
530
- url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,parents`,
531
- });
532
- }
533
- console.log(`✅ Resume relation updated. File ID: ${uploadedFile.id}`);
534
- return uploadedFile;
535
- }
536
- catch (error) {
537
- console.error('❌ Failed to update resume relation:', error);
538
- throw error;
539
- }
540
- }
541
- /**
542
- * Delete file by id
543
- * @param id
544
- * @returns
545
- */
546
482
  async delete(id) {
547
483
  try {
548
484
  const response = await this.fetcher({
@@ -558,94 +494,6 @@ export class GoogleDriveStorage {
558
494
  return null;
559
495
  }
560
496
  }
561
- async update(fileId, data) {
562
- const metadata = {
563
- name: data.fileName || 'resume.json',
564
- mimeType: 'application/json',
565
- };
566
- const uploadUrl = `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`;
567
- const formData = new FormData();
568
- formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
569
- formData.append('file', new Blob([JSON.stringify(data.body)], { type: 'application/json' }));
570
- try {
571
- const response = await this.fetcher({
572
- method: 'PATCH',
573
- headers: {},
574
- body: formData,
575
- url: `${uploadUrl}&fields=id,name,mimeType`,
576
- });
577
- console.log('✅ File updated successfully:', response);
578
- return response;
579
- }
580
- catch (error) {
581
- console.error('❌ Error updating Google Drive file:', error);
582
- throw error;
583
- }
584
- }
585
- async getFileIdsFromAppDataFolder() {
586
- try {
587
- const constructUrl = () => {
588
- const baseUrl = 'https://www.googleapis.com/drive/v3/files';
589
- const queryParams = new URLSearchParams({
590
- spaces: 'appDataFolder',
591
- q: "name='file_ids.json'",
592
- fields: 'files(id)',
593
- });
594
- return `${baseUrl}?${queryParams.toString()}`;
595
- };
596
- // Step 1: Search for the file_ids.json file in the appDataFolder
597
- const response = await this.fetcher({
598
- method: 'GET',
599
- headers: {},
600
- url: constructUrl(),
601
- });
602
- console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder response', response);
603
- // Step 2: Check if the file exists
604
- if (!response.files || response.files.length === 0) {
605
- console.log('No file_ids.json found in appDataFolder.');
606
- return [];
607
- }
608
- // Step 3: Get the file ID of file_ids.json
609
- const fileId = response.files[0].id;
610
- console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder fileId', fileId);
611
- // Step 4: Fetch the content of file_ids.json
612
- const fileContent = await this.fetcher({
613
- method: 'GET',
614
- headers: {},
615
- url: `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`,
616
- });
617
- console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder fileContent', fileContent);
618
- // Step 5: Parse the file content (array of file IDs)
619
- const fileIds = fileContent;
620
- console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder fileIds', fileIds);
621
- return fileIds;
622
- }
623
- catch (error) {
624
- console.error('Error fetching file IDs from appDataFolder:', error.message);
625
- return [];
626
- }
627
- }
628
- async getAllFilesData() {
629
- try {
630
- // Step 1: Get the file IDs from appDataFolder
631
- const fileIds = await this.getFileIdsFromAppDataFolder();
632
- if (fileIds.length === 0) {
633
- console.log('No files found.');
634
- return [];
635
- }
636
- // Step 2: Return the array of file IDs
637
- return fileIds;
638
- }
639
- catch (error) {
640
- console.error('Error fetching all files data:', error.message);
641
- return [];
642
- }
643
- }
644
- /**
645
- * Check if an email VC exists and return its content
646
- * @param email - The email address to check
647
- * @returns {Promise<{data: any, id: string} | null>} - The email VC content and ID if exists, null otherwise
648
- */
649
497
  async checkEmailExists(email) {
650
498
  try {
651
499
  // Get root folders
@@ -61,15 +61,27 @@ export class StorageHandler {
61
61
  if (!folder) {
62
62
  throw new Error(`${folderName} folder not found`);
63
63
  }
64
- return this.storage.findFilesUnderFolder(folder.id);
64
+ return this.storage.findFolderFiles(folder.id);
65
65
  }
66
66
  }
67
67
  export class Resume extends StorageHandler {
68
68
  constructor(storage) {
69
69
  super(storage);
70
70
  }
71
- async saveResume({ resume, type }) {
71
+ async saveResume({ resume, type, id }) {
72
72
  try {
73
+ // If an ID is provided, update the existing resume
74
+ if (id) {
75
+ console.log(`🔄 Updating existing ${type} resume with ID: ${id}`);
76
+ // Update the file content directly
77
+ const updatedResume = await this.storage.updateFileContent({
78
+ fileId: id,
79
+ data: resume
80
+ });
81
+ // Return the updated resume with its ID
82
+ return { ...updatedResume, id };
83
+ }
84
+ // For new resumes, create folders and save as before
73
85
  let rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
74
86
  console.log('🚀 ~ Resume ~ saveResume ~ rootFolder:', rootFolder);
75
87
  // Get or create the subfolder
@@ -106,7 +118,7 @@ export class Resume extends StorageHandler {
106
118
  // Find or create the signed resumes folder
107
119
  const signedFolder = await this.getOrCreateFolder(resumeFolderTypes.signed, rootFolder.id);
108
120
  // Retrieve all files from the signed folder
109
- const files = await this.storage.findFilesUnderFolder(signedFolder.id);
121
+ const files = await this.storage.findFolderFiles(signedFolder.id);
110
122
  return files;
111
123
  }
112
124
  catch (error) {
@@ -120,7 +132,7 @@ export class Resume extends StorageHandler {
120
132
  // Find or create the non-signed resumes folder
121
133
  const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
122
134
  // Retrieve all files from the non-signed folder
123
- const files = await this.storage.findFilesUnderFolder(nonSignedFolder.id);
135
+ const files = await this.storage.findFolderFiles(nonSignedFolder.id);
124
136
  return files;
125
137
  }
126
138
  catch (error) {
@@ -0,0 +1,71 @@
1
+ import { StorageClient } from '@wallet.storage/fetch-client';
2
+ import { WAS_BASE_URL } from '../../app.config.js';
3
+ export class LCWStorage {
4
+ static storageClient;
5
+ signer;
6
+ zcap;
7
+ spaceId;
8
+ constructor({ signer, zcap, spaceId }) {
9
+ this.signer = signer;
10
+ this.zcap = zcap;
11
+ this.spaceId = spaceId;
12
+ }
13
+ getStorageClient() {
14
+ if (!LCWStorage.storageClient) {
15
+ LCWStorage.storageClient = new StorageClient(new URL(WAS_BASE_URL));
16
+ }
17
+ return LCWStorage.storageClient;
18
+ }
19
+ getResource(key) {
20
+ const space = this.getStorageClient().space({
21
+ signer: this.signer,
22
+ id: this.spaceId,
23
+ });
24
+ return space.resource(key);
25
+ }
26
+ async add(key, value) {
27
+ const resource = this.getResource(key);
28
+ const blob = new Blob([JSON.stringify(value)], {
29
+ type: 'application/json',
30
+ });
31
+ const res = await resource.put(blob, {
32
+ signer: this.signer,
33
+ });
34
+ if (!res.ok) {
35
+ throw new Error(`Failed to add resource. Status: ${res.status}`);
36
+ }
37
+ return res;
38
+ }
39
+ async read(key) {
40
+ const resource = this.getResource(key);
41
+ const res = await resource.get({ signer: this.signer });
42
+ if (!res.ok) {
43
+ if (res.status === 404)
44
+ return null;
45
+ throw new Error(`Failed to read resource. Status: ${res.status}`);
46
+ }
47
+ return await res.json();
48
+ }
49
+ async update(key, value) {
50
+ return this.add(key, value); // Overwrite = update
51
+ }
52
+ async delete(key) {
53
+ const resource = this.getResource(key);
54
+ const res = await resource.delete({ signer: this.signer });
55
+ if (!res.ok && res.status !== 404) {
56
+ throw new Error(`Failed to delete resource. Status: ${res.status}`);
57
+ }
58
+ return true;
59
+ }
60
+ async list() {
61
+ // const space = this.getStorageClient().space({
62
+ // signer: this.signer,
63
+ // id: this.spaceId as `urn:uuid:${string}`,
64
+ // });
65
+ // const res = await space.resources().list({ signer: this.signer });
66
+ // if (!res.ok) {
67
+ // throw new Error(`Failed to list resources. Status: ${res.status}`);
68
+ // }
69
+ // return await res.json(); // Should contain list of resource IDs or summaries
70
+ }
71
+ }
@@ -0,0 +1,61 @@
1
+ import { Ed25519Signer } from '@did.coop/did-key-ed25519';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { LCWStorage } from '../models/WASStorage.js';
4
+ import { StorageClient } from '@wallet.storage/fetch-client';
5
+ import { WAS_BASE_URL } from '../../app.config.js';
6
+ async function main() {
7
+ const appDidSigner = await Ed25519Signer.generate();
8
+ console.log('Signer:', appDidSigner);
9
+ const spaceUUID = uuidv4();
10
+ const spaceId = `urn:uuid:${spaceUUID}`;
11
+ console.log('Space ID:', spaceId);
12
+ const storage = new StorageClient(new URL(WAS_BASE_URL));
13
+ const space = storage.space({
14
+ signer: appDidSigner,
15
+ id: spaceId,
16
+ });
17
+ const spaceObject = {
18
+ id: spaceId,
19
+ controller: appDidSigner.id.split('#')[0],
20
+ };
21
+ console.log('Creating space with object:', spaceObject);
22
+ const spaceObjectBlob = new Blob([JSON.stringify(spaceObject)], { type: 'application/json' });
23
+ // Create the space
24
+ const response = await space.put(spaceObjectBlob, {
25
+ signer: appDidSigner,
26
+ });
27
+ console.log('🚀 ~ main ~ response:', response);
28
+ console.log('Space PUT response:', {
29
+ status: response.status,
30
+ ok: response.ok,
31
+ });
32
+ if (!response.ok) {
33
+ throw new Error(`Failed to initialize space. Status: ${response.status}`);
34
+ }
35
+ // Store the signer for future connections
36
+ const signerJson = await appDidSigner.toJSON();
37
+ console.log('Signer JSON:', signerJson);
38
+ const lcwStorage = new LCWStorage({ signer: appDidSigner, zcap: {}, spaceId });
39
+ const res = await lcwStorage.add('test', { test: 'test' });
40
+ if (res.ok) {
41
+ console.log('Record added successfully');
42
+ }
43
+ else {
44
+ console.error('Failed to add record');
45
+ }
46
+ const res2 = await lcwStorage.read('test');
47
+ if (res2) {
48
+ console.log('Record read successfully');
49
+ }
50
+ else {
51
+ console.error('Failed to read record');
52
+ }
53
+ const res3 = await lcwStorage.update('test', { test: 'test2' });
54
+ if (res3.ok) {
55
+ console.log('Record updated successfully');
56
+ }
57
+ else {
58
+ console.error('Failed to update record');
59
+ }
60
+ }
61
+ main();
@@ -1,6 +1,8 @@
1
1
  export * from './models/GoogleDriveStorage.js';
2
2
  export * from './models/CredentialEngine.js';
3
3
  export * from './utils/google.js';
4
- export * from './utils/presentation.js';
5
4
  export * from './models/Resume.js';
6
5
  export * from './models/ResumeVC.js';
6
+ export * from './models/WASStorage.js';
7
+ export * from './utils/createWASSpace.js';
8
+ export * from './utils/getOrCreateAppDID.js';
@@ -50,7 +50,7 @@ export declare class CredentialEngine {
50
50
  }>;
51
51
  /**
52
52
  * Sign a Verifiable Credential (VC)
53
- * @param {'VC' | 'RECOMMENDATION'} type - The signature type.
53
+ * @param {'VC' | 'RECOMMENDATION' | 'EMPLOYMENT' | 'VOLUNTEERING' | 'PERFORMANCE_REVIEW'} type - The signature type.
54
54
  * @param {string} issuerId - The ID of the issuer [currently we put it as the did id]
55
55
  * @param {KeyPair} keyPair - The key pair to use for signing.
56
56
  * @param {FormDataI | RecommendationFormDataI} formData - The form data to include in the VC.
@@ -3,70 +3,64 @@ type FileType = 'KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs' | '
3
3
  * @class GoogleDriveStorage
4
4
  * @description Class to interact with Google Drive API
5
5
  * @param accessToken - Access token to authenticate with Google Drive API
6
- * @method createFolder - Create a new folder in Google Drive
7
- * @method save - Save data to Google Drive
8
- * @method addCommentToFile - Add a comment to a file in Google Drive
9
- * @method addCommenterRoleToFile - Add commenter role to a file in Google Drive
6
+ * @method saveFile - Save a file to Google Drive
10
7
  * @method retrieve - Retrieve a file from Google Drive
11
- * @method findFolders - Find folders in Google Drive
12
- * @method findLastFile - Find the last file in a folder
13
- * @method getAllVCs - Get all verifiable credentials from Google Drive
14
- * @method getAllSessions - Get all sessions from Google Drive
8
+ * @method createFolder - Create a new folder in Google Drive
9
+ * @method getOrCreateMediaFolder - Get the ID of the MEDIAs folder
10
+ * @method uploadBinaryFile - Upload a binary file to Google Drive
11
+ * @method updateFileData - Update the data of a file
12
+ * @method updateRelationsFile - Update the relations file
15
13
  * @method delete - Delete a file from Google Drive
14
+ * @method checkEmailExists - Check if an email VC exists and return its content
15
+ * @method findFolders - Find folders in Google Drive
16
+ * @method findFolderFiles - Find files in a folder
16
17
  */
17
18
  export declare class GoogleDriveStorage {
18
19
  private accessToken;
19
- folderCache: any;
20
- private fileIdsCache;
21
- private updateFileIdsJson;
20
+ static folderCache: any;
21
+ private static fileIdsCache;
22
22
  constructor(accessToken: string);
23
+ private updateFileIdsJson;
23
24
  private fetcher;
24
25
  private getFileContent;
25
26
  private searchFiles;
27
+ private getOrCreateMediaFolder;
28
+ findFolderFiles(folderId: string): Promise<any[]>;
26
29
  createFolder({ folderName, parentFolderId }: {
27
30
  folderName: string;
28
31
  parentFolderId: string;
29
32
  }): Promise<any>;
30
- getMediaFolderId(): Promise<any>;
33
+ /**
34
+ * Get the ID of the MEDIAs folder (public wrapper for getOrCreateMediaFolder)
35
+ * @returns The folder ID for the MEDIAs folder
36
+ */
37
+ getMediaFolderId(): Promise<string>;
31
38
  uploadBinaryFile({ file }: {
32
39
  file: File;
33
40
  }): Promise<any>;
34
- saveFile({ data, folderId }: {
41
+ saveFile({ data, folderId, fileId }: {
35
42
  data: any;
36
- folderId: string;
43
+ folderId?: string;
44
+ fileId?: string;
37
45
  }): Promise<any>;
38
- /**
39
- * Get file from google drive by id
40
- * @param id
41
- * @returns file content
42
- */
43
46
  retrieve(id: string): Promise<{
44
47
  data: any;
45
48
  id: string;
46
49
  } | null>;
47
- /**
48
- * Get folder by folderId, if folderId == null you will have them all
49
- * @param folderId [Optional]
50
- * @returns
51
- */
52
50
  findFolders(folderId?: string): Promise<any[]>;
53
- /**
54
- * Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
55
- * @param type
56
- * @returns
57
- */
58
51
  getAllFilesByType(type: FileType): Promise<any[]>;
52
+ updateFileData(fileId: string, data: {
53
+ fileName: string;
54
+ }): Promise<any>;
59
55
  /**
60
- * Update the name of a file in Google Drive
56
+ * Update the content of an existing file in Google Drive
61
57
  * @param fileId - The ID of the file to update
62
- * @param newFileName - The new name for the file
63
- * @returns The updated file metadata, including the new name
58
+ * @param data - The new content for the file
59
+ * @returns The updated file metadata
64
60
  */
65
- updateFileName(fileId: string, newFileName: string): Promise<any>;
66
- findFileByName(name: string): Promise<any>;
67
- findFilesUnderFolder(folderId: string): Promise<any[]>;
68
- updateFileData(fileId: string, data: {
69
- fileName: string;
61
+ updateFileContent({ fileId, data }: {
62
+ fileId: string;
63
+ data: any;
70
64
  }): Promise<any>;
71
65
  getFileParents(fileId: string): Promise<any>;
72
66
  updateRelationsFile({ relationsFileId, recommendationFileId }: {
@@ -76,25 +70,7 @@ export declare class GoogleDriveStorage {
76
70
  createRelationsFile({ vcFolderId }: {
77
71
  vcFolderId: string;
78
72
  }): Promise<any>;
79
- updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }: {
80
- authorFolderId: string;
81
- draftFileId: string;
82
- signedFileId: string;
83
- }): Promise<any>;
84
- /**
85
- * Delete file by id
86
- * @param id
87
- * @returns
88
- */
89
73
  delete(id: string): Promise<any>;
90
- update(fileId: string, data: any): Promise<any>;
91
- getFileIdsFromAppDataFolder(): Promise<any>;
92
- getAllFilesData(): Promise<any>;
93
- /**
94
- * Check if an email VC exists and return its content
95
- * @param email - The email address to check
96
- * @returns {Promise<{data: any, id: string} | null>} - The email VC content and ID if exists, null otherwise
97
- */
98
74
  checkEmailExists(email: string): Promise<{
99
75
  data: any;
100
76
  id: string;
@@ -1,4 +1,4 @@
1
- import { GoogleDriveStorage } from './GoogleDriveStorage.js';
1
+ import { GoogleDriveStorage } from './GoogleDriveStorage';
2
2
  export declare const resumeFolderTypes: {
3
3
  root: string;
4
4
  nonSigned: string;
@@ -14,9 +14,10 @@ export declare class StorageHandler {
14
14
  }
15
15
  export declare class Resume extends StorageHandler {
16
16
  constructor(storage: GoogleDriveStorage);
17
- saveResume({ resume, type }: {
17
+ saveResume({ resume, type, id }: {
18
18
  resume: any;
19
19
  type: 'sign' | 'unsigned';
20
+ id?: string;
20
21
  }): Promise<any>;
21
22
  find(): Promise<{
22
23
  signed: any[];
@@ -0,0 +1,30 @@
1
+ export declare class LCWStorage {
2
+ private static storageClient;
3
+ private signer;
4
+ private zcap;
5
+ private spaceId;
6
+ constructor({ signer, zcap, spaceId }: {
7
+ signer: any;
8
+ zcap: any;
9
+ spaceId: string;
10
+ });
11
+ private getStorageClient;
12
+ private getResource;
13
+ add(key: string, value: any): Promise<{
14
+ ok: boolean;
15
+ headers: [string, string][];
16
+ status: number;
17
+ blob(): Promise<import("buffer").Blob>;
18
+ json(): Promise<unknown>;
19
+ }>;
20
+ read(key: string): Promise<unknown>;
21
+ update(key: string, value: any): Promise<{
22
+ ok: boolean;
23
+ headers: [string, string][];
24
+ status: number;
25
+ blob(): Promise<import("buffer").Blob>;
26
+ json(): Promise<unknown>;
27
+ }>;
28
+ delete(key: string): Promise<boolean>;
29
+ list(): Promise<void>;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { Ed25519Signer } from '@did.coop/did-key-ed25519';
2
+ /**
3
+ * Create a new WAS space
4
+ * @returns {Promise<{ signer: InstanceType<typeof Ed25519Signer>; spaceId: `urn:uuid:${string}` }>}
5
+ */
6
+ export declare function createSpace(): Promise<{
7
+ signer: InstanceType<typeof Ed25519Signer>;
8
+ spaceId: `urn:uuid:${string}`;
9
+ }>;
@@ -1,4 +1,4 @@
1
- import { KeyPair, DidDocument, FormDataI, RecommendationCredential, Credential, RecommendationFormDataI, VerifiableCredential, EmploymentFormDataI, VolunteeringFormDataI, PerformanceReviewFormDataI } from '../../types/credential';
1
+ import { KeyPair, DidDocument, FormDataI, RecommendationCredential, Credential, RecommendationFormDataI, VerifiableCredential, EmploymentFormDataI, VolunteeringFormDataI, PerformanceReviewFormDataI } from '../../types';
2
2
  /**
3
3
  * Create a DID document using the provided key pair.
4
4
  * @param {KeyPair} keyPair - The key pair used to create the DID document.
@@ -0,0 +1,11 @@
1
+ import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ export interface AppInstanceKeyPair {
3
+ controller: string;
4
+ id: string;
5
+ publicKeyMultibase: string;
6
+ privateKeyMultibase: string;
7
+ }
8
+ export declare function getOrCreateAppInstanceDid(): Promise<{
9
+ did: string;
10
+ keyPair: Ed25519VerificationKey2020;
11
+ }>;
@@ -1,4 +1,4 @@
1
- import { GoogleDriveStorage } from '../models/GoogleDriveStorage.js';
1
+ import { GoogleDriveStorage } from '../models/GoogleDriveStorage';
2
2
  export type FileType = 'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR';
3
3
  interface SaveToGooglePropsI {
4
4
  storage: GoogleDriveStorage;
@@ -0,0 +1,27 @@
1
+ import { StorageClient } from '@wallet.storage/fetch-client';
2
+ import { Ed25519Signer } from '@did.coop/did-key-ed25519';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { WAS_BASE_URL } from '../../app.config.js';
5
+ /**
6
+ * Create a new WAS space
7
+ * @returns {Promise<{ signer: InstanceType<typeof Ed25519Signer>; spaceId: `urn:uuid:${string}` }>}
8
+ */
9
+ export async function createSpace() {
10
+ const signer = await Ed25519Signer.generate();
11
+ const controller = signer.id.split('#')[0];
12
+ const spaceUUID = uuidv4();
13
+ const spaceId = `urn:uuid:${spaceUUID}`;
14
+ const client = new StorageClient(new URL(WAS_BASE_URL));
15
+ const space = client.space({ signer, id: spaceId });
16
+ const spaceObject = {
17
+ id: spaceId,
18
+ controller,
19
+ };
20
+ const blob = new Blob([JSON.stringify(spaceObject)], { type: 'application/json' });
21
+ const res = await space.put(blob, { signer });
22
+ if (!res.ok) {
23
+ throw new Error(`Failed to initialize WAS space. Status: ${res.status}`);
24
+ }
25
+ console.log('✅ Provisioned and saved new WAS space');
26
+ return { signer, spaceId };
27
+ }
@@ -177,10 +177,7 @@ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid
177
177
  export function generateUnsignedEmployment({ formData, issuerDid }) {
178
178
  const issuanceDate = new Date().toISOString();
179
179
  const unsignedCredential = {
180
- '@context': [
181
- 'https://www.w3.org/2018/credentials/v1',
182
- employmentCredentialContext['@context'],
183
- ],
180
+ '@context': ['https://www.w3.org/2018/credentials/v1', employmentCredentialContext['@context']],
184
181
  id: '',
185
182
  type: ['VerifiableCredential', 'EmploymentCredential'],
186
183
  issuer: { id: issuerDid, type: ['Profile'] },
@@ -192,7 +189,7 @@ export function generateUnsignedEmployment({ formData, issuerDid }) {
192
189
  credentialName: formData.credentialName,
193
190
  credentialDuration: formData.credentialDuration,
194
191
  credentialDescription: formData.credentialDescription,
195
- portfolio: formData.portfolio.map(item => ({ name: item.name, url: item.url })),
192
+ portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
196
193
  evidenceLink: formData.evidenceLink,
197
194
  evidenceDescription: formData.evidenceDescription,
198
195
  company: formData.company,
@@ -208,10 +205,7 @@ export function generateUnsignedEmployment({ formData, issuerDid }) {
208
205
  export function generateUnsignedVolunteering({ formData, issuerDid }) {
209
206
  const issuanceDate = new Date().toISOString();
210
207
  const unsignedCredential = {
211
- '@context': [
212
- 'https://www.w3.org/2018/credentials/v1',
213
- volunteeringCredentialContext['@context'],
214
- ],
208
+ '@context': ['https://www.w3.org/2018/credentials/v1', volunteeringCredentialContext['@context']],
215
209
  id: '',
216
210
  type: ['VerifiableCredential', 'VolunteeringCredential'],
217
211
  issuer: { id: issuerDid, type: ['Profile'] },
@@ -223,10 +217,10 @@ export function generateUnsignedVolunteering({ formData, issuerDid }) {
223
217
  volunteerWork: formData.volunteerWork,
224
218
  volunteerOrg: formData.volunteerOrg,
225
219
  volunteerDescription: formData.volunteerDescription,
226
- skillsGained: formData.skillsGained ? formData.skillsGained.split(',').map(s => s.trim()) : undefined,
220
+ skillsGained: formData.skillsGained ? formData.skillsGained.split(',').map((s) => s.trim()) : undefined,
227
221
  duration: formData.duration,
228
222
  volunteerDates: formData.volunteerDates,
229
- portfolio: formData.portfolio.map(item => ({ name: item.name, url: item.url })),
223
+ portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
230
224
  evidenceLink: formData.evidenceLink,
231
225
  evidenceDescription: formData.evidenceDescription,
232
226
  },
@@ -240,10 +234,7 @@ export function generateUnsignedVolunteering({ formData, issuerDid }) {
240
234
  export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
241
235
  const issuanceDate = new Date().toISOString();
242
236
  const unsignedCredential = {
243
- '@context': [
244
- 'https://www.w3.org/2018/credentials/v1',
245
- performanceReviewCredentialContext['@context'],
246
- ],
237
+ '@context': ['https://www.w3.org/2018/credentials/v1', performanceReviewCredentialContext['@context']],
247
238
  id: '',
248
239
  type: ['VerifiableCredential', 'PerformanceReviewCredential'],
249
240
  issuer: { id: issuerDid, type: ['Profile'] },
@@ -266,7 +257,7 @@ export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
266
257
  overallRating: formData.overallRating,
267
258
  reviewComments: formData.reviewComments,
268
259
  goalsNext: formData.goalsNext,
269
- portfolio: formData.portfolio.map(item => ({ name: item.name, url: item.url })),
260
+ portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
270
261
  evidenceLink: formData.evidenceLink,
271
262
  evidenceDescription: formData.evidenceDescription,
272
263
  },
@@ -0,0 +1,22 @@
1
+ import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ const LOCAL_STORAGE_KEY = 'AppInstanceDID';
3
+ export async function getOrCreateAppInstanceDid() {
4
+ const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
5
+ if (stored) {
6
+ const parsed = JSON.parse(stored);
7
+ const keyPair = await Ed25519VerificationKey2020.from(parsed);
8
+ return { did: keyPair.controller, keyPair };
9
+ }
10
+ const keyPair = await Ed25519VerificationKey2020.generate();
11
+ keyPair.controller = `did:key:${keyPair.publicKeyMultibase}`;
12
+ keyPair.id = `${keyPair.controller}#${keyPair.publicKeyMultibase}`;
13
+ keyPair.revoked = false;
14
+ const did = keyPair.controller;
15
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify({
16
+ controller: keyPair.controller,
17
+ id: keyPair.id,
18
+ publicKeyMultibase: keyPair.publicKeyMultibase,
19
+ privateKeyMultibase: keyPair.privateKeyMultibase,
20
+ }));
21
+ return { did, keyPair };
22
+ }
@@ -1,7 +1,7 @@
1
1
  export const getVCWithRecommendations = async ({ vcId, storage }) => {
2
2
  try {
3
3
  const vcFolderId = await storage.getFileParents(vcId);
4
- const files = await storage.findFilesUnderFolder(vcFolderId);
4
+ const files = await storage.findFolderFiles(vcFolderId);
5
5
  const relationsFile = files.find((f) => f.name === 'RELATIONS');
6
6
  const relationsContent = await storage.retrieve(relationsFile.id);
7
7
  const relationsData = relationsContent.data.body ? JSON.parse(relationsContent.data.body) : relationsContent.data;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@cooperation/vc-storage",
3
3
  "type": "module",
4
- "version": "1.0.34",
4
+ "version": "1.0.40",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "app.config.js"
10
11
  ],
11
12
  "scripts": {
12
13
  "build": "tsc",
@@ -16,10 +17,12 @@
16
17
  "author": "cooperation",
17
18
  "license": "ISC",
18
19
  "dependencies": {
20
+ "@did.coop/did-key-ed25519": "^0.0.13",
19
21
  "@digitalbazaar/did-method-key": "^5.2.0",
20
22
  "@digitalbazaar/ed25519-signature-2020": "^5.4.0",
21
23
  "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
22
24
  "@digitalbazaar/vc": "^6.3.0",
25
+ "@wallet.storage/fetch-client": "^1.2.0",
23
26
  "add": "^2.0.6",
24
27
  "bnid": "^3.0.0",
25
28
  "crypto-js": "^4.2.0",
@@ -37,7 +40,7 @@
37
40
  "@types/jest": "^29.5.14",
38
41
  "@types/uuid": "^10.0.0",
39
42
  "babel-jest": "^29.7.0",
40
- "typescript": "^5.8.2",
43
+ "typescript": "^5.8.3",
41
44
  "vitest": "^3.0.5"
42
45
  }
43
46
  }
@@ -1,10 +0,0 @@
1
- /**
2
- * Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
3
- * @param {string} accessTokens - The access tokens for the user.
4
- * @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
5
- * @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
6
- * @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
7
- */
8
- export declare const createAndSignVerifiablePresentation: (accessTokens: string, vcFileId: string) => Promise<{
9
- signedPresentation: object;
10
- } | null>;
@@ -1,45 +0,0 @@
1
- /**
2
- * Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
3
- * @param {string} accessTokens - The access tokens for the user.
4
- * @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
5
- * @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
6
- * @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
7
- */
8
- export const createAndSignVerifiablePresentation = async (accessTokens, vcFileId) => {
9
- if (!accessTokens || !vcFileId) {
10
- console.error('Invalid input: Access tokens and VC file ID are required.');
11
- return null;
12
- }
13
- try {
14
- // const storage = new GoogleDriveStorage(accessTokens);
15
- // const engine = new CredentialEngine(accessTokens);
16
- // // Fetch Verifiable Credential (VC)
17
- // const verifiableCredential = await storage.retrieve(vcFileId);
18
- // if (!verifiableCredential) {
19
- // throw new Error('Verifiable Credential not found.');
20
- // }
21
- // // Fetch VC comments (potential recommendations)
22
- // const verifiableCredentialComments = await storage.getFileComments(vcFileId);
23
- // let recommendations: object[] = [];
24
- // // Extract recommendations from comments if present
25
- // if (verifiableCredentialComments.length > 0) {
26
- // for (const comment of verifiableCredentialComments) {
27
- // console.log('🚀 ~ createAndSignVerifiablePresentation ~ comment', comment);
28
- // const recommendationFile = await storage.retrieve(extractGoogleDriveFileId(comment.content));
29
- // console.log('🚀 ~ createAndSignVerifiablePresentation ~ recommendationFile', recommendationFile);
30
- // if (recommendationFile) {
31
- // recommendations.push(recommendationFile);
32
- // }
33
- // }
34
- // }
35
- // // Create Verifiable Presentation (VP) with the retrieved VC
36
- // const presentation = await engine.createPresentation([verifiableCredential.data, ...recommendations]); //! do not edit the array order!!
37
- // // Use the key pair to sign the presentation
38
- // const signedPresentation = await engine.signPresentation(presentation);
39
- // return {};
40
- }
41
- catch (error) {
42
- console.error('Error during Verifiable Presentation creation and signing:', error);
43
- return null;
44
- }
45
- };