@reverbia/sdk 1.0.0-next.20251219092050 → 1.0.0-next.20251219162520
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/expo/index.cjs +71 -8
- package/dist/expo/index.d.mts +6 -0
- package/dist/expo/index.d.ts +6 -0
- package/dist/expo/index.mjs +71 -8
- package/dist/react/index.cjs +833 -36
- package/dist/react/index.d.mts +353 -8
- package/dist/react/index.d.ts +353 -8
- package/dist/react/index.mjs +833 -36
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -1704,7 +1704,7 @@ import {
|
|
|
1704
1704
|
addColumns
|
|
1705
1705
|
} from "@nozbe/watermelondb/Schema/migrations";
|
|
1706
1706
|
var chatStorageSchema = appSchema({
|
|
1707
|
-
version:
|
|
1707
|
+
version: 3,
|
|
1708
1708
|
tables: [
|
|
1709
1709
|
tableSchema({
|
|
1710
1710
|
name: "history",
|
|
@@ -1722,7 +1722,8 @@ var chatStorageSchema = appSchema({
|
|
|
1722
1722
|
{ name: "usage", type: "string", isOptional: true },
|
|
1723
1723
|
{ name: "sources", type: "string", isOptional: true },
|
|
1724
1724
|
{ name: "response_duration", type: "number", isOptional: true },
|
|
1725
|
-
{ name: "was_stopped", type: "boolean", isOptional: true }
|
|
1725
|
+
{ name: "was_stopped", type: "boolean", isOptional: true },
|
|
1726
|
+
{ name: "error", type: "string", isOptional: true }
|
|
1726
1727
|
]
|
|
1727
1728
|
}),
|
|
1728
1729
|
tableSchema({
|
|
@@ -1747,6 +1748,15 @@ var chatStorageMigrations = schemaMigrations({
|
|
|
1747
1748
|
columns: [{ name: "was_stopped", type: "boolean", isOptional: true }]
|
|
1748
1749
|
})
|
|
1749
1750
|
]
|
|
1751
|
+
},
|
|
1752
|
+
{
|
|
1753
|
+
toVersion: 3,
|
|
1754
|
+
steps: [
|
|
1755
|
+
addColumns({
|
|
1756
|
+
table: "history",
|
|
1757
|
+
columns: [{ name: "error", type: "string", isOptional: true }]
|
|
1758
|
+
})
|
|
1759
|
+
]
|
|
1750
1760
|
}
|
|
1751
1761
|
]
|
|
1752
1762
|
});
|
|
@@ -1802,6 +1812,9 @@ __decorateClass([
|
|
|
1802
1812
|
__decorateClass([
|
|
1803
1813
|
field("was_stopped")
|
|
1804
1814
|
], Message.prototype, "wasStopped", 2);
|
|
1815
|
+
__decorateClass([
|
|
1816
|
+
text("error")
|
|
1817
|
+
], Message.prototype, "error", 2);
|
|
1805
1818
|
var Conversation = class extends Model {
|
|
1806
1819
|
};
|
|
1807
1820
|
Conversation.table = "conversations";
|
|
@@ -1856,7 +1869,8 @@ function messageToStored(message) {
|
|
|
1856
1869
|
usage: message.usage,
|
|
1857
1870
|
sources: message.sources,
|
|
1858
1871
|
responseDuration: message.responseDuration,
|
|
1859
|
-
wasStopped: message.wasStopped
|
|
1872
|
+
wasStopped: message.wasStopped,
|
|
1873
|
+
error: message.error
|
|
1860
1874
|
};
|
|
1861
1875
|
}
|
|
1862
1876
|
function conversationToStored(conversation) {
|
|
@@ -1946,6 +1960,7 @@ async function createMessageOp(ctx, opts) {
|
|
|
1946
1960
|
if (opts.vector) msg._setRaw("vector", JSON.stringify(opts.vector));
|
|
1947
1961
|
if (opts.embeddingModel) msg._setRaw("embedding_model", opts.embeddingModel);
|
|
1948
1962
|
if (opts.wasStopped) msg._setRaw("was_stopped", opts.wasStopped);
|
|
1963
|
+
if (opts.error) msg._setRaw("error", opts.error);
|
|
1949
1964
|
});
|
|
1950
1965
|
});
|
|
1951
1966
|
return messageToStored(created);
|
|
@@ -1965,6 +1980,20 @@ async function updateMessageEmbeddingOp(ctx, uniqueId, vector, embeddingModel) {
|
|
|
1965
1980
|
});
|
|
1966
1981
|
return messageToStored(message);
|
|
1967
1982
|
}
|
|
1983
|
+
async function updateMessageErrorOp(ctx, uniqueId, error) {
|
|
1984
|
+
let message;
|
|
1985
|
+
try {
|
|
1986
|
+
message = await ctx.messagesCollection.find(uniqueId);
|
|
1987
|
+
} catch {
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
await ctx.database.write(async () => {
|
|
1991
|
+
await message.update((msg) => {
|
|
1992
|
+
msg._setRaw("error", error);
|
|
1993
|
+
});
|
|
1994
|
+
});
|
|
1995
|
+
return messageToStored(message);
|
|
1996
|
+
}
|
|
1968
1997
|
function cosineSimilarity(a, b) {
|
|
1969
1998
|
if (a.length !== b.length) return 0;
|
|
1970
1999
|
let dotProduct = 0;
|
|
@@ -2182,7 +2211,8 @@ function useChatStorage(options) {
|
|
|
2182
2211
|
let messagesToSend = [];
|
|
2183
2212
|
if (includeHistory && !providedMessages) {
|
|
2184
2213
|
const storedMessages = await getMessages(convId);
|
|
2185
|
-
const
|
|
2214
|
+
const validMessages = storedMessages.filter((msg) => !msg.error);
|
|
2215
|
+
const limitedMessages = validMessages.slice(-maxHistoryMessages);
|
|
2186
2216
|
messagesToSend = limitedMessages.map(storedToLlmapiMessage);
|
|
2187
2217
|
} else if (providedMessages) {
|
|
2188
2218
|
messagesToSend = providedMessages;
|
|
@@ -2281,14 +2311,37 @@ function useChatStorage(options) {
|
|
|
2281
2311
|
userMessage: storedUserMessage,
|
|
2282
2312
|
assistantMessage: storedAssistantMessage2
|
|
2283
2313
|
};
|
|
2284
|
-
} catch
|
|
2314
|
+
} catch {
|
|
2315
|
+
return {
|
|
2316
|
+
data: null,
|
|
2317
|
+
error: "Request aborted",
|
|
2318
|
+
toolExecution: abortedResult.toolExecution,
|
|
2319
|
+
userMessage: storedUserMessage
|
|
2320
|
+
};
|
|
2285
2321
|
}
|
|
2286
2322
|
}
|
|
2323
|
+
const errorMessage = result.error || "No response data received";
|
|
2324
|
+
try {
|
|
2325
|
+
await updateMessageErrorOp(
|
|
2326
|
+
storageCtx,
|
|
2327
|
+
storedUserMessage.uniqueId,
|
|
2328
|
+
errorMessage
|
|
2329
|
+
);
|
|
2330
|
+
await createMessageOp(storageCtx, {
|
|
2331
|
+
conversationId: convId,
|
|
2332
|
+
role: "assistant",
|
|
2333
|
+
content: "",
|
|
2334
|
+
model: model || "",
|
|
2335
|
+
responseDuration,
|
|
2336
|
+
error: errorMessage
|
|
2337
|
+
});
|
|
2338
|
+
} catch {
|
|
2339
|
+
}
|
|
2287
2340
|
return {
|
|
2288
2341
|
data: null,
|
|
2289
|
-
error:
|
|
2342
|
+
error: errorMessage,
|
|
2290
2343
|
toolExecution: result.toolExecution,
|
|
2291
|
-
userMessage: storedUserMessage
|
|
2344
|
+
userMessage: { ...storedUserMessage, error: errorMessage }
|
|
2292
2345
|
};
|
|
2293
2346
|
}
|
|
2294
2347
|
const responseData = result.data;
|
|
@@ -2430,7 +2483,7 @@ __decorateClass([
|
|
|
2430
2483
|
], ModelPreference.prototype, "models", 2);
|
|
2431
2484
|
|
|
2432
2485
|
// src/lib/db/schema.ts
|
|
2433
|
-
var SDK_SCHEMA_VERSION =
|
|
2486
|
+
var SDK_SCHEMA_VERSION = 5;
|
|
2434
2487
|
var sdkSchema = appSchema2({
|
|
2435
2488
|
version: SDK_SCHEMA_VERSION,
|
|
2436
2489
|
tables: [
|
|
@@ -2451,7 +2504,8 @@ var sdkSchema = appSchema2({
|
|
|
2451
2504
|
{ name: "usage", type: "string", isOptional: true },
|
|
2452
2505
|
{ name: "sources", type: "string", isOptional: true },
|
|
2453
2506
|
{ name: "response_duration", type: "number", isOptional: true },
|
|
2454
|
-
{ name: "was_stopped", type: "boolean", isOptional: true }
|
|
2507
|
+
{ name: "was_stopped", type: "boolean", isOptional: true },
|
|
2508
|
+
{ name: "error", type: "string", isOptional: true }
|
|
2455
2509
|
]
|
|
2456
2510
|
}),
|
|
2457
2511
|
tableSchema2({
|
|
@@ -2518,6 +2572,16 @@ var sdkMigrations = schemaMigrations2({
|
|
|
2518
2572
|
]
|
|
2519
2573
|
})
|
|
2520
2574
|
]
|
|
2575
|
+
},
|
|
2576
|
+
// v4 -> v5: Added error column to history for error persistence
|
|
2577
|
+
{
|
|
2578
|
+
toVersion: 5,
|
|
2579
|
+
steps: [
|
|
2580
|
+
addColumns2({
|
|
2581
|
+
table: "history",
|
|
2582
|
+
columns: [{ name: "error", type: "string", isOptional: true }]
|
|
2583
|
+
})
|
|
2584
|
+
]
|
|
2521
2585
|
}
|
|
2522
2586
|
]
|
|
2523
2587
|
});
|
|
@@ -5575,7 +5639,7 @@ function useGoogleDriveBackup(options) {
|
|
|
5575
5639
|
};
|
|
5576
5640
|
}
|
|
5577
5641
|
|
|
5578
|
-
// src/react/
|
|
5642
|
+
// src/react/useICloudAuth.ts
|
|
5579
5643
|
import {
|
|
5580
5644
|
createContext as createContext3,
|
|
5581
5645
|
createElement as createElement3,
|
|
@@ -5584,20 +5648,593 @@ import {
|
|
|
5584
5648
|
useEffect as useEffect8,
|
|
5585
5649
|
useState as useState12
|
|
5586
5650
|
} from "react";
|
|
5587
|
-
|
|
5651
|
+
|
|
5652
|
+
// src/lib/backup/icloud/api.ts
|
|
5653
|
+
var CLOUDKIT_JS_URL = "https://cdn.apple-cloudkit.com/ck/2/cloudkit.js";
|
|
5654
|
+
var DEFAULT_BACKUP_FOLDER2 = "conversations";
|
|
5655
|
+
var DEFAULT_CONTAINER_ID = "iCloud.Memoryless";
|
|
5656
|
+
var RECORD_TYPE = "ConversationBackup";
|
|
5657
|
+
var cloudKitLoadPromise = null;
|
|
5658
|
+
function isCloudKitAvailable() {
|
|
5659
|
+
return typeof window !== "undefined" && !!window.CloudKit;
|
|
5660
|
+
}
|
|
5661
|
+
async function loadCloudKit() {
|
|
5662
|
+
if (typeof window === "undefined") {
|
|
5663
|
+
throw new Error("CloudKit JS can only be loaded in browser environment");
|
|
5664
|
+
}
|
|
5665
|
+
if (window.CloudKit) {
|
|
5666
|
+
return;
|
|
5667
|
+
}
|
|
5668
|
+
if (cloudKitLoadPromise) {
|
|
5669
|
+
return cloudKitLoadPromise;
|
|
5670
|
+
}
|
|
5671
|
+
cloudKitLoadPromise = new Promise((resolve, reject) => {
|
|
5672
|
+
const script = document.createElement("script");
|
|
5673
|
+
script.src = CLOUDKIT_JS_URL;
|
|
5674
|
+
script.async = true;
|
|
5675
|
+
script.onload = () => {
|
|
5676
|
+
if (window.CloudKit) {
|
|
5677
|
+
resolve();
|
|
5678
|
+
} else {
|
|
5679
|
+
reject(new Error("CloudKit JS loaded but CloudKit object not found"));
|
|
5680
|
+
}
|
|
5681
|
+
};
|
|
5682
|
+
script.onerror = () => {
|
|
5683
|
+
cloudKitLoadPromise = null;
|
|
5684
|
+
reject(new Error("Failed to load CloudKit JS"));
|
|
5685
|
+
};
|
|
5686
|
+
document.head.appendChild(script);
|
|
5687
|
+
});
|
|
5688
|
+
return cloudKitLoadPromise;
|
|
5689
|
+
}
|
|
5690
|
+
async function ensureCloudKitLoaded() {
|
|
5691
|
+
if (!isCloudKitAvailable()) {
|
|
5692
|
+
await loadCloudKit();
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
async function configureCloudKit(config) {
|
|
5696
|
+
await ensureCloudKitLoaded();
|
|
5697
|
+
ensureAuthElements();
|
|
5698
|
+
window.CloudKit.configure({
|
|
5699
|
+
containers: [
|
|
5700
|
+
{
|
|
5701
|
+
containerIdentifier: config.containerIdentifier,
|
|
5702
|
+
apiTokenAuth: {
|
|
5703
|
+
apiToken: config.apiToken,
|
|
5704
|
+
persist: true,
|
|
5705
|
+
signInButton: {
|
|
5706
|
+
id: "apple-sign-in-button",
|
|
5707
|
+
theme: "black"
|
|
5708
|
+
},
|
|
5709
|
+
signOutButton: {
|
|
5710
|
+
id: "apple-sign-out-button",
|
|
5711
|
+
theme: "black"
|
|
5712
|
+
}
|
|
5713
|
+
},
|
|
5714
|
+
environment: config.environment
|
|
5715
|
+
}
|
|
5716
|
+
]
|
|
5717
|
+
});
|
|
5718
|
+
}
|
|
5719
|
+
async function getContainer() {
|
|
5720
|
+
await ensureCloudKitLoaded();
|
|
5721
|
+
return window.CloudKit.getDefaultContainer();
|
|
5722
|
+
}
|
|
5723
|
+
function ensureAuthElements() {
|
|
5724
|
+
let signInButton = document.getElementById("apple-sign-in-button");
|
|
5725
|
+
let signOutButton = document.getElementById("apple-sign-out-button");
|
|
5726
|
+
if (!signInButton) {
|
|
5727
|
+
signInButton = document.createElement("div");
|
|
5728
|
+
signInButton.id = "apple-sign-in-button";
|
|
5729
|
+
signInButton.style.position = "fixed";
|
|
5730
|
+
signInButton.style.top = "-9999px";
|
|
5731
|
+
signInButton.style.left = "-9999px";
|
|
5732
|
+
document.body.appendChild(signInButton);
|
|
5733
|
+
}
|
|
5734
|
+
if (!signOutButton) {
|
|
5735
|
+
signOutButton = document.createElement("div");
|
|
5736
|
+
signOutButton.id = "apple-sign-out-button";
|
|
5737
|
+
signOutButton.style.position = "fixed";
|
|
5738
|
+
signOutButton.style.top = "-9999px";
|
|
5739
|
+
signOutButton.style.left = "-9999px";
|
|
5740
|
+
document.body.appendChild(signOutButton);
|
|
5741
|
+
}
|
|
5742
|
+
return { signIn: signInButton, signOut: signOutButton };
|
|
5743
|
+
}
|
|
5744
|
+
async function authenticateICloud() {
|
|
5745
|
+
const container = await getContainer();
|
|
5746
|
+
ensureAuthElements();
|
|
5747
|
+
return container.setUpAuth();
|
|
5748
|
+
}
|
|
5749
|
+
async function requestICloudSignIn() {
|
|
5750
|
+
const container = await getContainer();
|
|
5751
|
+
const { signIn } = ensureAuthElements();
|
|
5752
|
+
const existingUser = await container.setUpAuth();
|
|
5753
|
+
if (existingUser) {
|
|
5754
|
+
return existingUser;
|
|
5755
|
+
}
|
|
5756
|
+
console.log("[CloudKit] Sign-in container innerHTML:", signIn.innerHTML);
|
|
5757
|
+
console.log("[CloudKit] Sign-in container children:", signIn.children.length);
|
|
5758
|
+
const appleButton = signIn.querySelector("a, button, [role='button'], div[id*='apple']");
|
|
5759
|
+
console.log("[CloudKit] Found button element:", appleButton);
|
|
5760
|
+
if (appleButton) {
|
|
5761
|
+
console.log("[CloudKit] Clicking button...");
|
|
5762
|
+
appleButton.click();
|
|
5763
|
+
} else {
|
|
5764
|
+
const anyClickable = signIn.firstElementChild;
|
|
5765
|
+
if (anyClickable) {
|
|
5766
|
+
console.log("[CloudKit] Clicking first child element:", anyClickable);
|
|
5767
|
+
anyClickable.click();
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
return container.whenUserSignsIn();
|
|
5771
|
+
}
|
|
5772
|
+
async function uploadFileToICloud(filename, content) {
|
|
5773
|
+
const container = await getContainer();
|
|
5774
|
+
const database = container.privateCloudDatabase;
|
|
5775
|
+
const recordName = `backup_${filename.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
5776
|
+
const arrayBuffer = await content.arrayBuffer();
|
|
5777
|
+
const base64Data = btoa(
|
|
5778
|
+
String.fromCharCode(...new Uint8Array(arrayBuffer))
|
|
5779
|
+
);
|
|
5780
|
+
const record = {
|
|
5781
|
+
recordType: RECORD_TYPE,
|
|
5782
|
+
recordName,
|
|
5783
|
+
fields: {
|
|
5784
|
+
filename: { value: filename },
|
|
5785
|
+
data: { value: base64Data },
|
|
5786
|
+
size: { value: content.size },
|
|
5787
|
+
contentType: { value: content.type || "application/json" }
|
|
5788
|
+
}
|
|
5789
|
+
};
|
|
5790
|
+
const response = await database.saveRecords(record);
|
|
5791
|
+
if (!response.records || response.records.length === 0) {
|
|
5792
|
+
throw new Error("Failed to upload file to iCloud");
|
|
5793
|
+
}
|
|
5794
|
+
const savedRecord = response.records[0];
|
|
5795
|
+
return {
|
|
5796
|
+
recordName: savedRecord.recordName,
|
|
5797
|
+
filename,
|
|
5798
|
+
modifiedAt: new Date(savedRecord.modified?.timestamp ?? Date.now()),
|
|
5799
|
+
size: content.size
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5802
|
+
async function listICloudFiles() {
|
|
5803
|
+
const container = await getContainer();
|
|
5804
|
+
const database = container.privateCloudDatabase;
|
|
5805
|
+
const query = {
|
|
5806
|
+
recordType: RECORD_TYPE
|
|
5807
|
+
// Note: Sorting requires SORTABLE index on the field in CloudKit Dashboard
|
|
5808
|
+
// For now, we skip sorting and sort client-side after fetching
|
|
5809
|
+
};
|
|
5810
|
+
const allRecords = [];
|
|
5811
|
+
let response = await database.performQuery(query);
|
|
5812
|
+
if (response.records) {
|
|
5813
|
+
allRecords.push(...response.records);
|
|
5814
|
+
}
|
|
5815
|
+
while (response.continuationMarker) {
|
|
5816
|
+
break;
|
|
5817
|
+
}
|
|
5818
|
+
const files = allRecords.map((record) => ({
|
|
5819
|
+
recordName: record.recordName,
|
|
5820
|
+
filename: record.fields.filename?.value ?? "",
|
|
5821
|
+
modifiedAt: new Date(record.modified?.timestamp ?? Date.now()),
|
|
5822
|
+
size: typeof record.fields.data?.value === "object" && record.fields.data?.value !== null ? record.fields.data.value.size : 0
|
|
5823
|
+
}));
|
|
5824
|
+
return files.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
|
|
5825
|
+
}
|
|
5826
|
+
async function downloadICloudFile(recordName) {
|
|
5827
|
+
const container = await getContainer();
|
|
5828
|
+
const database = container.privateCloudDatabase;
|
|
5829
|
+
const response = await database.fetchRecords([{ recordName }], {
|
|
5830
|
+
desiredKeys: ["filename", "data", "contentType"]
|
|
5831
|
+
});
|
|
5832
|
+
if (!response.records || response.records.length === 0) {
|
|
5833
|
+
throw new Error(`File not found: ${recordName}`);
|
|
5834
|
+
}
|
|
5835
|
+
const record = response.records[0];
|
|
5836
|
+
const dataField = record.fields.data?.value;
|
|
5837
|
+
if (!dataField) {
|
|
5838
|
+
throw new Error("No data in record");
|
|
5839
|
+
}
|
|
5840
|
+
if (typeof dataField === "string") {
|
|
5841
|
+
const binaryString = atob(dataField);
|
|
5842
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
5843
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
5844
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
5845
|
+
}
|
|
5846
|
+
return new Blob([bytes], { type: "application/json" });
|
|
5847
|
+
}
|
|
5848
|
+
if (typeof dataField === "object" && "downloadURL" in dataField) {
|
|
5849
|
+
const fetchResponse = await fetch(
|
|
5850
|
+
dataField.downloadURL
|
|
5851
|
+
);
|
|
5852
|
+
if (!fetchResponse.ok) {
|
|
5853
|
+
throw new Error(`Failed to download from iCloud: ${fetchResponse.status}`);
|
|
5854
|
+
}
|
|
5855
|
+
return fetchResponse.blob();
|
|
5856
|
+
}
|
|
5857
|
+
throw new Error("Unknown data format in iCloud record");
|
|
5858
|
+
}
|
|
5859
|
+
async function findICloudFile(filename) {
|
|
5860
|
+
const container = await getContainer();
|
|
5861
|
+
const database = container.privateCloudDatabase;
|
|
5862
|
+
const query = {
|
|
5863
|
+
recordType: RECORD_TYPE,
|
|
5864
|
+
filterBy: [
|
|
5865
|
+
{
|
|
5866
|
+
fieldName: "filename",
|
|
5867
|
+
comparator: "EQUALS",
|
|
5868
|
+
fieldValue: { value: filename }
|
|
5869
|
+
}
|
|
5870
|
+
]
|
|
5871
|
+
};
|
|
5872
|
+
const response = await database.performQuery(query);
|
|
5873
|
+
if (!response.records || response.records.length === 0) {
|
|
5874
|
+
return null;
|
|
5875
|
+
}
|
|
5876
|
+
const record = response.records[0];
|
|
5877
|
+
return {
|
|
5878
|
+
recordName: record.recordName,
|
|
5879
|
+
filename: record.fields.filename?.value ?? "",
|
|
5880
|
+
modifiedAt: new Date(record.modified?.timestamp ?? Date.now()),
|
|
5881
|
+
size: typeof record.fields.data?.value === "object" && record.fields.data?.value !== null ? record.fields.data.value.size : 0
|
|
5882
|
+
};
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5885
|
+
// src/react/useICloudAuth.ts
|
|
5886
|
+
var ICloudAuthContext = createContext3(null);
|
|
5887
|
+
function ICloudAuthProvider({
|
|
5888
|
+
apiToken,
|
|
5889
|
+
containerIdentifier = DEFAULT_CONTAINER_ID,
|
|
5890
|
+
environment = "production",
|
|
5891
|
+
children
|
|
5892
|
+
}) {
|
|
5893
|
+
const [isAuthenticated, setIsAuthenticated] = useState12(false);
|
|
5894
|
+
const [userRecordName, setUserRecordName] = useState12(null);
|
|
5895
|
+
const [isAvailable, setIsAvailable] = useState12(false);
|
|
5896
|
+
const [isConfigured, setIsConfigured] = useState12(false);
|
|
5897
|
+
const [isLoading, setIsLoading] = useState12(false);
|
|
5898
|
+
useEffect8(() => {
|
|
5899
|
+
if (!apiToken || typeof window === "undefined") {
|
|
5900
|
+
return;
|
|
5901
|
+
}
|
|
5902
|
+
const initCloudKit = async () => {
|
|
5903
|
+
setIsLoading(true);
|
|
5904
|
+
try {
|
|
5905
|
+
await loadCloudKit();
|
|
5906
|
+
setIsAvailable(true);
|
|
5907
|
+
const config = {
|
|
5908
|
+
containerIdentifier,
|
|
5909
|
+
apiToken,
|
|
5910
|
+
environment
|
|
5911
|
+
};
|
|
5912
|
+
await configureCloudKit(config);
|
|
5913
|
+
setIsConfigured(true);
|
|
5914
|
+
try {
|
|
5915
|
+
const userIdentity = await authenticateICloud();
|
|
5916
|
+
if (userIdentity) {
|
|
5917
|
+
setIsAuthenticated(true);
|
|
5918
|
+
setUserRecordName(userIdentity.userRecordName);
|
|
5919
|
+
}
|
|
5920
|
+
} catch {
|
|
5921
|
+
}
|
|
5922
|
+
} catch {
|
|
5923
|
+
setIsAvailable(false);
|
|
5924
|
+
setIsConfigured(false);
|
|
5925
|
+
} finally {
|
|
5926
|
+
setIsLoading(false);
|
|
5927
|
+
}
|
|
5928
|
+
};
|
|
5929
|
+
initCloudKit();
|
|
5930
|
+
}, [apiToken, containerIdentifier, environment]);
|
|
5931
|
+
const requestAccess = useCallback14(async () => {
|
|
5932
|
+
if (!isConfigured) {
|
|
5933
|
+
throw new Error("iCloud is not configured");
|
|
5934
|
+
}
|
|
5935
|
+
if (isAuthenticated) {
|
|
5936
|
+
return;
|
|
5937
|
+
}
|
|
5938
|
+
try {
|
|
5939
|
+
const userIdentity = await requestICloudSignIn();
|
|
5940
|
+
setIsAuthenticated(true);
|
|
5941
|
+
setUserRecordName(userIdentity.userRecordName);
|
|
5942
|
+
} catch (err) {
|
|
5943
|
+
throw new Error(
|
|
5944
|
+
err instanceof Error ? err.message : "Failed to sign in to iCloud"
|
|
5945
|
+
);
|
|
5946
|
+
}
|
|
5947
|
+
}, [isAuthenticated, isConfigured]);
|
|
5948
|
+
const logout = useCallback14(() => {
|
|
5949
|
+
setIsAuthenticated(false);
|
|
5950
|
+
setUserRecordName(null);
|
|
5951
|
+
}, []);
|
|
5952
|
+
return createElement3(
|
|
5953
|
+
ICloudAuthContext.Provider,
|
|
5954
|
+
{
|
|
5955
|
+
value: {
|
|
5956
|
+
isAuthenticated,
|
|
5957
|
+
isConfigured,
|
|
5958
|
+
isAvailable,
|
|
5959
|
+
userRecordName,
|
|
5960
|
+
requestAccess,
|
|
5961
|
+
logout
|
|
5962
|
+
}
|
|
5963
|
+
},
|
|
5964
|
+
children
|
|
5965
|
+
);
|
|
5966
|
+
}
|
|
5967
|
+
function useICloudAuth() {
|
|
5968
|
+
const context = useContext3(ICloudAuthContext);
|
|
5969
|
+
if (!context) {
|
|
5970
|
+
throw new Error("useICloudAuth must be used within ICloudAuthProvider");
|
|
5971
|
+
}
|
|
5972
|
+
return context;
|
|
5973
|
+
}
|
|
5974
|
+
function hasICloudCredentials() {
|
|
5975
|
+
return isCloudKitAvailable();
|
|
5976
|
+
}
|
|
5977
|
+
function clearICloudAuth() {
|
|
5978
|
+
}
|
|
5979
|
+
|
|
5980
|
+
// src/react/useICloudBackup.ts
|
|
5981
|
+
import { useCallback as useCallback15, useMemo as useMemo6 } from "react";
|
|
5982
|
+
|
|
5983
|
+
// src/lib/backup/icloud/backup.ts
|
|
5984
|
+
var isAuthError3 = (err) => err instanceof Error && (err.message.includes("AUTHENTICATION") || err.message.includes("NOT_AUTHENTICATED") || err.message.includes("sign in"));
|
|
5985
|
+
async function pushConversationToICloud(database, conversationId, userAddress, deps, _retried = false) {
|
|
5986
|
+
try {
|
|
5987
|
+
await deps.requestEncryptionKey(userAddress);
|
|
5988
|
+
const filename = `${conversationId}.json`;
|
|
5989
|
+
const existingFile = await findICloudFile(filename);
|
|
5990
|
+
if (existingFile) {
|
|
5991
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
5992
|
+
const conversationsCollection = database.get("conversations");
|
|
5993
|
+
const records = await conversationsCollection.query(Q4.where("conversation_id", conversationId)).fetch();
|
|
5994
|
+
if (records.length > 0) {
|
|
5995
|
+
const conversation = conversationToStored(records[0]);
|
|
5996
|
+
const localUpdated = conversation.updatedAt.getTime();
|
|
5997
|
+
const remoteModified = existingFile.modifiedAt.getTime();
|
|
5998
|
+
if (localUpdated <= remoteModified) {
|
|
5999
|
+
return "skipped";
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
const exportResult = await deps.exportConversation(
|
|
6004
|
+
conversationId,
|
|
6005
|
+
userAddress
|
|
6006
|
+
);
|
|
6007
|
+
if (!exportResult.success || !exportResult.blob) {
|
|
6008
|
+
return "failed";
|
|
6009
|
+
}
|
|
6010
|
+
await uploadFileToICloud(filename, exportResult.blob);
|
|
6011
|
+
return "uploaded";
|
|
6012
|
+
} catch (err) {
|
|
6013
|
+
if (isAuthError3(err) && !_retried) {
|
|
6014
|
+
try {
|
|
6015
|
+
await deps.requestICloudAccess();
|
|
6016
|
+
return pushConversationToICloud(
|
|
6017
|
+
database,
|
|
6018
|
+
conversationId,
|
|
6019
|
+
userAddress,
|
|
6020
|
+
deps,
|
|
6021
|
+
true
|
|
6022
|
+
);
|
|
6023
|
+
} catch {
|
|
6024
|
+
return "failed";
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
return "failed";
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
async function performICloudExport(database, userAddress, deps, onProgress) {
|
|
6031
|
+
await deps.requestEncryptionKey(userAddress);
|
|
6032
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
6033
|
+
const conversationsCollection = database.get("conversations");
|
|
6034
|
+
const records = await conversationsCollection.query(Q4.where("is_deleted", false)).fetch();
|
|
6035
|
+
const conversations = records.map(conversationToStored);
|
|
6036
|
+
const total = conversations.length;
|
|
6037
|
+
if (total === 0) {
|
|
6038
|
+
return { success: true, uploaded: 0, skipped: 0, total: 0 };
|
|
6039
|
+
}
|
|
6040
|
+
let uploaded = 0;
|
|
6041
|
+
let skipped = 0;
|
|
6042
|
+
for (let i = 0; i < conversations.length; i++) {
|
|
6043
|
+
const conv = conversations[i];
|
|
6044
|
+
onProgress?.(i + 1, total);
|
|
6045
|
+
const result = await pushConversationToICloud(
|
|
6046
|
+
database,
|
|
6047
|
+
conv.conversationId,
|
|
6048
|
+
userAddress,
|
|
6049
|
+
deps
|
|
6050
|
+
);
|
|
6051
|
+
if (result === "uploaded") uploaded++;
|
|
6052
|
+
if (result === "skipped") skipped++;
|
|
6053
|
+
}
|
|
6054
|
+
return { success: true, uploaded, skipped, total };
|
|
6055
|
+
}
|
|
6056
|
+
async function performICloudImport(userAddress, deps, onProgress) {
|
|
6057
|
+
await deps.requestEncryptionKey(userAddress);
|
|
6058
|
+
const remoteFiles = await listICloudFiles();
|
|
6059
|
+
if (remoteFiles.length === 0) {
|
|
6060
|
+
return {
|
|
6061
|
+
success: false,
|
|
6062
|
+
restored: 0,
|
|
6063
|
+
failed: 0,
|
|
6064
|
+
total: 0,
|
|
6065
|
+
noBackupsFound: true
|
|
6066
|
+
};
|
|
6067
|
+
}
|
|
6068
|
+
const jsonFiles = remoteFiles.filter(
|
|
6069
|
+
(file) => file.filename.endsWith(".json")
|
|
6070
|
+
);
|
|
6071
|
+
const total = jsonFiles.length;
|
|
6072
|
+
let restored = 0;
|
|
6073
|
+
let failed = 0;
|
|
6074
|
+
for (let i = 0; i < jsonFiles.length; i++) {
|
|
6075
|
+
const file = jsonFiles[i];
|
|
6076
|
+
onProgress?.(i + 1, total);
|
|
6077
|
+
try {
|
|
6078
|
+
const blob = await downloadICloudFile(file.recordName);
|
|
6079
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
6080
|
+
if (result.success) {
|
|
6081
|
+
restored++;
|
|
6082
|
+
} else {
|
|
6083
|
+
failed++;
|
|
6084
|
+
}
|
|
6085
|
+
} catch (err) {
|
|
6086
|
+
if (isAuthError3(err)) {
|
|
6087
|
+
try {
|
|
6088
|
+
await deps.requestICloudAccess();
|
|
6089
|
+
const blob = await downloadICloudFile(file.recordName);
|
|
6090
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
6091
|
+
if (result.success) {
|
|
6092
|
+
restored++;
|
|
6093
|
+
} else {
|
|
6094
|
+
failed++;
|
|
6095
|
+
}
|
|
6096
|
+
} catch {
|
|
6097
|
+
failed++;
|
|
6098
|
+
}
|
|
6099
|
+
} else {
|
|
6100
|
+
failed++;
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
return { success: true, restored, failed, total };
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6107
|
+
// src/react/useICloudBackup.ts
|
|
6108
|
+
function useICloudBackup(options) {
|
|
6109
|
+
const {
|
|
6110
|
+
database,
|
|
6111
|
+
userAddress,
|
|
6112
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
6113
|
+
exportConversation,
|
|
6114
|
+
importConversation
|
|
6115
|
+
} = options;
|
|
6116
|
+
const {
|
|
6117
|
+
isAuthenticated,
|
|
6118
|
+
isConfigured,
|
|
6119
|
+
isAvailable,
|
|
6120
|
+
requestAccess
|
|
6121
|
+
} = useICloudAuth();
|
|
6122
|
+
const deps = useMemo6(
|
|
6123
|
+
() => ({
|
|
6124
|
+
requestICloudAccess: requestAccess,
|
|
6125
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
6126
|
+
exportConversation,
|
|
6127
|
+
importConversation
|
|
6128
|
+
}),
|
|
6129
|
+
[requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
6130
|
+
);
|
|
6131
|
+
const ensureAuthenticated = useCallback15(async () => {
|
|
6132
|
+
if (isAuthenticated) return true;
|
|
6133
|
+
try {
|
|
6134
|
+
await requestAccess();
|
|
6135
|
+
return true;
|
|
6136
|
+
} catch {
|
|
6137
|
+
return false;
|
|
6138
|
+
}
|
|
6139
|
+
}, [isAuthenticated, requestAccess]);
|
|
6140
|
+
const backup = useCallback15(
|
|
6141
|
+
async (backupOptions) => {
|
|
6142
|
+
if (!userAddress) {
|
|
6143
|
+
return { error: "Please sign in to backup to iCloud" };
|
|
6144
|
+
}
|
|
6145
|
+
if (!isAvailable) {
|
|
6146
|
+
return { error: "CloudKit JS is not loaded" };
|
|
6147
|
+
}
|
|
6148
|
+
if (!isConfigured) {
|
|
6149
|
+
return { error: "iCloud is not configured" };
|
|
6150
|
+
}
|
|
6151
|
+
const authenticated = await ensureAuthenticated();
|
|
6152
|
+
if (!authenticated) {
|
|
6153
|
+
return { error: "iCloud access denied" };
|
|
6154
|
+
}
|
|
6155
|
+
try {
|
|
6156
|
+
return await performICloudExport(
|
|
6157
|
+
database,
|
|
6158
|
+
userAddress,
|
|
6159
|
+
deps,
|
|
6160
|
+
backupOptions?.onProgress
|
|
6161
|
+
);
|
|
6162
|
+
} catch (err) {
|
|
6163
|
+
return {
|
|
6164
|
+
error: err instanceof Error ? err.message : "Failed to backup to iCloud"
|
|
6165
|
+
};
|
|
6166
|
+
}
|
|
6167
|
+
},
|
|
6168
|
+
[database, userAddress, isAvailable, isConfigured, ensureAuthenticated, deps]
|
|
6169
|
+
);
|
|
6170
|
+
const restore = useCallback15(
|
|
6171
|
+
async (restoreOptions) => {
|
|
6172
|
+
if (!userAddress) {
|
|
6173
|
+
return { error: "Please sign in to restore from iCloud" };
|
|
6174
|
+
}
|
|
6175
|
+
if (!isAvailable) {
|
|
6176
|
+
return { error: "CloudKit JS is not loaded" };
|
|
6177
|
+
}
|
|
6178
|
+
if (!isConfigured) {
|
|
6179
|
+
return { error: "iCloud is not configured" };
|
|
6180
|
+
}
|
|
6181
|
+
const authenticated = await ensureAuthenticated();
|
|
6182
|
+
if (!authenticated) {
|
|
6183
|
+
return { error: "iCloud access denied" };
|
|
6184
|
+
}
|
|
6185
|
+
try {
|
|
6186
|
+
return await performICloudImport(
|
|
6187
|
+
userAddress,
|
|
6188
|
+
deps,
|
|
6189
|
+
restoreOptions?.onProgress
|
|
6190
|
+
);
|
|
6191
|
+
} catch (err) {
|
|
6192
|
+
return {
|
|
6193
|
+
error: err instanceof Error ? err.message : "Failed to restore from iCloud"
|
|
6194
|
+
};
|
|
6195
|
+
}
|
|
6196
|
+
},
|
|
6197
|
+
[userAddress, isAvailable, isConfigured, ensureAuthenticated, deps]
|
|
6198
|
+
);
|
|
6199
|
+
return {
|
|
6200
|
+
backup,
|
|
6201
|
+
restore,
|
|
6202
|
+
isConfigured,
|
|
6203
|
+
isAuthenticated,
|
|
6204
|
+
isAvailable
|
|
6205
|
+
};
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
// src/react/useBackupAuth.ts
|
|
6209
|
+
import {
|
|
6210
|
+
createContext as createContext4,
|
|
6211
|
+
createElement as createElement4,
|
|
6212
|
+
useCallback as useCallback16,
|
|
6213
|
+
useContext as useContext4,
|
|
6214
|
+
useEffect as useEffect9,
|
|
6215
|
+
useState as useState13
|
|
6216
|
+
} from "react";
|
|
6217
|
+
var BackupAuthContext = createContext4(null);
|
|
5588
6218
|
function BackupAuthProvider({
|
|
5589
6219
|
dropboxAppKey,
|
|
5590
6220
|
dropboxCallbackPath = "/auth/dropbox/callback",
|
|
5591
6221
|
googleClientId,
|
|
5592
6222
|
googleCallbackPath = "/auth/google/callback",
|
|
6223
|
+
icloudApiToken,
|
|
6224
|
+
icloudContainerIdentifier = DEFAULT_CONTAINER_ID,
|
|
6225
|
+
icloudEnvironment = "production",
|
|
5593
6226
|
apiClient,
|
|
5594
6227
|
children
|
|
5595
6228
|
}) {
|
|
5596
|
-
const [dropboxToken, setDropboxToken] =
|
|
6229
|
+
const [dropboxToken, setDropboxToken] = useState13(null);
|
|
5597
6230
|
const isDropboxConfigured = !!dropboxAppKey;
|
|
5598
|
-
const [googleToken, setGoogleToken] =
|
|
6231
|
+
const [googleToken, setGoogleToken] = useState13(null);
|
|
5599
6232
|
const isGoogleConfigured = !!googleClientId;
|
|
5600
|
-
|
|
6233
|
+
const [icloudAuthenticated, setIcloudAuthenticated] = useState13(false);
|
|
6234
|
+
const [icloudUserRecordName, setIcloudUserRecordName] = useState13(null);
|
|
6235
|
+
const [isIcloudAvailable, setIsIcloudAvailable] = useState13(false);
|
|
6236
|
+
const isIcloudConfigured = isIcloudAvailable && !!icloudApiToken;
|
|
6237
|
+
useEffect9(() => {
|
|
5601
6238
|
const checkStoredTokens = async () => {
|
|
5602
6239
|
if (hasDropboxCredentials()) {
|
|
5603
6240
|
const token = await getDropboxAccessToken(apiClient);
|
|
@@ -5614,7 +6251,35 @@ function BackupAuthProvider({
|
|
|
5614
6251
|
};
|
|
5615
6252
|
checkStoredTokens();
|
|
5616
6253
|
}, [apiClient]);
|
|
5617
|
-
|
|
6254
|
+
useEffect9(() => {
|
|
6255
|
+
if (!icloudApiToken || typeof window === "undefined") {
|
|
6256
|
+
return;
|
|
6257
|
+
}
|
|
6258
|
+
const initCloudKit = async () => {
|
|
6259
|
+
try {
|
|
6260
|
+
await loadCloudKit();
|
|
6261
|
+
setIsIcloudAvailable(true);
|
|
6262
|
+
const config = {
|
|
6263
|
+
containerIdentifier: icloudContainerIdentifier,
|
|
6264
|
+
apiToken: icloudApiToken,
|
|
6265
|
+
environment: icloudEnvironment
|
|
6266
|
+
};
|
|
6267
|
+
await configureCloudKit(config);
|
|
6268
|
+
try {
|
|
6269
|
+
const userIdentity = await authenticateICloud();
|
|
6270
|
+
if (userIdentity) {
|
|
6271
|
+
setIcloudAuthenticated(true);
|
|
6272
|
+
setIcloudUserRecordName(userIdentity.userRecordName);
|
|
6273
|
+
}
|
|
6274
|
+
} catch {
|
|
6275
|
+
}
|
|
6276
|
+
} catch {
|
|
6277
|
+
setIsIcloudAvailable(false);
|
|
6278
|
+
}
|
|
6279
|
+
};
|
|
6280
|
+
initCloudKit();
|
|
6281
|
+
}, [icloudApiToken, icloudContainerIdentifier, icloudEnvironment]);
|
|
6282
|
+
useEffect9(() => {
|
|
5618
6283
|
if (!isDropboxConfigured) return;
|
|
5619
6284
|
const handleCallback = async () => {
|
|
5620
6285
|
if (isDropboxCallback()) {
|
|
@@ -5629,7 +6294,7 @@ function BackupAuthProvider({
|
|
|
5629
6294
|
};
|
|
5630
6295
|
handleCallback();
|
|
5631
6296
|
}, [dropboxCallbackPath, isDropboxConfigured, apiClient]);
|
|
5632
|
-
|
|
6297
|
+
useEffect9(() => {
|
|
5633
6298
|
if (!isGoogleConfigured) return;
|
|
5634
6299
|
const handleCallback = async () => {
|
|
5635
6300
|
if (isGoogleDriveCallback()) {
|
|
@@ -5644,14 +6309,14 @@ function BackupAuthProvider({
|
|
|
5644
6309
|
};
|
|
5645
6310
|
handleCallback();
|
|
5646
6311
|
}, [googleCallbackPath, isGoogleConfigured, apiClient]);
|
|
5647
|
-
const refreshDropboxTokenFn =
|
|
6312
|
+
const refreshDropboxTokenFn = useCallback16(async () => {
|
|
5648
6313
|
const token = await getDropboxAccessToken(apiClient);
|
|
5649
6314
|
if (token) {
|
|
5650
6315
|
setDropboxToken(token);
|
|
5651
6316
|
}
|
|
5652
6317
|
return token;
|
|
5653
6318
|
}, [apiClient]);
|
|
5654
|
-
const requestDropboxAccess =
|
|
6319
|
+
const requestDropboxAccess = useCallback16(async () => {
|
|
5655
6320
|
if (!isDropboxConfigured || !dropboxAppKey) {
|
|
5656
6321
|
throw new Error("Dropbox is not configured");
|
|
5657
6322
|
}
|
|
@@ -5671,18 +6336,18 @@ function BackupAuthProvider({
|
|
|
5671
6336
|
isDropboxConfigured,
|
|
5672
6337
|
apiClient
|
|
5673
6338
|
]);
|
|
5674
|
-
const logoutDropbox =
|
|
6339
|
+
const logoutDropbox = useCallback16(async () => {
|
|
5675
6340
|
await revokeDropboxToken(apiClient);
|
|
5676
6341
|
setDropboxToken(null);
|
|
5677
6342
|
}, [apiClient]);
|
|
5678
|
-
const refreshGoogleTokenFn =
|
|
6343
|
+
const refreshGoogleTokenFn = useCallback16(async () => {
|
|
5679
6344
|
const token = await getGoogleDriveAccessToken(apiClient);
|
|
5680
6345
|
if (token) {
|
|
5681
6346
|
setGoogleToken(token);
|
|
5682
6347
|
}
|
|
5683
6348
|
return token;
|
|
5684
6349
|
}, [apiClient]);
|
|
5685
|
-
const requestGoogleAccess =
|
|
6350
|
+
const requestGoogleAccess = useCallback16(async () => {
|
|
5686
6351
|
if (!isGoogleConfigured || !googleClientId) {
|
|
5687
6352
|
throw new Error("Google Drive is not configured");
|
|
5688
6353
|
}
|
|
@@ -5702,16 +6367,51 @@ function BackupAuthProvider({
|
|
|
5702
6367
|
isGoogleConfigured,
|
|
5703
6368
|
apiClient
|
|
5704
6369
|
]);
|
|
5705
|
-
const logoutGoogle =
|
|
6370
|
+
const logoutGoogle = useCallback16(async () => {
|
|
5706
6371
|
await revokeGoogleDriveToken(apiClient);
|
|
5707
6372
|
setGoogleToken(null);
|
|
5708
6373
|
}, [apiClient]);
|
|
5709
|
-
const
|
|
6374
|
+
const refreshIcloudTokenFn = useCallback16(async () => {
|
|
6375
|
+
try {
|
|
6376
|
+
const userIdentity = await authenticateICloud();
|
|
6377
|
+
if (userIdentity) {
|
|
6378
|
+
setIcloudAuthenticated(true);
|
|
6379
|
+
setIcloudUserRecordName(userIdentity.userRecordName);
|
|
6380
|
+
return userIdentity.userRecordName;
|
|
6381
|
+
}
|
|
6382
|
+
} catch {
|
|
6383
|
+
}
|
|
6384
|
+
return null;
|
|
6385
|
+
}, []);
|
|
6386
|
+
const requestIcloudAccess = useCallback16(async () => {
|
|
6387
|
+
if (!isIcloudConfigured) {
|
|
6388
|
+
throw new Error("iCloud is not configured");
|
|
6389
|
+
}
|
|
6390
|
+
if (icloudAuthenticated && icloudUserRecordName) {
|
|
6391
|
+
return icloudUserRecordName;
|
|
6392
|
+
}
|
|
6393
|
+
try {
|
|
6394
|
+
const userIdentity = await requestICloudSignIn();
|
|
6395
|
+
setIcloudAuthenticated(true);
|
|
6396
|
+
setIcloudUserRecordName(userIdentity.userRecordName);
|
|
6397
|
+
return userIdentity.userRecordName;
|
|
6398
|
+
} catch (err) {
|
|
6399
|
+
throw new Error(
|
|
6400
|
+
err instanceof Error ? err.message : "Failed to sign in to iCloud"
|
|
6401
|
+
);
|
|
6402
|
+
}
|
|
6403
|
+
}, [icloudAuthenticated, icloudUserRecordName, isIcloudConfigured]);
|
|
6404
|
+
const logoutIcloud = useCallback16(async () => {
|
|
6405
|
+
setIcloudAuthenticated(false);
|
|
6406
|
+
setIcloudUserRecordName(null);
|
|
6407
|
+
}, []);
|
|
6408
|
+
const logoutAll = useCallback16(async () => {
|
|
5710
6409
|
await Promise.all([
|
|
5711
6410
|
isDropboxConfigured ? logoutDropbox() : Promise.resolve(),
|
|
5712
|
-
isGoogleConfigured ? logoutGoogle() : Promise.resolve()
|
|
6411
|
+
isGoogleConfigured ? logoutGoogle() : Promise.resolve(),
|
|
6412
|
+
isIcloudConfigured ? logoutIcloud() : Promise.resolve()
|
|
5713
6413
|
]);
|
|
5714
|
-
}, [isDropboxConfigured, isGoogleConfigured, logoutDropbox, logoutGoogle]);
|
|
6414
|
+
}, [isDropboxConfigured, isGoogleConfigured, isIcloudConfigured, logoutDropbox, logoutGoogle, logoutIcloud]);
|
|
5715
6415
|
const dropboxState = {
|
|
5716
6416
|
accessToken: dropboxToken,
|
|
5717
6417
|
isAuthenticated: !!dropboxToken,
|
|
@@ -5728,14 +6428,24 @@ function BackupAuthProvider({
|
|
|
5728
6428
|
logout: logoutGoogle,
|
|
5729
6429
|
refreshToken: refreshGoogleTokenFn
|
|
5730
6430
|
};
|
|
5731
|
-
|
|
6431
|
+
const icloudState = {
|
|
6432
|
+
accessToken: icloudUserRecordName,
|
|
6433
|
+
// Use userRecordName as the "token" for iCloud
|
|
6434
|
+
isAuthenticated: icloudAuthenticated,
|
|
6435
|
+
isConfigured: isIcloudConfigured,
|
|
6436
|
+
requestAccess: requestIcloudAccess,
|
|
6437
|
+
logout: logoutIcloud,
|
|
6438
|
+
refreshToken: refreshIcloudTokenFn
|
|
6439
|
+
};
|
|
6440
|
+
return createElement4(
|
|
5732
6441
|
BackupAuthContext.Provider,
|
|
5733
6442
|
{
|
|
5734
6443
|
value: {
|
|
5735
6444
|
dropbox: dropboxState,
|
|
5736
6445
|
googleDrive: googleDriveState,
|
|
5737
|
-
|
|
5738
|
-
|
|
6446
|
+
icloud: icloudState,
|
|
6447
|
+
hasAnyProvider: isDropboxConfigured || isGoogleConfigured || isIcloudConfigured,
|
|
6448
|
+
hasAnyAuthentication: !!dropboxToken || !!googleToken || icloudAuthenticated,
|
|
5739
6449
|
logoutAll
|
|
5740
6450
|
}
|
|
5741
6451
|
},
|
|
@@ -5743,7 +6453,7 @@ function BackupAuthProvider({
|
|
|
5743
6453
|
);
|
|
5744
6454
|
}
|
|
5745
6455
|
function useBackupAuth() {
|
|
5746
|
-
const context =
|
|
6456
|
+
const context = useContext4(BackupAuthContext);
|
|
5747
6457
|
if (!context) {
|
|
5748
6458
|
throw new Error("useBackupAuth must be used within BackupAuthProvider");
|
|
5749
6459
|
}
|
|
@@ -5751,7 +6461,7 @@ function useBackupAuth() {
|
|
|
5751
6461
|
}
|
|
5752
6462
|
|
|
5753
6463
|
// src/react/useBackup.ts
|
|
5754
|
-
import { useCallback as
|
|
6464
|
+
import { useCallback as useCallback17, useMemo as useMemo7 } from "react";
|
|
5755
6465
|
function useBackup(options) {
|
|
5756
6466
|
const {
|
|
5757
6467
|
database,
|
|
@@ -5766,11 +6476,12 @@ function useBackup(options) {
|
|
|
5766
6476
|
const {
|
|
5767
6477
|
dropbox: dropboxAuth,
|
|
5768
6478
|
googleDrive: googleDriveAuth,
|
|
6479
|
+
icloud: icloudAuth,
|
|
5769
6480
|
hasAnyProvider,
|
|
5770
6481
|
hasAnyAuthentication,
|
|
5771
6482
|
logoutAll
|
|
5772
6483
|
} = useBackupAuth();
|
|
5773
|
-
const dropboxDeps =
|
|
6484
|
+
const dropboxDeps = useMemo7(
|
|
5774
6485
|
() => ({
|
|
5775
6486
|
requestDropboxAccess: dropboxAuth.requestAccess,
|
|
5776
6487
|
requestEncryptionKey: requestEncryptionKey2,
|
|
@@ -5779,7 +6490,7 @@ function useBackup(options) {
|
|
|
5779
6490
|
}),
|
|
5780
6491
|
[dropboxAuth.requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
5781
6492
|
);
|
|
5782
|
-
const googleDriveDeps =
|
|
6493
|
+
const googleDriveDeps = useMemo7(
|
|
5783
6494
|
() => ({
|
|
5784
6495
|
requestDriveAccess: googleDriveAuth.requestAccess,
|
|
5785
6496
|
requestEncryptionKey: requestEncryptionKey2,
|
|
@@ -5788,7 +6499,18 @@ function useBackup(options) {
|
|
|
5788
6499
|
}),
|
|
5789
6500
|
[googleDriveAuth.requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
5790
6501
|
);
|
|
5791
|
-
const
|
|
6502
|
+
const icloudDeps = useMemo7(
|
|
6503
|
+
() => ({
|
|
6504
|
+
requestICloudAccess: async () => {
|
|
6505
|
+
await icloudAuth.requestAccess();
|
|
6506
|
+
},
|
|
6507
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
6508
|
+
exportConversation,
|
|
6509
|
+
importConversation
|
|
6510
|
+
}),
|
|
6511
|
+
[icloudAuth.requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
6512
|
+
);
|
|
6513
|
+
const dropboxBackup = useCallback17(
|
|
5792
6514
|
async (backupOptions) => {
|
|
5793
6515
|
if (!userAddress) {
|
|
5794
6516
|
return { error: "Please sign in to backup to Dropbox" };
|
|
@@ -5818,7 +6540,7 @@ function useBackup(options) {
|
|
|
5818
6540
|
},
|
|
5819
6541
|
[database, userAddress, dropboxAuth, dropboxDeps, dropboxFolder]
|
|
5820
6542
|
);
|
|
5821
|
-
const dropboxRestore =
|
|
6543
|
+
const dropboxRestore = useCallback17(
|
|
5822
6544
|
async (restoreOptions) => {
|
|
5823
6545
|
if (!userAddress) {
|
|
5824
6546
|
return { error: "Please sign in to restore from Dropbox" };
|
|
@@ -5847,7 +6569,7 @@ function useBackup(options) {
|
|
|
5847
6569
|
},
|
|
5848
6570
|
[userAddress, dropboxAuth, dropboxDeps, dropboxFolder]
|
|
5849
6571
|
);
|
|
5850
|
-
const googleDriveBackup =
|
|
6572
|
+
const googleDriveBackup = useCallback17(
|
|
5851
6573
|
async (backupOptions) => {
|
|
5852
6574
|
if (!userAddress) {
|
|
5853
6575
|
return { error: "Please sign in to backup to Google Drive" };
|
|
@@ -5885,7 +6607,7 @@ function useBackup(options) {
|
|
|
5885
6607
|
googleConversationsFolder
|
|
5886
6608
|
]
|
|
5887
6609
|
);
|
|
5888
|
-
const googleDriveRestore =
|
|
6610
|
+
const googleDriveRestore = useCallback17(
|
|
5889
6611
|
async (restoreOptions) => {
|
|
5890
6612
|
if (!userAddress) {
|
|
5891
6613
|
return { error: "Please sign in to restore from Google Drive" };
|
|
@@ -5937,9 +6659,77 @@ function useBackup(options) {
|
|
|
5937
6659
|
connect: googleDriveAuth.requestAccess,
|
|
5938
6660
|
disconnect: googleDriveAuth.logout
|
|
5939
6661
|
};
|
|
6662
|
+
const icloudBackup = useCallback17(
|
|
6663
|
+
async (backupOptions) => {
|
|
6664
|
+
if (!userAddress) {
|
|
6665
|
+
return { error: "Please sign in to backup to iCloud" };
|
|
6666
|
+
}
|
|
6667
|
+
if (!icloudAuth.isConfigured) {
|
|
6668
|
+
return { error: "iCloud is not configured" };
|
|
6669
|
+
}
|
|
6670
|
+
if (!icloudAuth.isAuthenticated) {
|
|
6671
|
+
try {
|
|
6672
|
+
await icloudAuth.requestAccess();
|
|
6673
|
+
} catch {
|
|
6674
|
+
return { error: "iCloud access denied" };
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
try {
|
|
6678
|
+
return await performICloudExport(
|
|
6679
|
+
database,
|
|
6680
|
+
userAddress,
|
|
6681
|
+
icloudDeps,
|
|
6682
|
+
backupOptions?.onProgress
|
|
6683
|
+
);
|
|
6684
|
+
} catch (err) {
|
|
6685
|
+
return {
|
|
6686
|
+
error: err instanceof Error ? err.message : "Failed to backup to iCloud"
|
|
6687
|
+
};
|
|
6688
|
+
}
|
|
6689
|
+
},
|
|
6690
|
+
[database, userAddress, icloudAuth, icloudDeps]
|
|
6691
|
+
);
|
|
6692
|
+
const icloudRestore = useCallback17(
|
|
6693
|
+
async (restoreOptions) => {
|
|
6694
|
+
if (!userAddress) {
|
|
6695
|
+
return { error: "Please sign in to restore from iCloud" };
|
|
6696
|
+
}
|
|
6697
|
+
if (!icloudAuth.isConfigured) {
|
|
6698
|
+
return { error: "iCloud is not configured" };
|
|
6699
|
+
}
|
|
6700
|
+
if (!icloudAuth.isAuthenticated) {
|
|
6701
|
+
try {
|
|
6702
|
+
await icloudAuth.requestAccess();
|
|
6703
|
+
} catch {
|
|
6704
|
+
return { error: "iCloud access denied" };
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
try {
|
|
6708
|
+
return await performICloudImport(
|
|
6709
|
+
userAddress,
|
|
6710
|
+
icloudDeps,
|
|
6711
|
+
restoreOptions?.onProgress
|
|
6712
|
+
);
|
|
6713
|
+
} catch (err) {
|
|
6714
|
+
return {
|
|
6715
|
+
error: err instanceof Error ? err.message : "Failed to restore from iCloud"
|
|
6716
|
+
};
|
|
6717
|
+
}
|
|
6718
|
+
},
|
|
6719
|
+
[userAddress, icloudAuth, icloudDeps]
|
|
6720
|
+
);
|
|
6721
|
+
const icloudState = {
|
|
6722
|
+
isConfigured: icloudAuth.isConfigured,
|
|
6723
|
+
isAuthenticated: icloudAuth.isAuthenticated,
|
|
6724
|
+
backup: icloudBackup,
|
|
6725
|
+
restore: icloudRestore,
|
|
6726
|
+
connect: icloudAuth.requestAccess,
|
|
6727
|
+
disconnect: icloudAuth.logout
|
|
6728
|
+
};
|
|
5940
6729
|
return {
|
|
5941
6730
|
dropbox: dropboxState,
|
|
5942
6731
|
googleDrive: googleDriveState,
|
|
6732
|
+
icloud: icloudState,
|
|
5943
6733
|
hasAnyProvider,
|
|
5944
6734
|
hasAnyAuthentication,
|
|
5945
6735
|
disconnectAll: logoutAll
|
|
@@ -5948,6 +6738,7 @@ function useBackup(options) {
|
|
|
5948
6738
|
export {
|
|
5949
6739
|
DEFAULT_CONVERSATIONS_FOLDER as BACKUP_DRIVE_CONVERSATIONS_FOLDER,
|
|
5950
6740
|
DEFAULT_ROOT_FOLDER as BACKUP_DRIVE_ROOT_FOLDER,
|
|
6741
|
+
DEFAULT_BACKUP_FOLDER2 as BACKUP_ICLOUD_FOLDER,
|
|
5951
6742
|
BackupAuthProvider,
|
|
5952
6743
|
Conversation as ChatConversation,
|
|
5953
6744
|
Message as ChatMessage,
|
|
@@ -5955,15 +6746,18 @@ export {
|
|
|
5955
6746
|
DEFAULT_CONVERSATIONS_FOLDER as DEFAULT_DRIVE_CONVERSATIONS_FOLDER,
|
|
5956
6747
|
DEFAULT_ROOT_FOLDER as DEFAULT_DRIVE_ROOT_FOLDER,
|
|
5957
6748
|
DEFAULT_BACKUP_FOLDER as DEFAULT_DROPBOX_FOLDER,
|
|
6749
|
+
DEFAULT_BACKUP_FOLDER2 as DEFAULT_ICLOUD_BACKUP_FOLDER,
|
|
5958
6750
|
DEFAULT_TOOL_SELECTOR_MODEL,
|
|
5959
6751
|
DropboxAuthProvider,
|
|
5960
6752
|
GoogleDriveAuthProvider,
|
|
6753
|
+
ICloudAuthProvider,
|
|
5961
6754
|
Memory as StoredMemoryModel,
|
|
5962
6755
|
ModelPreference as StoredModelPreferenceModel,
|
|
5963
6756
|
chatStorageMigrations,
|
|
5964
6757
|
chatStorageSchema,
|
|
5965
6758
|
clearToken as clearDropboxToken,
|
|
5966
6759
|
clearGoogleDriveToken,
|
|
6760
|
+
clearICloudAuth,
|
|
5967
6761
|
createMemoryContextSystemMessage,
|
|
5968
6762
|
decryptData,
|
|
5969
6763
|
decryptDataBytes,
|
|
@@ -5978,6 +6772,7 @@ export {
|
|
|
5978
6772
|
hasDropboxCredentials,
|
|
5979
6773
|
hasEncryptionKey,
|
|
5980
6774
|
hasGoogleDriveCredentials,
|
|
6775
|
+
hasICloudCredentials,
|
|
5981
6776
|
memoryStorageSchema,
|
|
5982
6777
|
requestEncryptionKey,
|
|
5983
6778
|
sdkMigrations,
|
|
@@ -5994,6 +6789,8 @@ export {
|
|
|
5994
6789
|
useEncryption,
|
|
5995
6790
|
useGoogleDriveAuth,
|
|
5996
6791
|
useGoogleDriveBackup,
|
|
6792
|
+
useICloudAuth,
|
|
6793
|
+
useICloudBackup,
|
|
5997
6794
|
useImageGeneration,
|
|
5998
6795
|
useMemoryStorage,
|
|
5999
6796
|
useModels,
|