create-100x-mobile 0.6.9 → 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
  }
@@ -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 workerUrl = process.env.EXPO_PUBLIC_STORAGE_WORKER_URL;
156
- if (!workerUrl) {
157
- Alert.alert("Error", "Storage worker URL is not configured.");
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
- const downloadUrl =
161
- workerUrl + "/uploads/" + file.uploadId + "/content";
162
- await Linking.openURL(downloadUrl);
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("Error", "Could not open file.");
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
  [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-100x-mobile",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "Scaffold a full-stack mobile app with Expo + Convex + Clerk in seconds",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {