@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.cjs
CHANGED
|
@@ -40,6 +40,7 @@ var index_exports = {};
|
|
|
40
40
|
__export(index_exports, {
|
|
41
41
|
BACKUP_DRIVE_CONVERSATIONS_FOLDER: () => DEFAULT_CONVERSATIONS_FOLDER,
|
|
42
42
|
BACKUP_DRIVE_ROOT_FOLDER: () => DEFAULT_ROOT_FOLDER,
|
|
43
|
+
BACKUP_ICLOUD_FOLDER: () => DEFAULT_BACKUP_FOLDER2,
|
|
43
44
|
BackupAuthProvider: () => BackupAuthProvider,
|
|
44
45
|
ChatConversation: () => Conversation,
|
|
45
46
|
ChatMessage: () => Message,
|
|
@@ -47,15 +48,18 @@ __export(index_exports, {
|
|
|
47
48
|
DEFAULT_DRIVE_CONVERSATIONS_FOLDER: () => DEFAULT_CONVERSATIONS_FOLDER,
|
|
48
49
|
DEFAULT_DRIVE_ROOT_FOLDER: () => DEFAULT_ROOT_FOLDER,
|
|
49
50
|
DEFAULT_DROPBOX_FOLDER: () => DEFAULT_BACKUP_FOLDER,
|
|
51
|
+
DEFAULT_ICLOUD_BACKUP_FOLDER: () => DEFAULT_BACKUP_FOLDER2,
|
|
50
52
|
DEFAULT_TOOL_SELECTOR_MODEL: () => DEFAULT_TOOL_SELECTOR_MODEL,
|
|
51
53
|
DropboxAuthProvider: () => DropboxAuthProvider,
|
|
52
54
|
GoogleDriveAuthProvider: () => GoogleDriveAuthProvider,
|
|
55
|
+
ICloudAuthProvider: () => ICloudAuthProvider,
|
|
53
56
|
StoredMemoryModel: () => Memory,
|
|
54
57
|
StoredModelPreferenceModel: () => ModelPreference,
|
|
55
58
|
chatStorageMigrations: () => chatStorageMigrations,
|
|
56
59
|
chatStorageSchema: () => chatStorageSchema,
|
|
57
60
|
clearDropboxToken: () => clearToken,
|
|
58
61
|
clearGoogleDriveToken: () => clearGoogleDriveToken,
|
|
62
|
+
clearICloudAuth: () => clearICloudAuth,
|
|
59
63
|
createMemoryContextSystemMessage: () => createMemoryContextSystemMessage,
|
|
60
64
|
decryptData: () => decryptData,
|
|
61
65
|
decryptDataBytes: () => decryptDataBytes,
|
|
@@ -70,6 +74,7 @@ __export(index_exports, {
|
|
|
70
74
|
hasDropboxCredentials: () => hasDropboxCredentials,
|
|
71
75
|
hasEncryptionKey: () => hasEncryptionKey,
|
|
72
76
|
hasGoogleDriveCredentials: () => hasGoogleDriveCredentials,
|
|
77
|
+
hasICloudCredentials: () => hasICloudCredentials,
|
|
73
78
|
memoryStorageSchema: () => memoryStorageSchema,
|
|
74
79
|
requestEncryptionKey: () => requestEncryptionKey,
|
|
75
80
|
sdkMigrations: () => sdkMigrations,
|
|
@@ -86,6 +91,8 @@ __export(index_exports, {
|
|
|
86
91
|
useEncryption: () => useEncryption,
|
|
87
92
|
useGoogleDriveAuth: () => useGoogleDriveAuth,
|
|
88
93
|
useGoogleDriveBackup: () => useGoogleDriveBackup,
|
|
94
|
+
useICloudAuth: () => useICloudAuth,
|
|
95
|
+
useICloudBackup: () => useICloudBackup,
|
|
89
96
|
useImageGeneration: () => useImageGeneration,
|
|
90
97
|
useMemoryStorage: () => useMemoryStorage,
|
|
91
98
|
useModels: () => useModels,
|
|
@@ -1788,7 +1795,7 @@ var import_react2 = require("react");
|
|
|
1788
1795
|
var import_watermelondb = require("@nozbe/watermelondb");
|
|
1789
1796
|
var import_migrations = require("@nozbe/watermelondb/Schema/migrations");
|
|
1790
1797
|
var chatStorageSchema = (0, import_watermelondb.appSchema)({
|
|
1791
|
-
version:
|
|
1798
|
+
version: 3,
|
|
1792
1799
|
tables: [
|
|
1793
1800
|
(0, import_watermelondb.tableSchema)({
|
|
1794
1801
|
name: "history",
|
|
@@ -1806,7 +1813,8 @@ var chatStorageSchema = (0, import_watermelondb.appSchema)({
|
|
|
1806
1813
|
{ name: "usage", type: "string", isOptional: true },
|
|
1807
1814
|
{ name: "sources", type: "string", isOptional: true },
|
|
1808
1815
|
{ name: "response_duration", type: "number", isOptional: true },
|
|
1809
|
-
{ name: "was_stopped", type: "boolean", isOptional: true }
|
|
1816
|
+
{ name: "was_stopped", type: "boolean", isOptional: true },
|
|
1817
|
+
{ name: "error", type: "string", isOptional: true }
|
|
1810
1818
|
]
|
|
1811
1819
|
}),
|
|
1812
1820
|
(0, import_watermelondb.tableSchema)({
|
|
@@ -1831,6 +1839,15 @@ var chatStorageMigrations = (0, import_migrations.schemaMigrations)({
|
|
|
1831
1839
|
columns: [{ name: "was_stopped", type: "boolean", isOptional: true }]
|
|
1832
1840
|
})
|
|
1833
1841
|
]
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
toVersion: 3,
|
|
1845
|
+
steps: [
|
|
1846
|
+
(0, import_migrations.addColumns)({
|
|
1847
|
+
table: "history",
|
|
1848
|
+
columns: [{ name: "error", type: "string", isOptional: true }]
|
|
1849
|
+
})
|
|
1850
|
+
]
|
|
1834
1851
|
}
|
|
1835
1852
|
]
|
|
1836
1853
|
});
|
|
@@ -1886,6 +1903,9 @@ __decorateClass([
|
|
|
1886
1903
|
__decorateClass([
|
|
1887
1904
|
(0, import_decorators.field)("was_stopped")
|
|
1888
1905
|
], Message.prototype, "wasStopped", 2);
|
|
1906
|
+
__decorateClass([
|
|
1907
|
+
(0, import_decorators.text)("error")
|
|
1908
|
+
], Message.prototype, "error", 2);
|
|
1889
1909
|
var Conversation = class extends import_watermelondb2.Model {
|
|
1890
1910
|
};
|
|
1891
1911
|
Conversation.table = "conversations";
|
|
@@ -1940,7 +1960,8 @@ function messageToStored(message) {
|
|
|
1940
1960
|
usage: message.usage,
|
|
1941
1961
|
sources: message.sources,
|
|
1942
1962
|
responseDuration: message.responseDuration,
|
|
1943
|
-
wasStopped: message.wasStopped
|
|
1963
|
+
wasStopped: message.wasStopped,
|
|
1964
|
+
error: message.error
|
|
1944
1965
|
};
|
|
1945
1966
|
}
|
|
1946
1967
|
function conversationToStored(conversation) {
|
|
@@ -2030,6 +2051,7 @@ async function createMessageOp(ctx, opts) {
|
|
|
2030
2051
|
if (opts.vector) msg._setRaw("vector", JSON.stringify(opts.vector));
|
|
2031
2052
|
if (opts.embeddingModel) msg._setRaw("embedding_model", opts.embeddingModel);
|
|
2032
2053
|
if (opts.wasStopped) msg._setRaw("was_stopped", opts.wasStopped);
|
|
2054
|
+
if (opts.error) msg._setRaw("error", opts.error);
|
|
2033
2055
|
});
|
|
2034
2056
|
});
|
|
2035
2057
|
return messageToStored(created);
|
|
@@ -2049,6 +2071,20 @@ async function updateMessageEmbeddingOp(ctx, uniqueId, vector, embeddingModel) {
|
|
|
2049
2071
|
});
|
|
2050
2072
|
return messageToStored(message);
|
|
2051
2073
|
}
|
|
2074
|
+
async function updateMessageErrorOp(ctx, uniqueId, error) {
|
|
2075
|
+
let message;
|
|
2076
|
+
try {
|
|
2077
|
+
message = await ctx.messagesCollection.find(uniqueId);
|
|
2078
|
+
} catch {
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
await ctx.database.write(async () => {
|
|
2082
|
+
await message.update((msg) => {
|
|
2083
|
+
msg._setRaw("error", error);
|
|
2084
|
+
});
|
|
2085
|
+
});
|
|
2086
|
+
return messageToStored(message);
|
|
2087
|
+
}
|
|
2052
2088
|
function cosineSimilarity(a, b) {
|
|
2053
2089
|
if (a.length !== b.length) return 0;
|
|
2054
2090
|
let dotProduct = 0;
|
|
@@ -2266,7 +2302,8 @@ function useChatStorage(options) {
|
|
|
2266
2302
|
let messagesToSend = [];
|
|
2267
2303
|
if (includeHistory && !providedMessages) {
|
|
2268
2304
|
const storedMessages = await getMessages(convId);
|
|
2269
|
-
const
|
|
2305
|
+
const validMessages = storedMessages.filter((msg) => !msg.error);
|
|
2306
|
+
const limitedMessages = validMessages.slice(-maxHistoryMessages);
|
|
2270
2307
|
messagesToSend = limitedMessages.map(storedToLlmapiMessage);
|
|
2271
2308
|
} else if (providedMessages) {
|
|
2272
2309
|
messagesToSend = providedMessages;
|
|
@@ -2365,14 +2402,37 @@ function useChatStorage(options) {
|
|
|
2365
2402
|
userMessage: storedUserMessage,
|
|
2366
2403
|
assistantMessage: storedAssistantMessage2
|
|
2367
2404
|
};
|
|
2368
|
-
} catch
|
|
2405
|
+
} catch {
|
|
2406
|
+
return {
|
|
2407
|
+
data: null,
|
|
2408
|
+
error: "Request aborted",
|
|
2409
|
+
toolExecution: abortedResult.toolExecution,
|
|
2410
|
+
userMessage: storedUserMessage
|
|
2411
|
+
};
|
|
2369
2412
|
}
|
|
2370
2413
|
}
|
|
2414
|
+
const errorMessage = result.error || "No response data received";
|
|
2415
|
+
try {
|
|
2416
|
+
await updateMessageErrorOp(
|
|
2417
|
+
storageCtx,
|
|
2418
|
+
storedUserMessage.uniqueId,
|
|
2419
|
+
errorMessage
|
|
2420
|
+
);
|
|
2421
|
+
await createMessageOp(storageCtx, {
|
|
2422
|
+
conversationId: convId,
|
|
2423
|
+
role: "assistant",
|
|
2424
|
+
content: "",
|
|
2425
|
+
model: model || "",
|
|
2426
|
+
responseDuration,
|
|
2427
|
+
error: errorMessage
|
|
2428
|
+
});
|
|
2429
|
+
} catch {
|
|
2430
|
+
}
|
|
2371
2431
|
return {
|
|
2372
2432
|
data: null,
|
|
2373
|
-
error:
|
|
2433
|
+
error: errorMessage,
|
|
2374
2434
|
toolExecution: result.toolExecution,
|
|
2375
|
-
userMessage: storedUserMessage
|
|
2435
|
+
userMessage: { ...storedUserMessage, error: errorMessage }
|
|
2376
2436
|
};
|
|
2377
2437
|
}
|
|
2378
2438
|
const responseData = result.data;
|
|
@@ -2510,7 +2570,7 @@ __decorateClass([
|
|
|
2510
2570
|
], ModelPreference.prototype, "models", 2);
|
|
2511
2571
|
|
|
2512
2572
|
// src/lib/db/schema.ts
|
|
2513
|
-
var SDK_SCHEMA_VERSION =
|
|
2573
|
+
var SDK_SCHEMA_VERSION = 5;
|
|
2514
2574
|
var sdkSchema = (0, import_watermelondb6.appSchema)({
|
|
2515
2575
|
version: SDK_SCHEMA_VERSION,
|
|
2516
2576
|
tables: [
|
|
@@ -2531,7 +2591,8 @@ var sdkSchema = (0, import_watermelondb6.appSchema)({
|
|
|
2531
2591
|
{ name: "usage", type: "string", isOptional: true },
|
|
2532
2592
|
{ name: "sources", type: "string", isOptional: true },
|
|
2533
2593
|
{ name: "response_duration", type: "number", isOptional: true },
|
|
2534
|
-
{ name: "was_stopped", type: "boolean", isOptional: true }
|
|
2594
|
+
{ name: "was_stopped", type: "boolean", isOptional: true },
|
|
2595
|
+
{ name: "error", type: "string", isOptional: true }
|
|
2535
2596
|
]
|
|
2536
2597
|
}),
|
|
2537
2598
|
(0, import_watermelondb6.tableSchema)({
|
|
@@ -2598,6 +2659,16 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
|
|
|
2598
2659
|
]
|
|
2599
2660
|
})
|
|
2600
2661
|
]
|
|
2662
|
+
},
|
|
2663
|
+
// v4 -> v5: Added error column to history for error persistence
|
|
2664
|
+
{
|
|
2665
|
+
toVersion: 5,
|
|
2666
|
+
steps: [
|
|
2667
|
+
(0, import_migrations2.addColumns)({
|
|
2668
|
+
table: "history",
|
|
2669
|
+
columns: [{ name: "error", type: "string", isOptional: true }]
|
|
2670
|
+
})
|
|
2671
|
+
]
|
|
2601
2672
|
}
|
|
2602
2673
|
]
|
|
2603
2674
|
});
|
|
@@ -5641,22 +5712,588 @@ function useGoogleDriveBackup(options) {
|
|
|
5641
5712
|
};
|
|
5642
5713
|
}
|
|
5643
5714
|
|
|
5644
|
-
// src/react/
|
|
5715
|
+
// src/react/useICloudAuth.ts
|
|
5645
5716
|
var import_react14 = require("react");
|
|
5646
|
-
|
|
5717
|
+
|
|
5718
|
+
// src/lib/backup/icloud/api.ts
|
|
5719
|
+
var CLOUDKIT_JS_URL = "https://cdn.apple-cloudkit.com/ck/2/cloudkit.js";
|
|
5720
|
+
var DEFAULT_BACKUP_FOLDER2 = "conversations";
|
|
5721
|
+
var DEFAULT_CONTAINER_ID = "iCloud.Memoryless";
|
|
5722
|
+
var RECORD_TYPE = "ConversationBackup";
|
|
5723
|
+
var cloudKitLoadPromise = null;
|
|
5724
|
+
function isCloudKitAvailable() {
|
|
5725
|
+
return typeof window !== "undefined" && !!window.CloudKit;
|
|
5726
|
+
}
|
|
5727
|
+
async function loadCloudKit() {
|
|
5728
|
+
if (typeof window === "undefined") {
|
|
5729
|
+
throw new Error("CloudKit JS can only be loaded in browser environment");
|
|
5730
|
+
}
|
|
5731
|
+
if (window.CloudKit) {
|
|
5732
|
+
return;
|
|
5733
|
+
}
|
|
5734
|
+
if (cloudKitLoadPromise) {
|
|
5735
|
+
return cloudKitLoadPromise;
|
|
5736
|
+
}
|
|
5737
|
+
cloudKitLoadPromise = new Promise((resolve, reject) => {
|
|
5738
|
+
const script = document.createElement("script");
|
|
5739
|
+
script.src = CLOUDKIT_JS_URL;
|
|
5740
|
+
script.async = true;
|
|
5741
|
+
script.onload = () => {
|
|
5742
|
+
if (window.CloudKit) {
|
|
5743
|
+
resolve();
|
|
5744
|
+
} else {
|
|
5745
|
+
reject(new Error("CloudKit JS loaded but CloudKit object not found"));
|
|
5746
|
+
}
|
|
5747
|
+
};
|
|
5748
|
+
script.onerror = () => {
|
|
5749
|
+
cloudKitLoadPromise = null;
|
|
5750
|
+
reject(new Error("Failed to load CloudKit JS"));
|
|
5751
|
+
};
|
|
5752
|
+
document.head.appendChild(script);
|
|
5753
|
+
});
|
|
5754
|
+
return cloudKitLoadPromise;
|
|
5755
|
+
}
|
|
5756
|
+
async function ensureCloudKitLoaded() {
|
|
5757
|
+
if (!isCloudKitAvailable()) {
|
|
5758
|
+
await loadCloudKit();
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
async function configureCloudKit(config) {
|
|
5762
|
+
await ensureCloudKitLoaded();
|
|
5763
|
+
ensureAuthElements();
|
|
5764
|
+
window.CloudKit.configure({
|
|
5765
|
+
containers: [
|
|
5766
|
+
{
|
|
5767
|
+
containerIdentifier: config.containerIdentifier,
|
|
5768
|
+
apiTokenAuth: {
|
|
5769
|
+
apiToken: config.apiToken,
|
|
5770
|
+
persist: true,
|
|
5771
|
+
signInButton: {
|
|
5772
|
+
id: "apple-sign-in-button",
|
|
5773
|
+
theme: "black"
|
|
5774
|
+
},
|
|
5775
|
+
signOutButton: {
|
|
5776
|
+
id: "apple-sign-out-button",
|
|
5777
|
+
theme: "black"
|
|
5778
|
+
}
|
|
5779
|
+
},
|
|
5780
|
+
environment: config.environment
|
|
5781
|
+
}
|
|
5782
|
+
]
|
|
5783
|
+
});
|
|
5784
|
+
}
|
|
5785
|
+
async function getContainer() {
|
|
5786
|
+
await ensureCloudKitLoaded();
|
|
5787
|
+
return window.CloudKit.getDefaultContainer();
|
|
5788
|
+
}
|
|
5789
|
+
function ensureAuthElements() {
|
|
5790
|
+
let signInButton = document.getElementById("apple-sign-in-button");
|
|
5791
|
+
let signOutButton = document.getElementById("apple-sign-out-button");
|
|
5792
|
+
if (!signInButton) {
|
|
5793
|
+
signInButton = document.createElement("div");
|
|
5794
|
+
signInButton.id = "apple-sign-in-button";
|
|
5795
|
+
signInButton.style.position = "fixed";
|
|
5796
|
+
signInButton.style.top = "-9999px";
|
|
5797
|
+
signInButton.style.left = "-9999px";
|
|
5798
|
+
document.body.appendChild(signInButton);
|
|
5799
|
+
}
|
|
5800
|
+
if (!signOutButton) {
|
|
5801
|
+
signOutButton = document.createElement("div");
|
|
5802
|
+
signOutButton.id = "apple-sign-out-button";
|
|
5803
|
+
signOutButton.style.position = "fixed";
|
|
5804
|
+
signOutButton.style.top = "-9999px";
|
|
5805
|
+
signOutButton.style.left = "-9999px";
|
|
5806
|
+
document.body.appendChild(signOutButton);
|
|
5807
|
+
}
|
|
5808
|
+
return { signIn: signInButton, signOut: signOutButton };
|
|
5809
|
+
}
|
|
5810
|
+
async function authenticateICloud() {
|
|
5811
|
+
const container = await getContainer();
|
|
5812
|
+
ensureAuthElements();
|
|
5813
|
+
return container.setUpAuth();
|
|
5814
|
+
}
|
|
5815
|
+
async function requestICloudSignIn() {
|
|
5816
|
+
const container = await getContainer();
|
|
5817
|
+
const { signIn } = ensureAuthElements();
|
|
5818
|
+
const existingUser = await container.setUpAuth();
|
|
5819
|
+
if (existingUser) {
|
|
5820
|
+
return existingUser;
|
|
5821
|
+
}
|
|
5822
|
+
console.log("[CloudKit] Sign-in container innerHTML:", signIn.innerHTML);
|
|
5823
|
+
console.log("[CloudKit] Sign-in container children:", signIn.children.length);
|
|
5824
|
+
const appleButton = signIn.querySelector("a, button, [role='button'], div[id*='apple']");
|
|
5825
|
+
console.log("[CloudKit] Found button element:", appleButton);
|
|
5826
|
+
if (appleButton) {
|
|
5827
|
+
console.log("[CloudKit] Clicking button...");
|
|
5828
|
+
appleButton.click();
|
|
5829
|
+
} else {
|
|
5830
|
+
const anyClickable = signIn.firstElementChild;
|
|
5831
|
+
if (anyClickable) {
|
|
5832
|
+
console.log("[CloudKit] Clicking first child element:", anyClickable);
|
|
5833
|
+
anyClickable.click();
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
return container.whenUserSignsIn();
|
|
5837
|
+
}
|
|
5838
|
+
async function uploadFileToICloud(filename, content) {
|
|
5839
|
+
const container = await getContainer();
|
|
5840
|
+
const database = container.privateCloudDatabase;
|
|
5841
|
+
const recordName = `backup_${filename.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
5842
|
+
const arrayBuffer = await content.arrayBuffer();
|
|
5843
|
+
const base64Data = btoa(
|
|
5844
|
+
String.fromCharCode(...new Uint8Array(arrayBuffer))
|
|
5845
|
+
);
|
|
5846
|
+
const record = {
|
|
5847
|
+
recordType: RECORD_TYPE,
|
|
5848
|
+
recordName,
|
|
5849
|
+
fields: {
|
|
5850
|
+
filename: { value: filename },
|
|
5851
|
+
data: { value: base64Data },
|
|
5852
|
+
size: { value: content.size },
|
|
5853
|
+
contentType: { value: content.type || "application/json" }
|
|
5854
|
+
}
|
|
5855
|
+
};
|
|
5856
|
+
const response = await database.saveRecords(record);
|
|
5857
|
+
if (!response.records || response.records.length === 0) {
|
|
5858
|
+
throw new Error("Failed to upload file to iCloud");
|
|
5859
|
+
}
|
|
5860
|
+
const savedRecord = response.records[0];
|
|
5861
|
+
return {
|
|
5862
|
+
recordName: savedRecord.recordName,
|
|
5863
|
+
filename,
|
|
5864
|
+
modifiedAt: new Date(savedRecord.modified?.timestamp ?? Date.now()),
|
|
5865
|
+
size: content.size
|
|
5866
|
+
};
|
|
5867
|
+
}
|
|
5868
|
+
async function listICloudFiles() {
|
|
5869
|
+
const container = await getContainer();
|
|
5870
|
+
const database = container.privateCloudDatabase;
|
|
5871
|
+
const query = {
|
|
5872
|
+
recordType: RECORD_TYPE
|
|
5873
|
+
// Note: Sorting requires SORTABLE index on the field in CloudKit Dashboard
|
|
5874
|
+
// For now, we skip sorting and sort client-side after fetching
|
|
5875
|
+
};
|
|
5876
|
+
const allRecords = [];
|
|
5877
|
+
let response = await database.performQuery(query);
|
|
5878
|
+
if (response.records) {
|
|
5879
|
+
allRecords.push(...response.records);
|
|
5880
|
+
}
|
|
5881
|
+
while (response.continuationMarker) {
|
|
5882
|
+
break;
|
|
5883
|
+
}
|
|
5884
|
+
const files = allRecords.map((record) => ({
|
|
5885
|
+
recordName: record.recordName,
|
|
5886
|
+
filename: record.fields.filename?.value ?? "",
|
|
5887
|
+
modifiedAt: new Date(record.modified?.timestamp ?? Date.now()),
|
|
5888
|
+
size: typeof record.fields.data?.value === "object" && record.fields.data?.value !== null ? record.fields.data.value.size : 0
|
|
5889
|
+
}));
|
|
5890
|
+
return files.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
|
|
5891
|
+
}
|
|
5892
|
+
async function downloadICloudFile(recordName) {
|
|
5893
|
+
const container = await getContainer();
|
|
5894
|
+
const database = container.privateCloudDatabase;
|
|
5895
|
+
const response = await database.fetchRecords([{ recordName }], {
|
|
5896
|
+
desiredKeys: ["filename", "data", "contentType"]
|
|
5897
|
+
});
|
|
5898
|
+
if (!response.records || response.records.length === 0) {
|
|
5899
|
+
throw new Error(`File not found: ${recordName}`);
|
|
5900
|
+
}
|
|
5901
|
+
const record = response.records[0];
|
|
5902
|
+
const dataField = record.fields.data?.value;
|
|
5903
|
+
if (!dataField) {
|
|
5904
|
+
throw new Error("No data in record");
|
|
5905
|
+
}
|
|
5906
|
+
if (typeof dataField === "string") {
|
|
5907
|
+
const binaryString = atob(dataField);
|
|
5908
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
5909
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
5910
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
5911
|
+
}
|
|
5912
|
+
return new Blob([bytes], { type: "application/json" });
|
|
5913
|
+
}
|
|
5914
|
+
if (typeof dataField === "object" && "downloadURL" in dataField) {
|
|
5915
|
+
const fetchResponse = await fetch(
|
|
5916
|
+
dataField.downloadURL
|
|
5917
|
+
);
|
|
5918
|
+
if (!fetchResponse.ok) {
|
|
5919
|
+
throw new Error(`Failed to download from iCloud: ${fetchResponse.status}`);
|
|
5920
|
+
}
|
|
5921
|
+
return fetchResponse.blob();
|
|
5922
|
+
}
|
|
5923
|
+
throw new Error("Unknown data format in iCloud record");
|
|
5924
|
+
}
|
|
5925
|
+
async function findICloudFile(filename) {
|
|
5926
|
+
const container = await getContainer();
|
|
5927
|
+
const database = container.privateCloudDatabase;
|
|
5928
|
+
const query = {
|
|
5929
|
+
recordType: RECORD_TYPE,
|
|
5930
|
+
filterBy: [
|
|
5931
|
+
{
|
|
5932
|
+
fieldName: "filename",
|
|
5933
|
+
comparator: "EQUALS",
|
|
5934
|
+
fieldValue: { value: filename }
|
|
5935
|
+
}
|
|
5936
|
+
]
|
|
5937
|
+
};
|
|
5938
|
+
const response = await database.performQuery(query);
|
|
5939
|
+
if (!response.records || response.records.length === 0) {
|
|
5940
|
+
return null;
|
|
5941
|
+
}
|
|
5942
|
+
const record = response.records[0];
|
|
5943
|
+
return {
|
|
5944
|
+
recordName: record.recordName,
|
|
5945
|
+
filename: record.fields.filename?.value ?? "",
|
|
5946
|
+
modifiedAt: new Date(record.modified?.timestamp ?? Date.now()),
|
|
5947
|
+
size: typeof record.fields.data?.value === "object" && record.fields.data?.value !== null ? record.fields.data.value.size : 0
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
// src/react/useICloudAuth.ts
|
|
5952
|
+
var ICloudAuthContext = (0, import_react14.createContext)(null);
|
|
5953
|
+
function ICloudAuthProvider({
|
|
5954
|
+
apiToken,
|
|
5955
|
+
containerIdentifier = DEFAULT_CONTAINER_ID,
|
|
5956
|
+
environment = "production",
|
|
5957
|
+
children
|
|
5958
|
+
}) {
|
|
5959
|
+
const [isAuthenticated, setIsAuthenticated] = (0, import_react14.useState)(false);
|
|
5960
|
+
const [userRecordName, setUserRecordName] = (0, import_react14.useState)(null);
|
|
5961
|
+
const [isAvailable, setIsAvailable] = (0, import_react14.useState)(false);
|
|
5962
|
+
const [isConfigured, setIsConfigured] = (0, import_react14.useState)(false);
|
|
5963
|
+
const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
|
|
5964
|
+
(0, import_react14.useEffect)(() => {
|
|
5965
|
+
if (!apiToken || typeof window === "undefined") {
|
|
5966
|
+
return;
|
|
5967
|
+
}
|
|
5968
|
+
const initCloudKit = async () => {
|
|
5969
|
+
setIsLoading(true);
|
|
5970
|
+
try {
|
|
5971
|
+
await loadCloudKit();
|
|
5972
|
+
setIsAvailable(true);
|
|
5973
|
+
const config = {
|
|
5974
|
+
containerIdentifier,
|
|
5975
|
+
apiToken,
|
|
5976
|
+
environment
|
|
5977
|
+
};
|
|
5978
|
+
await configureCloudKit(config);
|
|
5979
|
+
setIsConfigured(true);
|
|
5980
|
+
try {
|
|
5981
|
+
const userIdentity = await authenticateICloud();
|
|
5982
|
+
if (userIdentity) {
|
|
5983
|
+
setIsAuthenticated(true);
|
|
5984
|
+
setUserRecordName(userIdentity.userRecordName);
|
|
5985
|
+
}
|
|
5986
|
+
} catch {
|
|
5987
|
+
}
|
|
5988
|
+
} catch {
|
|
5989
|
+
setIsAvailable(false);
|
|
5990
|
+
setIsConfigured(false);
|
|
5991
|
+
} finally {
|
|
5992
|
+
setIsLoading(false);
|
|
5993
|
+
}
|
|
5994
|
+
};
|
|
5995
|
+
initCloudKit();
|
|
5996
|
+
}, [apiToken, containerIdentifier, environment]);
|
|
5997
|
+
const requestAccess = (0, import_react14.useCallback)(async () => {
|
|
5998
|
+
if (!isConfigured) {
|
|
5999
|
+
throw new Error("iCloud is not configured");
|
|
6000
|
+
}
|
|
6001
|
+
if (isAuthenticated) {
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
try {
|
|
6005
|
+
const userIdentity = await requestICloudSignIn();
|
|
6006
|
+
setIsAuthenticated(true);
|
|
6007
|
+
setUserRecordName(userIdentity.userRecordName);
|
|
6008
|
+
} catch (err) {
|
|
6009
|
+
throw new Error(
|
|
6010
|
+
err instanceof Error ? err.message : "Failed to sign in to iCloud"
|
|
6011
|
+
);
|
|
6012
|
+
}
|
|
6013
|
+
}, [isAuthenticated, isConfigured]);
|
|
6014
|
+
const logout = (0, import_react14.useCallback)(() => {
|
|
6015
|
+
setIsAuthenticated(false);
|
|
6016
|
+
setUserRecordName(null);
|
|
6017
|
+
}, []);
|
|
6018
|
+
return (0, import_react14.createElement)(
|
|
6019
|
+
ICloudAuthContext.Provider,
|
|
6020
|
+
{
|
|
6021
|
+
value: {
|
|
6022
|
+
isAuthenticated,
|
|
6023
|
+
isConfigured,
|
|
6024
|
+
isAvailable,
|
|
6025
|
+
userRecordName,
|
|
6026
|
+
requestAccess,
|
|
6027
|
+
logout
|
|
6028
|
+
}
|
|
6029
|
+
},
|
|
6030
|
+
children
|
|
6031
|
+
);
|
|
6032
|
+
}
|
|
6033
|
+
function useICloudAuth() {
|
|
6034
|
+
const context = (0, import_react14.useContext)(ICloudAuthContext);
|
|
6035
|
+
if (!context) {
|
|
6036
|
+
throw new Error("useICloudAuth must be used within ICloudAuthProvider");
|
|
6037
|
+
}
|
|
6038
|
+
return context;
|
|
6039
|
+
}
|
|
6040
|
+
function hasICloudCredentials() {
|
|
6041
|
+
return isCloudKitAvailable();
|
|
6042
|
+
}
|
|
6043
|
+
function clearICloudAuth() {
|
|
6044
|
+
}
|
|
6045
|
+
|
|
6046
|
+
// src/react/useICloudBackup.ts
|
|
6047
|
+
var import_react15 = require("react");
|
|
6048
|
+
|
|
6049
|
+
// src/lib/backup/icloud/backup.ts
|
|
6050
|
+
var isAuthError3 = (err) => err instanceof Error && (err.message.includes("AUTHENTICATION") || err.message.includes("NOT_AUTHENTICATED") || err.message.includes("sign in"));
|
|
6051
|
+
async function pushConversationToICloud(database, conversationId, userAddress, deps, _retried = false) {
|
|
6052
|
+
try {
|
|
6053
|
+
await deps.requestEncryptionKey(userAddress);
|
|
6054
|
+
const filename = `${conversationId}.json`;
|
|
6055
|
+
const existingFile = await findICloudFile(filename);
|
|
6056
|
+
if (existingFile) {
|
|
6057
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
6058
|
+
const conversationsCollection = database.get("conversations");
|
|
6059
|
+
const records = await conversationsCollection.query(Q4.where("conversation_id", conversationId)).fetch();
|
|
6060
|
+
if (records.length > 0) {
|
|
6061
|
+
const conversation = conversationToStored(records[0]);
|
|
6062
|
+
const localUpdated = conversation.updatedAt.getTime();
|
|
6063
|
+
const remoteModified = existingFile.modifiedAt.getTime();
|
|
6064
|
+
if (localUpdated <= remoteModified) {
|
|
6065
|
+
return "skipped";
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
const exportResult = await deps.exportConversation(
|
|
6070
|
+
conversationId,
|
|
6071
|
+
userAddress
|
|
6072
|
+
);
|
|
6073
|
+
if (!exportResult.success || !exportResult.blob) {
|
|
6074
|
+
return "failed";
|
|
6075
|
+
}
|
|
6076
|
+
await uploadFileToICloud(filename, exportResult.blob);
|
|
6077
|
+
return "uploaded";
|
|
6078
|
+
} catch (err) {
|
|
6079
|
+
if (isAuthError3(err) && !_retried) {
|
|
6080
|
+
try {
|
|
6081
|
+
await deps.requestICloudAccess();
|
|
6082
|
+
return pushConversationToICloud(
|
|
6083
|
+
database,
|
|
6084
|
+
conversationId,
|
|
6085
|
+
userAddress,
|
|
6086
|
+
deps,
|
|
6087
|
+
true
|
|
6088
|
+
);
|
|
6089
|
+
} catch {
|
|
6090
|
+
return "failed";
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6093
|
+
return "failed";
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
async function performICloudExport(database, userAddress, deps, onProgress) {
|
|
6097
|
+
await deps.requestEncryptionKey(userAddress);
|
|
6098
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
6099
|
+
const conversationsCollection = database.get("conversations");
|
|
6100
|
+
const records = await conversationsCollection.query(Q4.where("is_deleted", false)).fetch();
|
|
6101
|
+
const conversations = records.map(conversationToStored);
|
|
6102
|
+
const total = conversations.length;
|
|
6103
|
+
if (total === 0) {
|
|
6104
|
+
return { success: true, uploaded: 0, skipped: 0, total: 0 };
|
|
6105
|
+
}
|
|
6106
|
+
let uploaded = 0;
|
|
6107
|
+
let skipped = 0;
|
|
6108
|
+
for (let i = 0; i < conversations.length; i++) {
|
|
6109
|
+
const conv = conversations[i];
|
|
6110
|
+
onProgress?.(i + 1, total);
|
|
6111
|
+
const result = await pushConversationToICloud(
|
|
6112
|
+
database,
|
|
6113
|
+
conv.conversationId,
|
|
6114
|
+
userAddress,
|
|
6115
|
+
deps
|
|
6116
|
+
);
|
|
6117
|
+
if (result === "uploaded") uploaded++;
|
|
6118
|
+
if (result === "skipped") skipped++;
|
|
6119
|
+
}
|
|
6120
|
+
return { success: true, uploaded, skipped, total };
|
|
6121
|
+
}
|
|
6122
|
+
async function performICloudImport(userAddress, deps, onProgress) {
|
|
6123
|
+
await deps.requestEncryptionKey(userAddress);
|
|
6124
|
+
const remoteFiles = await listICloudFiles();
|
|
6125
|
+
if (remoteFiles.length === 0) {
|
|
6126
|
+
return {
|
|
6127
|
+
success: false,
|
|
6128
|
+
restored: 0,
|
|
6129
|
+
failed: 0,
|
|
6130
|
+
total: 0,
|
|
6131
|
+
noBackupsFound: true
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
6134
|
+
const jsonFiles = remoteFiles.filter(
|
|
6135
|
+
(file) => file.filename.endsWith(".json")
|
|
6136
|
+
);
|
|
6137
|
+
const total = jsonFiles.length;
|
|
6138
|
+
let restored = 0;
|
|
6139
|
+
let failed = 0;
|
|
6140
|
+
for (let i = 0; i < jsonFiles.length; i++) {
|
|
6141
|
+
const file = jsonFiles[i];
|
|
6142
|
+
onProgress?.(i + 1, total);
|
|
6143
|
+
try {
|
|
6144
|
+
const blob = await downloadICloudFile(file.recordName);
|
|
6145
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
6146
|
+
if (result.success) {
|
|
6147
|
+
restored++;
|
|
6148
|
+
} else {
|
|
6149
|
+
failed++;
|
|
6150
|
+
}
|
|
6151
|
+
} catch (err) {
|
|
6152
|
+
if (isAuthError3(err)) {
|
|
6153
|
+
try {
|
|
6154
|
+
await deps.requestICloudAccess();
|
|
6155
|
+
const blob = await downloadICloudFile(file.recordName);
|
|
6156
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
6157
|
+
if (result.success) {
|
|
6158
|
+
restored++;
|
|
6159
|
+
} else {
|
|
6160
|
+
failed++;
|
|
6161
|
+
}
|
|
6162
|
+
} catch {
|
|
6163
|
+
failed++;
|
|
6164
|
+
}
|
|
6165
|
+
} else {
|
|
6166
|
+
failed++;
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
return { success: true, restored, failed, total };
|
|
6171
|
+
}
|
|
6172
|
+
|
|
6173
|
+
// src/react/useICloudBackup.ts
|
|
6174
|
+
function useICloudBackup(options) {
|
|
6175
|
+
const {
|
|
6176
|
+
database,
|
|
6177
|
+
userAddress,
|
|
6178
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
6179
|
+
exportConversation,
|
|
6180
|
+
importConversation
|
|
6181
|
+
} = options;
|
|
6182
|
+
const {
|
|
6183
|
+
isAuthenticated,
|
|
6184
|
+
isConfigured,
|
|
6185
|
+
isAvailable,
|
|
6186
|
+
requestAccess
|
|
6187
|
+
} = useICloudAuth();
|
|
6188
|
+
const deps = (0, import_react15.useMemo)(
|
|
6189
|
+
() => ({
|
|
6190
|
+
requestICloudAccess: requestAccess,
|
|
6191
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
6192
|
+
exportConversation,
|
|
6193
|
+
importConversation
|
|
6194
|
+
}),
|
|
6195
|
+
[requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
6196
|
+
);
|
|
6197
|
+
const ensureAuthenticated = (0, import_react15.useCallback)(async () => {
|
|
6198
|
+
if (isAuthenticated) return true;
|
|
6199
|
+
try {
|
|
6200
|
+
await requestAccess();
|
|
6201
|
+
return true;
|
|
6202
|
+
} catch {
|
|
6203
|
+
return false;
|
|
6204
|
+
}
|
|
6205
|
+
}, [isAuthenticated, requestAccess]);
|
|
6206
|
+
const backup = (0, import_react15.useCallback)(
|
|
6207
|
+
async (backupOptions) => {
|
|
6208
|
+
if (!userAddress) {
|
|
6209
|
+
return { error: "Please sign in to backup to iCloud" };
|
|
6210
|
+
}
|
|
6211
|
+
if (!isAvailable) {
|
|
6212
|
+
return { error: "CloudKit JS is not loaded" };
|
|
6213
|
+
}
|
|
6214
|
+
if (!isConfigured) {
|
|
6215
|
+
return { error: "iCloud is not configured" };
|
|
6216
|
+
}
|
|
6217
|
+
const authenticated = await ensureAuthenticated();
|
|
6218
|
+
if (!authenticated) {
|
|
6219
|
+
return { error: "iCloud access denied" };
|
|
6220
|
+
}
|
|
6221
|
+
try {
|
|
6222
|
+
return await performICloudExport(
|
|
6223
|
+
database,
|
|
6224
|
+
userAddress,
|
|
6225
|
+
deps,
|
|
6226
|
+
backupOptions?.onProgress
|
|
6227
|
+
);
|
|
6228
|
+
} catch (err) {
|
|
6229
|
+
return {
|
|
6230
|
+
error: err instanceof Error ? err.message : "Failed to backup to iCloud"
|
|
6231
|
+
};
|
|
6232
|
+
}
|
|
6233
|
+
},
|
|
6234
|
+
[database, userAddress, isAvailable, isConfigured, ensureAuthenticated, deps]
|
|
6235
|
+
);
|
|
6236
|
+
const restore = (0, import_react15.useCallback)(
|
|
6237
|
+
async (restoreOptions) => {
|
|
6238
|
+
if (!userAddress) {
|
|
6239
|
+
return { error: "Please sign in to restore from iCloud" };
|
|
6240
|
+
}
|
|
6241
|
+
if (!isAvailable) {
|
|
6242
|
+
return { error: "CloudKit JS is not loaded" };
|
|
6243
|
+
}
|
|
6244
|
+
if (!isConfigured) {
|
|
6245
|
+
return { error: "iCloud is not configured" };
|
|
6246
|
+
}
|
|
6247
|
+
const authenticated = await ensureAuthenticated();
|
|
6248
|
+
if (!authenticated) {
|
|
6249
|
+
return { error: "iCloud access denied" };
|
|
6250
|
+
}
|
|
6251
|
+
try {
|
|
6252
|
+
return await performICloudImport(
|
|
6253
|
+
userAddress,
|
|
6254
|
+
deps,
|
|
6255
|
+
restoreOptions?.onProgress
|
|
6256
|
+
);
|
|
6257
|
+
} catch (err) {
|
|
6258
|
+
return {
|
|
6259
|
+
error: err instanceof Error ? err.message : "Failed to restore from iCloud"
|
|
6260
|
+
};
|
|
6261
|
+
}
|
|
6262
|
+
},
|
|
6263
|
+
[userAddress, isAvailable, isConfigured, ensureAuthenticated, deps]
|
|
6264
|
+
);
|
|
6265
|
+
return {
|
|
6266
|
+
backup,
|
|
6267
|
+
restore,
|
|
6268
|
+
isConfigured,
|
|
6269
|
+
isAuthenticated,
|
|
6270
|
+
isAvailable
|
|
6271
|
+
};
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6274
|
+
// src/react/useBackupAuth.ts
|
|
6275
|
+
var import_react16 = require("react");
|
|
6276
|
+
var BackupAuthContext = (0, import_react16.createContext)(null);
|
|
5647
6277
|
function BackupAuthProvider({
|
|
5648
6278
|
dropboxAppKey,
|
|
5649
6279
|
dropboxCallbackPath = "/auth/dropbox/callback",
|
|
5650
6280
|
googleClientId,
|
|
5651
6281
|
googleCallbackPath = "/auth/google/callback",
|
|
6282
|
+
icloudApiToken,
|
|
6283
|
+
icloudContainerIdentifier = DEFAULT_CONTAINER_ID,
|
|
6284
|
+
icloudEnvironment = "production",
|
|
5652
6285
|
apiClient,
|
|
5653
6286
|
children
|
|
5654
6287
|
}) {
|
|
5655
|
-
const [dropboxToken, setDropboxToken] = (0,
|
|
6288
|
+
const [dropboxToken, setDropboxToken] = (0, import_react16.useState)(null);
|
|
5656
6289
|
const isDropboxConfigured = !!dropboxAppKey;
|
|
5657
|
-
const [googleToken, setGoogleToken] = (0,
|
|
6290
|
+
const [googleToken, setGoogleToken] = (0, import_react16.useState)(null);
|
|
5658
6291
|
const isGoogleConfigured = !!googleClientId;
|
|
5659
|
-
(0,
|
|
6292
|
+
const [icloudAuthenticated, setIcloudAuthenticated] = (0, import_react16.useState)(false);
|
|
6293
|
+
const [icloudUserRecordName, setIcloudUserRecordName] = (0, import_react16.useState)(null);
|
|
6294
|
+
const [isIcloudAvailable, setIsIcloudAvailable] = (0, import_react16.useState)(false);
|
|
6295
|
+
const isIcloudConfigured = isIcloudAvailable && !!icloudApiToken;
|
|
6296
|
+
(0, import_react16.useEffect)(() => {
|
|
5660
6297
|
const checkStoredTokens = async () => {
|
|
5661
6298
|
if (hasDropboxCredentials()) {
|
|
5662
6299
|
const token = await getDropboxAccessToken(apiClient);
|
|
@@ -5673,7 +6310,35 @@ function BackupAuthProvider({
|
|
|
5673
6310
|
};
|
|
5674
6311
|
checkStoredTokens();
|
|
5675
6312
|
}, [apiClient]);
|
|
5676
|
-
(0,
|
|
6313
|
+
(0, import_react16.useEffect)(() => {
|
|
6314
|
+
if (!icloudApiToken || typeof window === "undefined") {
|
|
6315
|
+
return;
|
|
6316
|
+
}
|
|
6317
|
+
const initCloudKit = async () => {
|
|
6318
|
+
try {
|
|
6319
|
+
await loadCloudKit();
|
|
6320
|
+
setIsIcloudAvailable(true);
|
|
6321
|
+
const config = {
|
|
6322
|
+
containerIdentifier: icloudContainerIdentifier,
|
|
6323
|
+
apiToken: icloudApiToken,
|
|
6324
|
+
environment: icloudEnvironment
|
|
6325
|
+
};
|
|
6326
|
+
await configureCloudKit(config);
|
|
6327
|
+
try {
|
|
6328
|
+
const userIdentity = await authenticateICloud();
|
|
6329
|
+
if (userIdentity) {
|
|
6330
|
+
setIcloudAuthenticated(true);
|
|
6331
|
+
setIcloudUserRecordName(userIdentity.userRecordName);
|
|
6332
|
+
}
|
|
6333
|
+
} catch {
|
|
6334
|
+
}
|
|
6335
|
+
} catch {
|
|
6336
|
+
setIsIcloudAvailable(false);
|
|
6337
|
+
}
|
|
6338
|
+
};
|
|
6339
|
+
initCloudKit();
|
|
6340
|
+
}, [icloudApiToken, icloudContainerIdentifier, icloudEnvironment]);
|
|
6341
|
+
(0, import_react16.useEffect)(() => {
|
|
5677
6342
|
if (!isDropboxConfigured) return;
|
|
5678
6343
|
const handleCallback = async () => {
|
|
5679
6344
|
if (isDropboxCallback()) {
|
|
@@ -5688,7 +6353,7 @@ function BackupAuthProvider({
|
|
|
5688
6353
|
};
|
|
5689
6354
|
handleCallback();
|
|
5690
6355
|
}, [dropboxCallbackPath, isDropboxConfigured, apiClient]);
|
|
5691
|
-
(0,
|
|
6356
|
+
(0, import_react16.useEffect)(() => {
|
|
5692
6357
|
if (!isGoogleConfigured) return;
|
|
5693
6358
|
const handleCallback = async () => {
|
|
5694
6359
|
if (isGoogleDriveCallback()) {
|
|
@@ -5703,14 +6368,14 @@ function BackupAuthProvider({
|
|
|
5703
6368
|
};
|
|
5704
6369
|
handleCallback();
|
|
5705
6370
|
}, [googleCallbackPath, isGoogleConfigured, apiClient]);
|
|
5706
|
-
const refreshDropboxTokenFn = (0,
|
|
6371
|
+
const refreshDropboxTokenFn = (0, import_react16.useCallback)(async () => {
|
|
5707
6372
|
const token = await getDropboxAccessToken(apiClient);
|
|
5708
6373
|
if (token) {
|
|
5709
6374
|
setDropboxToken(token);
|
|
5710
6375
|
}
|
|
5711
6376
|
return token;
|
|
5712
6377
|
}, [apiClient]);
|
|
5713
|
-
const requestDropboxAccess = (0,
|
|
6378
|
+
const requestDropboxAccess = (0, import_react16.useCallback)(async () => {
|
|
5714
6379
|
if (!isDropboxConfigured || !dropboxAppKey) {
|
|
5715
6380
|
throw new Error("Dropbox is not configured");
|
|
5716
6381
|
}
|
|
@@ -5730,18 +6395,18 @@ function BackupAuthProvider({
|
|
|
5730
6395
|
isDropboxConfigured,
|
|
5731
6396
|
apiClient
|
|
5732
6397
|
]);
|
|
5733
|
-
const logoutDropbox = (0,
|
|
6398
|
+
const logoutDropbox = (0, import_react16.useCallback)(async () => {
|
|
5734
6399
|
await revokeDropboxToken(apiClient);
|
|
5735
6400
|
setDropboxToken(null);
|
|
5736
6401
|
}, [apiClient]);
|
|
5737
|
-
const refreshGoogleTokenFn = (0,
|
|
6402
|
+
const refreshGoogleTokenFn = (0, import_react16.useCallback)(async () => {
|
|
5738
6403
|
const token = await getGoogleDriveAccessToken(apiClient);
|
|
5739
6404
|
if (token) {
|
|
5740
6405
|
setGoogleToken(token);
|
|
5741
6406
|
}
|
|
5742
6407
|
return token;
|
|
5743
6408
|
}, [apiClient]);
|
|
5744
|
-
const requestGoogleAccess = (0,
|
|
6409
|
+
const requestGoogleAccess = (0, import_react16.useCallback)(async () => {
|
|
5745
6410
|
if (!isGoogleConfigured || !googleClientId) {
|
|
5746
6411
|
throw new Error("Google Drive is not configured");
|
|
5747
6412
|
}
|
|
@@ -5761,16 +6426,51 @@ function BackupAuthProvider({
|
|
|
5761
6426
|
isGoogleConfigured,
|
|
5762
6427
|
apiClient
|
|
5763
6428
|
]);
|
|
5764
|
-
const logoutGoogle = (0,
|
|
6429
|
+
const logoutGoogle = (0, import_react16.useCallback)(async () => {
|
|
5765
6430
|
await revokeGoogleDriveToken(apiClient);
|
|
5766
6431
|
setGoogleToken(null);
|
|
5767
6432
|
}, [apiClient]);
|
|
5768
|
-
const
|
|
6433
|
+
const refreshIcloudTokenFn = (0, import_react16.useCallback)(async () => {
|
|
6434
|
+
try {
|
|
6435
|
+
const userIdentity = await authenticateICloud();
|
|
6436
|
+
if (userIdentity) {
|
|
6437
|
+
setIcloudAuthenticated(true);
|
|
6438
|
+
setIcloudUserRecordName(userIdentity.userRecordName);
|
|
6439
|
+
return userIdentity.userRecordName;
|
|
6440
|
+
}
|
|
6441
|
+
} catch {
|
|
6442
|
+
}
|
|
6443
|
+
return null;
|
|
6444
|
+
}, []);
|
|
6445
|
+
const requestIcloudAccess = (0, import_react16.useCallback)(async () => {
|
|
6446
|
+
if (!isIcloudConfigured) {
|
|
6447
|
+
throw new Error("iCloud is not configured");
|
|
6448
|
+
}
|
|
6449
|
+
if (icloudAuthenticated && icloudUserRecordName) {
|
|
6450
|
+
return icloudUserRecordName;
|
|
6451
|
+
}
|
|
6452
|
+
try {
|
|
6453
|
+
const userIdentity = await requestICloudSignIn();
|
|
6454
|
+
setIcloudAuthenticated(true);
|
|
6455
|
+
setIcloudUserRecordName(userIdentity.userRecordName);
|
|
6456
|
+
return userIdentity.userRecordName;
|
|
6457
|
+
} catch (err) {
|
|
6458
|
+
throw new Error(
|
|
6459
|
+
err instanceof Error ? err.message : "Failed to sign in to iCloud"
|
|
6460
|
+
);
|
|
6461
|
+
}
|
|
6462
|
+
}, [icloudAuthenticated, icloudUserRecordName, isIcloudConfigured]);
|
|
6463
|
+
const logoutIcloud = (0, import_react16.useCallback)(async () => {
|
|
6464
|
+
setIcloudAuthenticated(false);
|
|
6465
|
+
setIcloudUserRecordName(null);
|
|
6466
|
+
}, []);
|
|
6467
|
+
const logoutAll = (0, import_react16.useCallback)(async () => {
|
|
5769
6468
|
await Promise.all([
|
|
5770
6469
|
isDropboxConfigured ? logoutDropbox() : Promise.resolve(),
|
|
5771
|
-
isGoogleConfigured ? logoutGoogle() : Promise.resolve()
|
|
6470
|
+
isGoogleConfigured ? logoutGoogle() : Promise.resolve(),
|
|
6471
|
+
isIcloudConfigured ? logoutIcloud() : Promise.resolve()
|
|
5772
6472
|
]);
|
|
5773
|
-
}, [isDropboxConfigured, isGoogleConfigured, logoutDropbox, logoutGoogle]);
|
|
6473
|
+
}, [isDropboxConfigured, isGoogleConfigured, isIcloudConfigured, logoutDropbox, logoutGoogle, logoutIcloud]);
|
|
5774
6474
|
const dropboxState = {
|
|
5775
6475
|
accessToken: dropboxToken,
|
|
5776
6476
|
isAuthenticated: !!dropboxToken,
|
|
@@ -5787,14 +6487,24 @@ function BackupAuthProvider({
|
|
|
5787
6487
|
logout: logoutGoogle,
|
|
5788
6488
|
refreshToken: refreshGoogleTokenFn
|
|
5789
6489
|
};
|
|
5790
|
-
|
|
6490
|
+
const icloudState = {
|
|
6491
|
+
accessToken: icloudUserRecordName,
|
|
6492
|
+
// Use userRecordName as the "token" for iCloud
|
|
6493
|
+
isAuthenticated: icloudAuthenticated,
|
|
6494
|
+
isConfigured: isIcloudConfigured,
|
|
6495
|
+
requestAccess: requestIcloudAccess,
|
|
6496
|
+
logout: logoutIcloud,
|
|
6497
|
+
refreshToken: refreshIcloudTokenFn
|
|
6498
|
+
};
|
|
6499
|
+
return (0, import_react16.createElement)(
|
|
5791
6500
|
BackupAuthContext.Provider,
|
|
5792
6501
|
{
|
|
5793
6502
|
value: {
|
|
5794
6503
|
dropbox: dropboxState,
|
|
5795
6504
|
googleDrive: googleDriveState,
|
|
5796
|
-
|
|
5797
|
-
|
|
6505
|
+
icloud: icloudState,
|
|
6506
|
+
hasAnyProvider: isDropboxConfigured || isGoogleConfigured || isIcloudConfigured,
|
|
6507
|
+
hasAnyAuthentication: !!dropboxToken || !!googleToken || icloudAuthenticated,
|
|
5798
6508
|
logoutAll
|
|
5799
6509
|
}
|
|
5800
6510
|
},
|
|
@@ -5802,7 +6512,7 @@ function BackupAuthProvider({
|
|
|
5802
6512
|
);
|
|
5803
6513
|
}
|
|
5804
6514
|
function useBackupAuth() {
|
|
5805
|
-
const context = (0,
|
|
6515
|
+
const context = (0, import_react16.useContext)(BackupAuthContext);
|
|
5806
6516
|
if (!context) {
|
|
5807
6517
|
throw new Error("useBackupAuth must be used within BackupAuthProvider");
|
|
5808
6518
|
}
|
|
@@ -5810,7 +6520,7 @@ function useBackupAuth() {
|
|
|
5810
6520
|
}
|
|
5811
6521
|
|
|
5812
6522
|
// src/react/useBackup.ts
|
|
5813
|
-
var
|
|
6523
|
+
var import_react17 = require("react");
|
|
5814
6524
|
function useBackup(options) {
|
|
5815
6525
|
const {
|
|
5816
6526
|
database,
|
|
@@ -5825,11 +6535,12 @@ function useBackup(options) {
|
|
|
5825
6535
|
const {
|
|
5826
6536
|
dropbox: dropboxAuth,
|
|
5827
6537
|
googleDrive: googleDriveAuth,
|
|
6538
|
+
icloud: icloudAuth,
|
|
5828
6539
|
hasAnyProvider,
|
|
5829
6540
|
hasAnyAuthentication,
|
|
5830
6541
|
logoutAll
|
|
5831
6542
|
} = useBackupAuth();
|
|
5832
|
-
const dropboxDeps = (0,
|
|
6543
|
+
const dropboxDeps = (0, import_react17.useMemo)(
|
|
5833
6544
|
() => ({
|
|
5834
6545
|
requestDropboxAccess: dropboxAuth.requestAccess,
|
|
5835
6546
|
requestEncryptionKey: requestEncryptionKey2,
|
|
@@ -5838,7 +6549,7 @@ function useBackup(options) {
|
|
|
5838
6549
|
}),
|
|
5839
6550
|
[dropboxAuth.requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
5840
6551
|
);
|
|
5841
|
-
const googleDriveDeps = (0,
|
|
6552
|
+
const googleDriveDeps = (0, import_react17.useMemo)(
|
|
5842
6553
|
() => ({
|
|
5843
6554
|
requestDriveAccess: googleDriveAuth.requestAccess,
|
|
5844
6555
|
requestEncryptionKey: requestEncryptionKey2,
|
|
@@ -5847,7 +6558,18 @@ function useBackup(options) {
|
|
|
5847
6558
|
}),
|
|
5848
6559
|
[googleDriveAuth.requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
5849
6560
|
);
|
|
5850
|
-
const
|
|
6561
|
+
const icloudDeps = (0, import_react17.useMemo)(
|
|
6562
|
+
() => ({
|
|
6563
|
+
requestICloudAccess: async () => {
|
|
6564
|
+
await icloudAuth.requestAccess();
|
|
6565
|
+
},
|
|
6566
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
6567
|
+
exportConversation,
|
|
6568
|
+
importConversation
|
|
6569
|
+
}),
|
|
6570
|
+
[icloudAuth.requestAccess, requestEncryptionKey2, exportConversation, importConversation]
|
|
6571
|
+
);
|
|
6572
|
+
const dropboxBackup = (0, import_react17.useCallback)(
|
|
5851
6573
|
async (backupOptions) => {
|
|
5852
6574
|
if (!userAddress) {
|
|
5853
6575
|
return { error: "Please sign in to backup to Dropbox" };
|
|
@@ -5877,7 +6599,7 @@ function useBackup(options) {
|
|
|
5877
6599
|
},
|
|
5878
6600
|
[database, userAddress, dropboxAuth, dropboxDeps, dropboxFolder]
|
|
5879
6601
|
);
|
|
5880
|
-
const dropboxRestore = (0,
|
|
6602
|
+
const dropboxRestore = (0, import_react17.useCallback)(
|
|
5881
6603
|
async (restoreOptions) => {
|
|
5882
6604
|
if (!userAddress) {
|
|
5883
6605
|
return { error: "Please sign in to restore from Dropbox" };
|
|
@@ -5906,7 +6628,7 @@ function useBackup(options) {
|
|
|
5906
6628
|
},
|
|
5907
6629
|
[userAddress, dropboxAuth, dropboxDeps, dropboxFolder]
|
|
5908
6630
|
);
|
|
5909
|
-
const googleDriveBackup = (0,
|
|
6631
|
+
const googleDriveBackup = (0, import_react17.useCallback)(
|
|
5910
6632
|
async (backupOptions) => {
|
|
5911
6633
|
if (!userAddress) {
|
|
5912
6634
|
return { error: "Please sign in to backup to Google Drive" };
|
|
@@ -5944,7 +6666,7 @@ function useBackup(options) {
|
|
|
5944
6666
|
googleConversationsFolder
|
|
5945
6667
|
]
|
|
5946
6668
|
);
|
|
5947
|
-
const googleDriveRestore = (0,
|
|
6669
|
+
const googleDriveRestore = (0, import_react17.useCallback)(
|
|
5948
6670
|
async (restoreOptions) => {
|
|
5949
6671
|
if (!userAddress) {
|
|
5950
6672
|
return { error: "Please sign in to restore from Google Drive" };
|
|
@@ -5996,9 +6718,77 @@ function useBackup(options) {
|
|
|
5996
6718
|
connect: googleDriveAuth.requestAccess,
|
|
5997
6719
|
disconnect: googleDriveAuth.logout
|
|
5998
6720
|
};
|
|
6721
|
+
const icloudBackup = (0, import_react17.useCallback)(
|
|
6722
|
+
async (backupOptions) => {
|
|
6723
|
+
if (!userAddress) {
|
|
6724
|
+
return { error: "Please sign in to backup to iCloud" };
|
|
6725
|
+
}
|
|
6726
|
+
if (!icloudAuth.isConfigured) {
|
|
6727
|
+
return { error: "iCloud is not configured" };
|
|
6728
|
+
}
|
|
6729
|
+
if (!icloudAuth.isAuthenticated) {
|
|
6730
|
+
try {
|
|
6731
|
+
await icloudAuth.requestAccess();
|
|
6732
|
+
} catch {
|
|
6733
|
+
return { error: "iCloud access denied" };
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
try {
|
|
6737
|
+
return await performICloudExport(
|
|
6738
|
+
database,
|
|
6739
|
+
userAddress,
|
|
6740
|
+
icloudDeps,
|
|
6741
|
+
backupOptions?.onProgress
|
|
6742
|
+
);
|
|
6743
|
+
} catch (err) {
|
|
6744
|
+
return {
|
|
6745
|
+
error: err instanceof Error ? err.message : "Failed to backup to iCloud"
|
|
6746
|
+
};
|
|
6747
|
+
}
|
|
6748
|
+
},
|
|
6749
|
+
[database, userAddress, icloudAuth, icloudDeps]
|
|
6750
|
+
);
|
|
6751
|
+
const icloudRestore = (0, import_react17.useCallback)(
|
|
6752
|
+
async (restoreOptions) => {
|
|
6753
|
+
if (!userAddress) {
|
|
6754
|
+
return { error: "Please sign in to restore from iCloud" };
|
|
6755
|
+
}
|
|
6756
|
+
if (!icloudAuth.isConfigured) {
|
|
6757
|
+
return { error: "iCloud is not configured" };
|
|
6758
|
+
}
|
|
6759
|
+
if (!icloudAuth.isAuthenticated) {
|
|
6760
|
+
try {
|
|
6761
|
+
await icloudAuth.requestAccess();
|
|
6762
|
+
} catch {
|
|
6763
|
+
return { error: "iCloud access denied" };
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
try {
|
|
6767
|
+
return await performICloudImport(
|
|
6768
|
+
userAddress,
|
|
6769
|
+
icloudDeps,
|
|
6770
|
+
restoreOptions?.onProgress
|
|
6771
|
+
);
|
|
6772
|
+
} catch (err) {
|
|
6773
|
+
return {
|
|
6774
|
+
error: err instanceof Error ? err.message : "Failed to restore from iCloud"
|
|
6775
|
+
};
|
|
6776
|
+
}
|
|
6777
|
+
},
|
|
6778
|
+
[userAddress, icloudAuth, icloudDeps]
|
|
6779
|
+
);
|
|
6780
|
+
const icloudState = {
|
|
6781
|
+
isConfigured: icloudAuth.isConfigured,
|
|
6782
|
+
isAuthenticated: icloudAuth.isAuthenticated,
|
|
6783
|
+
backup: icloudBackup,
|
|
6784
|
+
restore: icloudRestore,
|
|
6785
|
+
connect: icloudAuth.requestAccess,
|
|
6786
|
+
disconnect: icloudAuth.logout
|
|
6787
|
+
};
|
|
5999
6788
|
return {
|
|
6000
6789
|
dropbox: dropboxState,
|
|
6001
6790
|
googleDrive: googleDriveState,
|
|
6791
|
+
icloud: icloudState,
|
|
6002
6792
|
hasAnyProvider,
|
|
6003
6793
|
hasAnyAuthentication,
|
|
6004
6794
|
disconnectAll: logoutAll
|
|
@@ -6008,6 +6798,7 @@ function useBackup(options) {
|
|
|
6008
6798
|
0 && (module.exports = {
|
|
6009
6799
|
BACKUP_DRIVE_CONVERSATIONS_FOLDER,
|
|
6010
6800
|
BACKUP_DRIVE_ROOT_FOLDER,
|
|
6801
|
+
BACKUP_ICLOUD_FOLDER,
|
|
6011
6802
|
BackupAuthProvider,
|
|
6012
6803
|
ChatConversation,
|
|
6013
6804
|
ChatMessage,
|
|
@@ -6015,15 +6806,18 @@ function useBackup(options) {
|
|
|
6015
6806
|
DEFAULT_DRIVE_CONVERSATIONS_FOLDER,
|
|
6016
6807
|
DEFAULT_DRIVE_ROOT_FOLDER,
|
|
6017
6808
|
DEFAULT_DROPBOX_FOLDER,
|
|
6809
|
+
DEFAULT_ICLOUD_BACKUP_FOLDER,
|
|
6018
6810
|
DEFAULT_TOOL_SELECTOR_MODEL,
|
|
6019
6811
|
DropboxAuthProvider,
|
|
6020
6812
|
GoogleDriveAuthProvider,
|
|
6813
|
+
ICloudAuthProvider,
|
|
6021
6814
|
StoredMemoryModel,
|
|
6022
6815
|
StoredModelPreferenceModel,
|
|
6023
6816
|
chatStorageMigrations,
|
|
6024
6817
|
chatStorageSchema,
|
|
6025
6818
|
clearDropboxToken,
|
|
6026
6819
|
clearGoogleDriveToken,
|
|
6820
|
+
clearICloudAuth,
|
|
6027
6821
|
createMemoryContextSystemMessage,
|
|
6028
6822
|
decryptData,
|
|
6029
6823
|
decryptDataBytes,
|
|
@@ -6038,6 +6832,7 @@ function useBackup(options) {
|
|
|
6038
6832
|
hasDropboxCredentials,
|
|
6039
6833
|
hasEncryptionKey,
|
|
6040
6834
|
hasGoogleDriveCredentials,
|
|
6835
|
+
hasICloudCredentials,
|
|
6041
6836
|
memoryStorageSchema,
|
|
6042
6837
|
requestEncryptionKey,
|
|
6043
6838
|
sdkMigrations,
|
|
@@ -6054,6 +6849,8 @@ function useBackup(options) {
|
|
|
6054
6849
|
useEncryption,
|
|
6055
6850
|
useGoogleDriveAuth,
|
|
6056
6851
|
useGoogleDriveBackup,
|
|
6852
|
+
useICloudAuth,
|
|
6853
|
+
useICloudBackup,
|
|
6057
6854
|
useImageGeneration,
|
|
6058
6855
|
useMemoryStorage,
|
|
6059
6856
|
useModels,
|