create-100x-mobile 0.6.8 → 0.6.10
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.
|
@@ -251,7 +251,7 @@ function buildExpoInstallPackages(backend, instantAuthMode = "clerk") {
|
|
|
251
251
|
"@react-native-community/netinfo",
|
|
252
252
|
];
|
|
253
253
|
const cloudflareExpoPackages = (0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode)
|
|
254
|
-
? ["expo-document-picker"]
|
|
254
|
+
? ["expo-document-picker", "expo-file-system", "expo-sharing"]
|
|
255
255
|
: [];
|
|
256
256
|
return [...commonExpoPackages, ...backendExpoPackages, ...cloudflareExpoPackages];
|
|
257
257
|
}
|
package/dist/commands/new.js
CHANGED
|
@@ -130,7 +130,7 @@ function buildTemplateFiles(projectName, expoVersion, backend, instantAuthMode =
|
|
|
130
130
|
: [["lib/instant.ts", (0, instantLib_1.instantLibTemplate)()]]),
|
|
131
131
|
// Hooks
|
|
132
132
|
["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
|
|
133
|
-
["instant.schema.ts", (0, instantSchema_1.instantSchemaTemplate)()],
|
|
133
|
+
["instant.schema.ts", (0, instantSchema_1.instantSchemaTemplate)((0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode))],
|
|
134
134
|
...cloudflareFiles,
|
|
135
135
|
]
|
|
136
136
|
: [
|
|
@@ -121,5 +121,35 @@ export async function deleteCloudflareObject(
|
|
|
121
121
|
throw new Error(await readErrorResponse(response));
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
export async function downloadCloudflareObject(
|
|
126
|
+
uploadId: string,
|
|
127
|
+
): Promise<{ blob: Blob; contentType: string; fileName: string }> {
|
|
128
|
+
const workerUrl = requireStorageWorkerUrl();
|
|
129
|
+
const token = await requireInstantRefreshToken();
|
|
130
|
+
const response = await fetch(
|
|
131
|
+
\`\${workerUrl}/uploads/\${uploadId}/content\`,
|
|
132
|
+
{
|
|
133
|
+
headers: {
|
|
134
|
+
authorization: \`Bearer \${token}\`,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error(await readErrorResponse(response));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const blob = await response.blob();
|
|
144
|
+
const contentType =
|
|
145
|
+
response.headers.get("content-type") || "application/octet-stream";
|
|
146
|
+
const disposition = response.headers.get("content-disposition") || "";
|
|
147
|
+
const fileNameMatch = disposition.match(/filename\*=UTF-8''(.+)/);
|
|
148
|
+
const fileName = fileNameMatch
|
|
149
|
+
? decodeURIComponent(fileNameMatch[1])
|
|
150
|
+
: "download";
|
|
151
|
+
|
|
152
|
+
return { blob, contentType, fileName };
|
|
153
|
+
}
|
|
124
154
|
`;
|
|
125
155
|
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
ActivityIndicator,
|
|
8
8
|
Alert,
|
|
9
9
|
FlatList,
|
|
10
|
-
Linking,
|
|
11
10
|
Platform,
|
|
12
11
|
Pressable,
|
|
13
12
|
StatusBar,
|
|
@@ -28,6 +27,8 @@ import {
|
|
|
28
27
|
Upload,
|
|
29
28
|
} from "lucide-react-native";
|
|
30
29
|
import * as DocumentPicker from "expo-document-picker";
|
|
30
|
+
import * as FileSystem from "expo-file-system";
|
|
31
|
+
import * as Sharing from "expo-sharing";
|
|
31
32
|
import {
|
|
32
33
|
instantDb,
|
|
33
34
|
type InstantCloudflareObject,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
isCloudflareStorageConfigured,
|
|
37
38
|
uploadCloudflareObject,
|
|
38
39
|
deleteCloudflareObject,
|
|
40
|
+
downloadCloudflareObject,
|
|
39
41
|
} from "@/lib/cloudflareStorage";
|
|
40
42
|
|
|
41
43
|
function formatBytes(bytes: number): string {
|
|
@@ -152,17 +154,45 @@ function FilesContent({
|
|
|
152
154
|
const handleDownload = useCallback(
|
|
153
155
|
async (file: InstantCloudflareObject) => {
|
|
154
156
|
try {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
const { blob, fileName } = await downloadCloudflareObject(
|
|
158
|
+
file.uploadId,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (Platform.OS === "web") {
|
|
162
|
+
const url = URL.createObjectURL(blob);
|
|
163
|
+
const anchor = document.createElement("a");
|
|
164
|
+
anchor.href = url;
|
|
165
|
+
anchor.download = fileName;
|
|
166
|
+
anchor.click();
|
|
167
|
+
URL.revokeObjectURL(url);
|
|
158
168
|
return;
|
|
159
169
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
|
|
171
|
+
// Native: write blob to cache, then share
|
|
172
|
+
const reader = new FileReader();
|
|
173
|
+
reader.onloadend = async () => {
|
|
174
|
+
try {
|
|
175
|
+
const base64 = (reader.result as string).split(",")[1];
|
|
176
|
+
const fileUri =
|
|
177
|
+
FileSystem.cacheDirectory + fileName;
|
|
178
|
+
await FileSystem.writeAsStringAsync(fileUri, base64, {
|
|
179
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
180
|
+
});
|
|
181
|
+
await Sharing.shareAsync(fileUri);
|
|
182
|
+
} catch (writeErr) {
|
|
183
|
+
console.error("Save failed:", writeErr);
|
|
184
|
+
Alert.alert("Error", "Could not save file.");
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
reader.readAsDataURL(blob);
|
|
163
188
|
} catch (downloadError) {
|
|
164
189
|
console.error("Download failed:", downloadError);
|
|
165
|
-
Alert.alert(
|
|
190
|
+
Alert.alert(
|
|
191
|
+
"Download Failed",
|
|
192
|
+
downloadError instanceof Error
|
|
193
|
+
? downloadError.message
|
|
194
|
+
: "Could not download file.",
|
|
195
|
+
);
|
|
166
196
|
}
|
|
167
197
|
},
|
|
168
198
|
[],
|
|
@@ -5,9 +5,36 @@ exports.instantSchemaTemplate = instantSchemaTemplate;
|
|
|
5
5
|
* Generates an `instant.schema.ts` file in the project root.
|
|
6
6
|
* The instant-cli requires this file to exist in the root for
|
|
7
7
|
* `npx instant-cli push` to register entities on the backend.
|
|
8
|
+
*
|
|
9
|
+
* This file MUST be self-contained — instant-cli evaluates it in
|
|
10
|
+
* Node.js, so it cannot import from lib/instant.ts (which pulls
|
|
11
|
+
* in react-native-get-random-values and other native modules).
|
|
8
12
|
*/
|
|
9
|
-
function instantSchemaTemplate() {
|
|
10
|
-
|
|
13
|
+
function instantSchemaTemplate(includeCloudflare = false) {
|
|
14
|
+
const cloudflareEntity = includeCloudflare
|
|
15
|
+
? `
|
|
16
|
+
cloudflareObjects: i.entity({
|
|
17
|
+
uploadId: i.string().indexed(),
|
|
18
|
+
key: i.string(),
|
|
19
|
+
ownerId: i.string().indexed(),
|
|
20
|
+
fileName: i.string(),
|
|
21
|
+
contentType: i.string(),
|
|
22
|
+
size: i.number(),
|
|
23
|
+
workerUrl: i.string(),
|
|
24
|
+
createdAt: i.number().indexed(),
|
|
25
|
+
}),`
|
|
26
|
+
: "";
|
|
27
|
+
return `import { i } from "@instantdb/react-native";
|
|
28
|
+
|
|
29
|
+
const schema = i.schema({
|
|
30
|
+
entities: {
|
|
31
|
+
todos: i.entity({
|
|
32
|
+
text: i.string(),
|
|
33
|
+
completed: i.boolean(),
|
|
34
|
+
createdAt: i.number().indexed(),
|
|
35
|
+
}),${cloudflareEntity}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
11
38
|
|
|
12
39
|
export default schema;
|
|
13
40
|
`;
|