@fileverse/api 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +5 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cloudflare.js +310 -32
- package/dist/cloudflare.js.map +1 -1
- package/dist/commands/index.js +96 -7
- package/dist/commands/index.js.map +1 -1
- package/dist/index.js +124 -29
- package/dist/index.js.map +1 -1
- package/dist/worker.js +99 -54
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/public/llm.txt +204 -478
package/dist/cloudflare.js
CHANGED
|
@@ -272,7 +272,9 @@ var init_files_model = __esm({
|
|
|
272
272
|
linkKey: fileRaw.linkKey,
|
|
273
273
|
linkKeyNonce: fileRaw.linkKeyNonce,
|
|
274
274
|
commentKey: fileRaw.commentKey,
|
|
275
|
-
link: fileRaw.link
|
|
275
|
+
link: fileRaw.link,
|
|
276
|
+
derivedKey: fileRaw.derivedKey,
|
|
277
|
+
secretKey: fileRaw.secretKey
|
|
276
278
|
};
|
|
277
279
|
}
|
|
278
280
|
static async findAll(portalAddress, limit, skip) {
|
|
@@ -356,10 +358,20 @@ var init_files_model = __esm({
|
|
|
356
358
|
const _id = uuidv7();
|
|
357
359
|
const sql = `
|
|
358
360
|
INSERT INTO ${this.TABLE}
|
|
359
|
-
(_id, title, content, ddocId, portalAddress)
|
|
360
|
-
VALUES (?, ?, ?, ?, ?)
|
|
361
|
+
(_id, title, content, ddocId, portalAddress, linkKey, linkKeyNonce, derivedKey, secretKey)
|
|
362
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
361
363
|
`;
|
|
362
|
-
await QueryBuilder.execute(sql, [
|
|
364
|
+
await QueryBuilder.execute(sql, [
|
|
365
|
+
_id,
|
|
366
|
+
input.title,
|
|
367
|
+
input.content,
|
|
368
|
+
input.ddocId,
|
|
369
|
+
input.portalAddress,
|
|
370
|
+
input.linkKey ?? null,
|
|
371
|
+
input.linkKeyNonce ?? null,
|
|
372
|
+
input.derivedKey ?? null,
|
|
373
|
+
input.secretKey ?? null
|
|
374
|
+
]);
|
|
363
375
|
const created = await this.findById(_id, input.portalAddress);
|
|
364
376
|
if (!created) {
|
|
365
377
|
throw new Error("Failed to create file");
|
|
@@ -735,6 +747,29 @@ var init_events_model = __esm({
|
|
|
735
747
|
`;
|
|
736
748
|
await QueryBuilder.execute(sql, [Date.now(), _id]);
|
|
737
749
|
}
|
|
750
|
+
static async markSubmitted(_id) {
|
|
751
|
+
const sql = `
|
|
752
|
+
UPDATE ${this.TABLE}
|
|
753
|
+
SET status = 'submitted',
|
|
754
|
+
lockedAt = NULL
|
|
755
|
+
WHERE _id = ?
|
|
756
|
+
`;
|
|
757
|
+
await QueryBuilder.execute(sql, [_id]);
|
|
758
|
+
}
|
|
759
|
+
static async findNextSubmitted(lockedFileIds) {
|
|
760
|
+
const exclusionClause = lockedFileIds.length > 0 ? `AND fileId NOT IN (${lockedFileIds.map(() => "?").join(", ")})` : "";
|
|
761
|
+
const sql = `
|
|
762
|
+
SELECT * FROM ${this.TABLE}
|
|
763
|
+
WHERE status = 'submitted'
|
|
764
|
+
AND userOpHash IS NOT NULL
|
|
765
|
+
${exclusionClause}
|
|
766
|
+
ORDER BY timestamp ASC
|
|
767
|
+
LIMIT 1
|
|
768
|
+
`;
|
|
769
|
+
const params = [...lockedFileIds];
|
|
770
|
+
const row = await QueryBuilder.selectOne(sql, params);
|
|
771
|
+
return row ? this.parseEvent(row) : void 0;
|
|
772
|
+
}
|
|
738
773
|
static async markProcessed(_id) {
|
|
739
774
|
const sql = `
|
|
740
775
|
UPDATE ${this.TABLE}
|
|
@@ -1275,7 +1310,7 @@ import { createSmartAccountClient } from "permissionless";
|
|
|
1275
1310
|
import { toSafeSmartAccount } from "permissionless/accounts";
|
|
1276
1311
|
import { entryPoint07Address } from "viem/account-abstraction";
|
|
1277
1312
|
import { generatePrivateKey } from "viem/accounts";
|
|
1278
|
-
var getPublicClient, getPimlicoClient, signerToSmartAccount, getSmartAccountClient, getNonce, waitForUserOpReceipt;
|
|
1313
|
+
var getPublicClient, getPimlicoClient, signerToSmartAccount, getSmartAccountClient, getNonce, getUserOpReceipt, waitForUserOpReceipt;
|
|
1279
1314
|
var init_pimlico_utils = __esm({
|
|
1280
1315
|
"src/sdk/pimlico-utils.ts"() {
|
|
1281
1316
|
"use strict";
|
|
@@ -1339,6 +1374,14 @@ var init_pimlico_utils = __esm({
|
|
|
1339
1374
|
size: 32
|
|
1340
1375
|
})
|
|
1341
1376
|
);
|
|
1377
|
+
getUserOpReceipt = async (hash2, authToken, portalAddress, invokerAddress) => {
|
|
1378
|
+
const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);
|
|
1379
|
+
try {
|
|
1380
|
+
return await pimlicoClient.getUserOperationReceipt({ hash: hash2 });
|
|
1381
|
+
} catch {
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1342
1385
|
waitForUserOpReceipt = async (hash2, authToken, portalAddress, invokerAddress, timeout = 12e4) => {
|
|
1343
1386
|
const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);
|
|
1344
1387
|
return pimlicoClient.waitForUserOperationReceipt({
|
|
@@ -2149,7 +2192,7 @@ import { fromUint8Array, toUint8Array } from "js-base64";
|
|
|
2149
2192
|
import { toAESKey, aesEncrypt } from "@fileverse/crypto/webcrypto";
|
|
2150
2193
|
import axios from "axios";
|
|
2151
2194
|
import { encodeFunctionData, parseEventLogs } from "viem";
|
|
2152
|
-
var deriveKeyFromAg2Hash,
|
|
2195
|
+
var deriveKeyFromAg2Hash, getExistingEncryptionMaterial, getNaclSecretKey, generateLinkKeyMaterial, jsonToFile, appendAuthTagIvToBlob, encryptFile, getNonceAppendedCipherText, jsonToBytes, buildLinklock, encryptTitleWithFileKey, uploadFileToIPFS, getEditFileTrxCalldata, getAddFileTrxCalldata, prepareCallData, prepareDeleteFileCallData, createEncryptedContentFile, buildFileMetadata, parseFileEventLog, uploadAllFilesToIPFS;
|
|
2153
2196
|
var init_file_utils = __esm({
|
|
2154
2197
|
"src/sdk/file-utils.ts"() {
|
|
2155
2198
|
"use strict";
|
|
@@ -2162,16 +2205,18 @@ var init_file_utils = __esm({
|
|
|
2162
2205
|
info: Buffer.from("encryptionKey")
|
|
2163
2206
|
});
|
|
2164
2207
|
};
|
|
2165
|
-
decryptSecretKey = async (docId, nonce, encryptedSecretKey) => {
|
|
2166
|
-
const derivedKey = await deriveKeyFromAg2Hash(docId, toUint8Array(nonce));
|
|
2167
|
-
return tweetnacl.secretbox.open(toUint8Array(encryptedSecretKey), toUint8Array(nonce), derivedKey);
|
|
2168
|
-
};
|
|
2169
2208
|
getExistingEncryptionMaterial = async (existingEncryptedSecretKey, existingNonce, docId) => {
|
|
2170
|
-
const
|
|
2209
|
+
const derivedKey = await deriveKeyFromAg2Hash(docId, toUint8Array(existingNonce));
|
|
2210
|
+
const secretKey = tweetnacl.secretbox.open(
|
|
2211
|
+
toUint8Array(existingEncryptedSecretKey),
|
|
2212
|
+
toUint8Array(existingNonce),
|
|
2213
|
+
derivedKey
|
|
2214
|
+
);
|
|
2171
2215
|
return {
|
|
2172
2216
|
encryptedSecretKey: existingEncryptedSecretKey,
|
|
2173
2217
|
nonce: toUint8Array(existingNonce),
|
|
2174
|
-
secretKey
|
|
2218
|
+
secretKey,
|
|
2219
|
+
derivedKey: new Uint8Array(derivedKey)
|
|
2175
2220
|
};
|
|
2176
2221
|
};
|
|
2177
2222
|
getNaclSecretKey = async (ddocId) => {
|
|
@@ -2179,19 +2224,19 @@ var init_file_utils = __esm({
|
|
|
2179
2224
|
const nonce = tweetnacl.randomBytes(tweetnacl.secretbox.nonceLength);
|
|
2180
2225
|
const derivedKey = await deriveKeyFromAg2Hash(ddocId, nonce);
|
|
2181
2226
|
const encryptedSecretKey = fromUint8Array(tweetnacl.secretbox(secretKey, nonce, derivedKey), true);
|
|
2182
|
-
return { nonce, encryptedSecretKey, secretKey };
|
|
2227
|
+
return { nonce, encryptedSecretKey, secretKey, derivedKey: new Uint8Array(derivedKey) };
|
|
2183
2228
|
};
|
|
2184
2229
|
generateLinkKeyMaterial = async (params) => {
|
|
2185
2230
|
if (params.linkKeyNonce && params.linkKey) {
|
|
2186
|
-
const { encryptedSecretKey: encryptedSecretKey2, nonce: nonce2, secretKey: secretKey2 } = await getExistingEncryptionMaterial(
|
|
2231
|
+
const { encryptedSecretKey: encryptedSecretKey2, nonce: nonce2, secretKey: secretKey2, derivedKey: derivedKey2 } = await getExistingEncryptionMaterial(
|
|
2187
2232
|
params.linkKey,
|
|
2188
2233
|
params.linkKeyNonce,
|
|
2189
2234
|
params.ddocId
|
|
2190
2235
|
);
|
|
2191
|
-
if (secretKey2) return { encryptedSecretKey: encryptedSecretKey2, nonce: nonce2, secretKey: secretKey2 };
|
|
2236
|
+
if (secretKey2) return { encryptedSecretKey: encryptedSecretKey2, nonce: nonce2, secretKey: secretKey2, derivedKey: derivedKey2 };
|
|
2192
2237
|
}
|
|
2193
|
-
const { secretKey, nonce, encryptedSecretKey } = await getNaclSecretKey(params.ddocId);
|
|
2194
|
-
return { secretKey, nonce, encryptedSecretKey };
|
|
2238
|
+
const { secretKey, nonce, encryptedSecretKey, derivedKey } = await getNaclSecretKey(params.ddocId);
|
|
2239
|
+
return { secretKey, nonce, encryptedSecretKey, derivedKey };
|
|
2195
2240
|
};
|
|
2196
2241
|
jsonToFile = (json2, fileName) => {
|
|
2197
2242
|
const blob = new Blob([JSON.stringify(json2)], {
|
|
@@ -2444,11 +2489,9 @@ var init_file_manager = __esm({
|
|
|
2444
2489
|
}
|
|
2445
2490
|
async submitAddFileTrx(file2) {
|
|
2446
2491
|
logger.debug(`Preparing to add file ${file2.ddocId}`);
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
linkKeyNonce: file2.linkKeyNonce
|
|
2451
|
-
});
|
|
2492
|
+
const encryptedSecretKey = file2.linkKey;
|
|
2493
|
+
const nonce = toUint8Array2(file2.linkKeyNonce);
|
|
2494
|
+
const secretKey = toUint8Array2(file2.secretKey);
|
|
2452
2495
|
const yJSContent = markdownToYjs(file2.content);
|
|
2453
2496
|
const { encryptedFile, key } = await createEncryptedContentFile(yJSContent);
|
|
2454
2497
|
logger.debug(`Generated encrypted content file for file ${file2.ddocId}`);
|
|
@@ -2492,13 +2535,59 @@ var init_file_manager = __esm({
|
|
|
2492
2535
|
metadata
|
|
2493
2536
|
};
|
|
2494
2537
|
}
|
|
2495
|
-
async
|
|
2496
|
-
logger.debug(`
|
|
2497
|
-
const
|
|
2538
|
+
async submitUpdateFile(file2) {
|
|
2539
|
+
logger.debug(`Submitting update for file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
|
|
2540
|
+
const encryptedSecretKey = file2.linkKey;
|
|
2541
|
+
const nonce = toUint8Array2(file2.linkKeyNonce);
|
|
2542
|
+
const secretKey = toUint8Array2(file2.secretKey);
|
|
2543
|
+
const yjsContent = markdownToYjs(file2.content);
|
|
2544
|
+
const { encryptedFile, key } = await createEncryptedContentFile(yjsContent);
|
|
2545
|
+
const commentKey = toUint8Array2(file2.commentKey);
|
|
2546
|
+
const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);
|
|
2547
|
+
const linkLock = buildLinklock(secretKey, toUint8Array2(key), commentKey);
|
|
2548
|
+
const encryptedTitle = await encryptTitleWithFileKey({
|
|
2549
|
+
title: file2.title || "Untitled",
|
|
2550
|
+
key
|
|
2551
|
+
});
|
|
2552
|
+
const metadata = buildFileMetadata({
|
|
2553
|
+
encryptedTitle,
|
|
2554
|
+
encryptedFileSize: encryptedFile.size,
|
|
2555
|
+
appLock,
|
|
2556
|
+
ownerLock,
|
|
2498
2557
|
ddocId: file2.ddocId,
|
|
2499
|
-
|
|
2500
|
-
|
|
2558
|
+
nonce: fromUint8Array2(nonce),
|
|
2559
|
+
owner: this.agentClient.getAgentAddress()
|
|
2501
2560
|
});
|
|
2561
|
+
const authParams = await this.getAuthParams();
|
|
2562
|
+
const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(
|
|
2563
|
+
{ metadata, encryptedFile, linkLock, ddocId: file2.ddocId },
|
|
2564
|
+
authParams
|
|
2565
|
+
);
|
|
2566
|
+
const callData = prepareCallData({
|
|
2567
|
+
metadataHash,
|
|
2568
|
+
contentHash,
|
|
2569
|
+
gateHash,
|
|
2570
|
+
appFileId: file2.ddocId,
|
|
2571
|
+
fileId: file2.onChainFileId
|
|
2572
|
+
});
|
|
2573
|
+
const userOpHash = await this.sendFileOperation(callData);
|
|
2574
|
+
logger.debug(`Submitted update user op for file ${file2.ddocId}`);
|
|
2575
|
+
return { userOpHash, metadata };
|
|
2576
|
+
}
|
|
2577
|
+
async submitDeleteFile(file2) {
|
|
2578
|
+
logger.debug(`Submitting delete for file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
|
|
2579
|
+
const callData = prepareDeleteFileCallData({
|
|
2580
|
+
onChainFileId: file2.onChainFileId
|
|
2581
|
+
});
|
|
2582
|
+
const userOpHash = await this.sendFileOperation(callData);
|
|
2583
|
+
logger.debug(`Submitted delete user op for file ${file2.ddocId}`);
|
|
2584
|
+
return { userOpHash };
|
|
2585
|
+
}
|
|
2586
|
+
async updateFile(file2) {
|
|
2587
|
+
logger.debug(`Updating file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
|
|
2588
|
+
const encryptedSecretKey = file2.linkKey;
|
|
2589
|
+
const nonce = toUint8Array2(file2.linkKeyNonce);
|
|
2590
|
+
const secretKey = toUint8Array2(file2.secretKey);
|
|
2502
2591
|
logger.debug(`Generating encrypted content file for file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
|
|
2503
2592
|
const yjsContent = markdownToYjs(file2.content);
|
|
2504
2593
|
const { encryptedFile, key } = await createEncryptedContentFile(yjsContent);
|
|
@@ -2583,7 +2672,7 @@ function deriveCollaboratorKeys(apiKeySeed) {
|
|
|
2583
2672
|
const { secretKey: ucanSecret } = generateKeyPairFromSeed(ucanDerivedSecret);
|
|
2584
2673
|
return { privateAccountKey, ucanSecret };
|
|
2585
2674
|
}
|
|
2586
|
-
var createFileManager, executeOperation, handleExistingFileOp, handleNewFileOp, getProxyAuthParams;
|
|
2675
|
+
var createFileManager, executeOperation, handleExistingFileOp, handleNewFileOp, getProxyAuthParams, submitUpdateFileOp, submitDeleteFileOp, resolveFileOp;
|
|
2587
2676
|
var init_publish = __esm({
|
|
2588
2677
|
"src/domain/portal/publish.ts"() {
|
|
2589
2678
|
"use strict";
|
|
@@ -2595,6 +2684,7 @@ var init_publish = __esm({
|
|
|
2595
2684
|
init_smart_agent();
|
|
2596
2685
|
init_file_manager();
|
|
2597
2686
|
init_config();
|
|
2687
|
+
init_pimlico_utils();
|
|
2598
2688
|
createFileManager = async (portalSeed, portalAddress, ucanSecret, privateAccountKey) => {
|
|
2599
2689
|
const keyPair = ucans2.EdKeypair.fromSecretKey(fromUint8Array3(ucanSecret), {
|
|
2600
2690
|
exportable: true
|
|
@@ -2657,6 +2747,50 @@ var init_publish = __esm({
|
|
|
2657
2747
|
);
|
|
2658
2748
|
return fileManager.getProxyAuthParams();
|
|
2659
2749
|
};
|
|
2750
|
+
submitUpdateFileOp = async (fileId) => {
|
|
2751
|
+
const { file: file2, portalDetails, apiKey } = await getPortalData(fileId);
|
|
2752
|
+
const apiKeySeed = toUint8Array3(apiKey);
|
|
2753
|
+
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2754
|
+
const fileManager = await createFileManager(
|
|
2755
|
+
portalDetails.portalSeed,
|
|
2756
|
+
portalDetails.portalAddress,
|
|
2757
|
+
ucanSecret,
|
|
2758
|
+
privateAccountKey
|
|
2759
|
+
);
|
|
2760
|
+
return fileManager.submitUpdateFile(file2);
|
|
2761
|
+
};
|
|
2762
|
+
submitDeleteFileOp = async (fileId) => {
|
|
2763
|
+
const { file: file2, portalDetails, apiKey } = await getPortalData(fileId);
|
|
2764
|
+
const apiKeySeed = toUint8Array3(apiKey);
|
|
2765
|
+
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2766
|
+
const fileManager = await createFileManager(
|
|
2767
|
+
portalDetails.portalSeed,
|
|
2768
|
+
portalDetails.portalAddress,
|
|
2769
|
+
ucanSecret,
|
|
2770
|
+
privateAccountKey
|
|
2771
|
+
);
|
|
2772
|
+
return fileManager.submitDeleteFile(file2);
|
|
2773
|
+
};
|
|
2774
|
+
resolveFileOp = async (fileId, userOpHash, eventType) => {
|
|
2775
|
+
const { portalDetails, apiKey } = await getPortalData(fileId);
|
|
2776
|
+
const apiKeySeed = toUint8Array3(apiKey);
|
|
2777
|
+
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2778
|
+
const fileManager = await createFileManager(
|
|
2779
|
+
portalDetails.portalSeed,
|
|
2780
|
+
portalDetails.portalAddress,
|
|
2781
|
+
ucanSecret,
|
|
2782
|
+
privateAccountKey
|
|
2783
|
+
);
|
|
2784
|
+
const { authToken, portalAddress, invokerAddress } = await fileManager.getProxyAuthParams();
|
|
2785
|
+
const receipt = await getUserOpReceipt(
|
|
2786
|
+
userOpHash,
|
|
2787
|
+
authToken,
|
|
2788
|
+
portalAddress,
|
|
2789
|
+
invokerAddress
|
|
2790
|
+
);
|
|
2791
|
+
if (!receipt) return null;
|
|
2792
|
+
return { receipt };
|
|
2793
|
+
};
|
|
2660
2794
|
}
|
|
2661
2795
|
});
|
|
2662
2796
|
|
|
@@ -2754,7 +2888,7 @@ var init_rate_limit = __esm({
|
|
|
2754
2888
|
});
|
|
2755
2889
|
|
|
2756
2890
|
// src/infra/worker/eventProcessor.ts
|
|
2757
|
-
var processEvent, onTransactionSuccess, processCreateEvent, processUpdateEvent, processDeleteEvent;
|
|
2891
|
+
var processEvent, submitEvent, resolveEvent, onTransactionSuccess, processCreateEvent, processUpdateEvent, processDeleteEvent;
|
|
2758
2892
|
var init_eventProcessor = __esm({
|
|
2759
2893
|
"src/infra/worker/eventProcessor.ts"() {
|
|
2760
2894
|
"use strict";
|
|
@@ -2792,6 +2926,132 @@ var init_eventProcessor = __esm({
|
|
|
2792
2926
|
return { success: false, error: errorMsg };
|
|
2793
2927
|
}
|
|
2794
2928
|
};
|
|
2929
|
+
submitEvent = async (event) => {
|
|
2930
|
+
const { fileId, type } = event;
|
|
2931
|
+
switch (type) {
|
|
2932
|
+
case "create": {
|
|
2933
|
+
const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
|
|
2934
|
+
if (!file2) throw new Error(`File ${fileId} not found`);
|
|
2935
|
+
if (file2.isDeleted === 1) {
|
|
2936
|
+
logger.info(`File ${fileId} is deleted, skipping create submit`);
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
const result = await handleNewFileOp(fileId);
|
|
2940
|
+
await EventsModel.setEventPendingOp(event._id, result.userOpHash, {
|
|
2941
|
+
linkKey: result.linkKey,
|
|
2942
|
+
linkKeyNonce: result.linkKeyNonce,
|
|
2943
|
+
commentKey: result.commentKey,
|
|
2944
|
+
metadata: result.metadata
|
|
2945
|
+
});
|
|
2946
|
+
logger.info(`File ${file2.ddocId} create op submitted (hash: ${result.userOpHash})`);
|
|
2947
|
+
break;
|
|
2948
|
+
}
|
|
2949
|
+
case "update": {
|
|
2950
|
+
const file2 = await FilesModel.findByIdExcludingDeleted(fileId);
|
|
2951
|
+
if (!file2) return;
|
|
2952
|
+
if (file2.localVersion <= file2.onchainVersion) return;
|
|
2953
|
+
const result = await submitUpdateFileOp(fileId);
|
|
2954
|
+
await EventsModel.setEventPendingOp(event._id, result.userOpHash, {
|
|
2955
|
+
metadata: result.metadata,
|
|
2956
|
+
localVersion: file2.localVersion
|
|
2957
|
+
});
|
|
2958
|
+
logger.info(`File ${file2.ddocId} update op submitted (hash: ${result.userOpHash})`);
|
|
2959
|
+
break;
|
|
2960
|
+
}
|
|
2961
|
+
case "delete": {
|
|
2962
|
+
const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
|
|
2963
|
+
if (!file2) return;
|
|
2964
|
+
if (file2.isDeleted === 1 && file2.syncStatus === "synced") {
|
|
2965
|
+
logger.info(`File ${fileId} deletion already synced, skipping`);
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
if (file2.onChainFileId === null || file2.onChainFileId === void 0) {
|
|
2969
|
+
await FilesModel.update(fileId, { syncStatus: "synced", isDeleted: 1 }, file2.portalAddress);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
const result = await submitDeleteFileOp(fileId);
|
|
2973
|
+
await EventsModel.setEventPendingOp(event._id, result.userOpHash, {});
|
|
2974
|
+
logger.info(`File ${file2.ddocId} delete op submitted (hash: ${result.userOpHash})`);
|
|
2975
|
+
break;
|
|
2976
|
+
}
|
|
2977
|
+
default:
|
|
2978
|
+
throw new Error(`Unknown event type: ${type}`);
|
|
2979
|
+
}
|
|
2980
|
+
};
|
|
2981
|
+
resolveEvent = async (event) => {
|
|
2982
|
+
const { fileId, type, userOpHash } = event;
|
|
2983
|
+
if (!userOpHash) {
|
|
2984
|
+
logger.warn(`Event ${event._id} has no userOpHash, cannot resolve`);
|
|
2985
|
+
return { resolved: false };
|
|
2986
|
+
}
|
|
2987
|
+
const result = await resolveFileOp(fileId, userOpHash, type);
|
|
2988
|
+
if (!result) {
|
|
2989
|
+
return { resolved: false };
|
|
2990
|
+
}
|
|
2991
|
+
const { receipt } = result;
|
|
2992
|
+
if (!receipt.success) {
|
|
2993
|
+
await EventsModel.clearEventPendingOp(event._id);
|
|
2994
|
+
throw new Error(`User operation failed: ${receipt.reason}`);
|
|
2995
|
+
}
|
|
2996
|
+
switch (type) {
|
|
2997
|
+
case "create": {
|
|
2998
|
+
const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
|
|
2999
|
+
if (!file2) throw new Error(`File ${fileId} not found during resolve`);
|
|
3000
|
+
const onChainFileId = parseFileEventLog(receipt.logs, "AddedFile", ADDED_FILE_EVENT);
|
|
3001
|
+
const pending = JSON.parse(event.pendingPayload);
|
|
3002
|
+
const frontendUrl = getRuntimeConfig().FRONTEND_URL;
|
|
3003
|
+
const payload = {
|
|
3004
|
+
onchainVersion: file2.localVersion,
|
|
3005
|
+
onChainFileId,
|
|
3006
|
+
linkKey: pending.linkKey,
|
|
3007
|
+
linkKeyNonce: pending.linkKeyNonce,
|
|
3008
|
+
commentKey: pending.commentKey,
|
|
3009
|
+
metadata: pending.metadata,
|
|
3010
|
+
link: `${frontendUrl}/${file2.portalAddress}/${onChainFileId}#key=${pending.linkKey}`
|
|
3011
|
+
};
|
|
3012
|
+
const updatedFile = await FilesModel.update(fileId, payload, file2.portalAddress);
|
|
3013
|
+
if (updatedFile.localVersion === updatedFile.onchainVersion) {
|
|
3014
|
+
await FilesModel.update(fileId, { syncStatus: "synced" }, file2.portalAddress);
|
|
3015
|
+
}
|
|
3016
|
+
await EventsModel.clearEventPendingOp(event._id);
|
|
3017
|
+
logger.info(`File ${file2.ddocId} create resolved successfully`);
|
|
3018
|
+
break;
|
|
3019
|
+
}
|
|
3020
|
+
case "update": {
|
|
3021
|
+
const file2 = await FilesModel.findByIdExcludingDeleted(fileId);
|
|
3022
|
+
if (!file2) throw new Error(`File ${fileId} not found during resolve`);
|
|
3023
|
+
parseFileEventLog(receipt.logs, "EditedFile", EDITED_FILE_EVENT);
|
|
3024
|
+
const pending = JSON.parse(event.pendingPayload);
|
|
3025
|
+
const payload = {
|
|
3026
|
+
onchainVersion: pending.localVersion,
|
|
3027
|
+
metadata: pending.metadata
|
|
3028
|
+
};
|
|
3029
|
+
const updatedFile = await FilesModel.update(fileId, payload, file2.portalAddress);
|
|
3030
|
+
if (updatedFile.localVersion === updatedFile.onchainVersion) {
|
|
3031
|
+
await FilesModel.update(fileId, { syncStatus: "synced" }, file2.portalAddress);
|
|
3032
|
+
}
|
|
3033
|
+
await EventsModel.clearEventPendingOp(event._id);
|
|
3034
|
+
logger.info(`File ${file2.ddocId} update resolved successfully`);
|
|
3035
|
+
break;
|
|
3036
|
+
}
|
|
3037
|
+
case "delete": {
|
|
3038
|
+
const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
|
|
3039
|
+
if (!file2) throw new Error(`File ${fileId} not found during resolve`);
|
|
3040
|
+
parseFileEventLog(receipt.logs, "DeletedFile", DELETED_FILE_EVENT);
|
|
3041
|
+
await FilesModel.update(
|
|
3042
|
+
fileId,
|
|
3043
|
+
{ syncStatus: "synced", isDeleted: 1, onchainVersion: file2.localVersion },
|
|
3044
|
+
file2.portalAddress
|
|
3045
|
+
);
|
|
3046
|
+
await EventsModel.clearEventPendingOp(event._id);
|
|
3047
|
+
logger.info(`File ${fileId} delete resolved successfully`);
|
|
3048
|
+
break;
|
|
3049
|
+
}
|
|
3050
|
+
default:
|
|
3051
|
+
throw new Error(`Unknown event type: ${type}`);
|
|
3052
|
+
}
|
|
3053
|
+
return { resolved: true };
|
|
3054
|
+
};
|
|
2795
3055
|
onTransactionSuccess = async (fileId, file2, onChainFileId, pending) => {
|
|
2796
3056
|
const frontendUrl = getRuntimeConfig().FRONTEND_URL;
|
|
2797
3057
|
const payload = {
|
|
@@ -3378,7 +3638,9 @@ CREATE TABLE IF NOT EXISTS files (
|
|
|
3378
3638
|
commentKey TEXT,
|
|
3379
3639
|
linkKey TEXT,
|
|
3380
3640
|
linkKeyNonce TEXT,
|
|
3381
|
-
link TEXT
|
|
3641
|
+
link TEXT,
|
|
3642
|
+
derivedKey TEXT,
|
|
3643
|
+
secretKey TEXT
|
|
3382
3644
|
);
|
|
3383
3645
|
CREATE INDEX IF NOT EXISTS idx_files_createdAt ON files(createdAt);
|
|
3384
3646
|
CREATE INDEX IF NOT EXISTS idx_files_syncStatus ON files(syncStatus);
|
|
@@ -3409,7 +3671,7 @@ CREATE TABLE IF NOT EXISTS events (
|
|
|
3409
3671
|
type TEXT NOT NULL CHECK (type IN ('create', 'update', 'delete')),
|
|
3410
3672
|
timestamp BIGINT NOT NULL,
|
|
3411
3673
|
fileId TEXT NOT NULL,
|
|
3412
|
-
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'processed', 'failed')),
|
|
3674
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'submitted', 'processed', 'failed')),
|
|
3413
3675
|
retryCount INTEGER NOT NULL DEFAULT 0,
|
|
3414
3676
|
lastError TEXT,
|
|
3415
3677
|
lockedAt BIGINT,
|
|
@@ -3456,7 +3718,9 @@ init_models();
|
|
|
3456
3718
|
init_esm_shims();
|
|
3457
3719
|
init_models();
|
|
3458
3720
|
init_constants3();
|
|
3721
|
+
init_file_utils();
|
|
3459
3722
|
import { generate } from "short-uuid";
|
|
3723
|
+
import { fromUint8Array as fromUint8Array4 } from "js-base64";
|
|
3460
3724
|
async function listFiles(params) {
|
|
3461
3725
|
const { limit, skip, portalAddress } = params;
|
|
3462
3726
|
const effectiveLimit = limit || DEFAULT_LIST_LIMIT;
|
|
@@ -3509,11 +3773,20 @@ var createFile = async (input) => {
|
|
|
3509
3773
|
throw new Error("title, content, and portalAddress are required");
|
|
3510
3774
|
}
|
|
3511
3775
|
const ddocId = generate();
|
|
3776
|
+
const { encryptedSecretKey, nonce, secretKey, derivedKey } = await generateLinkKeyMaterial({
|
|
3777
|
+
ddocId,
|
|
3778
|
+
linkKey: void 0,
|
|
3779
|
+
linkKeyNonce: void 0
|
|
3780
|
+
});
|
|
3512
3781
|
const file2 = await FilesModel.create({
|
|
3513
3782
|
title: input.title,
|
|
3514
3783
|
content: input.content,
|
|
3515
3784
|
ddocId,
|
|
3516
|
-
portalAddress: input.portalAddress
|
|
3785
|
+
portalAddress: input.portalAddress,
|
|
3786
|
+
linkKey: encryptedSecretKey,
|
|
3787
|
+
linkKeyNonce: fromUint8Array4(nonce),
|
|
3788
|
+
derivedKey: fromUint8Array4(derivedKey),
|
|
3789
|
+
secretKey: fromUint8Array4(secretKey)
|
|
3517
3790
|
});
|
|
3518
3791
|
await EventsModel.create({ type: "create", fileId: file2._id, portalAddress: file2.portalAddress });
|
|
3519
3792
|
return file2;
|
|
@@ -17848,10 +18121,15 @@ export {
|
|
|
17848
18121
|
listFiles,
|
|
17849
18122
|
listFolders,
|
|
17850
18123
|
processEvent,
|
|
18124
|
+
resolveEvent,
|
|
18125
|
+
resolveFileOp,
|
|
17851
18126
|
runMigrations,
|
|
17852
18127
|
savePortal,
|
|
17853
18128
|
searchNodes,
|
|
17854
18129
|
setAdapter,
|
|
18130
|
+
submitDeleteFileOp,
|
|
18131
|
+
submitEvent,
|
|
18132
|
+
submitUpdateFileOp,
|
|
17855
18133
|
updateFile
|
|
17856
18134
|
};
|
|
17857
18135
|
/*! Bundled license information:
|