diff --git a/.github/ISSUE_TEMPLATE/documentation_issue.md b/.github/ISSUE_TEMPLATE/documentation_issue.md index 68aa804..e97dc5e 100644 --- a/.github/ISSUE_TEMPLATE/documentation_issue.md +++ b/.github/ISSUE_TEMPLATE/documentation_issue.md @@ -40,7 +40,7 @@ Include replacement text or code examples if applicable. - [ ] CHANGELOG.md - [ ] API Reference / Configuration Parameters - [ ] Lifecycle Events Reference -- [ ] Example app (`test-example/`) +- [ ] Example app (`example/react-native` or `example/expo`) - [ ] Code examples - [ ] Other (describe): diff --git a/.github/ISSUE_TEMPLATE/question_support.md b/.github/ISSUE_TEMPLATE/question_support.md index c3e6bfd..0de144e 100644 --- a/.github/ISSUE_TEMPLATE/question_support.md +++ b/.github/ISSUE_TEMPLATE/question_support.md @@ -84,7 +84,7 @@ If relevant, paste your current upload configuration. - [ ] API Reference / Configuration Parameters - [ ] Lifecycle Events Reference - [ ] Troubleshooting section -- [ ] Example app (`test-example/`) +- [ ] Example app (`example/react-native` or `example/expo`) - [ ] GitHub Issues - [ ] Other (describe): diff --git a/.gitignore b/.gitignore index 8d64791..bef9b86 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,7 @@ project.xcworkspace **/.xcode.env.local # Example App -test-example/.env +example/react-native/.env # Android/IJ # @@ -55,15 +55,15 @@ android.iml # Cocoapods # -test-example/ios/Pods +example/react-native/ios/Pods # Ruby -test-example/vendor/ +example/react-native/vendor/ # node.js # node_modules/ -test-example/node_modules/ +example/react-native/node_modules/ npm-debug.log # BUCK @@ -88,12 +88,12 @@ android/keystores/debug.keystore # generated by bob , Compilation Outputs lib/ dist/ -test-example/ios/build/ +example/react-native/ios/build/ # test coverage reports (generated by jest --coverage) coverage/ -test-example/android/build/ -test-example/android/app/build/ +example/react-native/android/build/ +example/react-native/android/app/build/ # --- Diagnostic and Log Files --- npm-debug.log* @@ -106,3 +106,13 @@ android/generated # React Native Nitro Modules nitrogen/ + +# Expo example (example/expo) — native dirs are generated by prebuild (CNG) +example/expo/node_modules/ +example/expo/.expo/ +example/expo/dist/ +example/expo/.env +example/expo/ios/ +example/expo/android/ +example/expo/backend/node_modules/ +example/expo/backend/.env diff --git a/CHANGELOG.md b/CHANGELOG.md index 3417f18..2d75826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ chunked uploads for large files on Android and iOS. `on()`/`off()` subscription and `state`, `progress`, and `stateHistory` getters. - **`enableLogs`** option for SDK-internal debug logging (with URL/path masking). -- Example React Native app under `test-example/` demonstrating picking, upload, +- Example React Native app under `example/` demonstrating picking, upload, progress, pause/resume/abort, retries, and network recovery. - Jest test suite covering the core engine, chunking, network monitor, event emitter, and validation, with coverage measured in CI. diff --git a/README.md b/README.md index 444b5fe..d2abe7a 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ A signed URL is a pre-authenticated URL that allows secure, direct uploads to Fa ### Sample Code: Generating a Signed URL -Here is a self-contained service that calls the FastPix Direct Upload API and returns a signed URL. It uses `axios` and `base-64` for the Basic Auth header (the same approach used by the bundled example app in [`test-example/src/Services/ApiService.ts`](test-example/src/Services/ApiService.ts)): +Here is a self-contained service that calls the FastPix Direct Upload API and returns a signed URL. It uses `axios` and `base-64` for the Basic Auth header (the same approach used by the bundled example app in [`example/react-native/src/Services/ApiService.ts`](example/react-native/src/Services/ApiService.ts)): > This is **your** backend/service code, not part of the SDK. Install its helpers with `npm install axios base-64`. On a Node backend you can drop `base-64` and use `Buffer.from(...).toString("base64")` instead. @@ -218,7 +218,7 @@ await upload.resume(); // re-syncs the server offset, then continues ### Full React component: progress bar with pause / resume / abort -A complete, copy-paste example wiring the resumable lifecycle to UI controls. The same flow is implemented end-to-end in the bundled [`test-example/`](test-example/) app. +A complete, copy-paste example wiring the resumable lifecycle to UI controls. The same flow is implemented end-to-end in the bundled [`example/react-native/`](example/react-native/) app. ```jsx import React, { useEffect, useRef, useState } from "react"; @@ -551,7 +551,10 @@ The example demonstrates: * Network recovery * Error handling -Refer to the **`test-example/`** directory for the complete implementation. +This repo ships two runnable examples: + +* **[`example/react-native/`](example/react-native)** — full React Native CLI app (logs, chunk stats, configurable options). The complete reference implementation. +* **[`example/expo/`](example/expo)** — a minimal **Expo** app (pick a video, progress bar, pause/resume) with a small backend. Good starting point for Expo projects. ## Troubleshooting diff --git a/example/expo/.gitignore b/example/expo/.gitignore new file mode 100644 index 0000000..d914c32 --- /dev/null +++ b/example/expo/.gitignore @@ -0,0 +1,41 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +# generated native folders +/ios +/android diff --git a/example/expo/App.tsx b/example/expo/App.tsx new file mode 100644 index 0000000..9bec552 --- /dev/null +++ b/example/expo/App.tsx @@ -0,0 +1,121 @@ +import { FastPixUpload } from "@fastpix/react-native-uploads"; +import * as ImagePicker from "expo-image-picker"; +import { StatusBar } from "expo-status-bar"; +import { useRef, useState } from "react"; +import { Button, StyleSheet, Text, View } from "react-native"; + +// Your backend route that creates a FastPix signed upload URL. +// - iOS Simulator / Android emulator: http://localhost:8787 reaches your Mac. +// (Android emulator: use http://10.0.2.2:8787) +// - Physical device: use your computer's LAN IP, e.g. http://192.168.1.20:8787 +const CREATE_UPLOAD_ENDPOINT = "http://localhost:8787/uploads"; + +type Status = "idle" | "uploading" | "paused" | "success" | "error"; + +// The SDK calls this when an upload starts, so a signed URL is minted per +// upload rather than per app launch. +async function createUploadUrl(): Promise { + const response = await fetch(CREATE_UPLOAD_ENDPOINT, { method: "POST" }); + if (!response.ok) throw new Error("Could not create an upload URL"); + const { uploadUrl } = (await response.json()) as { uploadUrl: string }; + return uploadUrl; +} + +export default function App() { + const uploadRef = useRef(null); + const [status, setStatus] = useState("idle"); + const [percentage, setPercentage] = useState(0); + const [error, setError] = useState(null); + + async function pickAndUpload() { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: "videos", + }); + if (result.canceled) return; + + setError(null); + setPercentage(0); + + const upload = new FastPixUpload({ + endpoint: createUploadUrl, + fileUri: result.assets[0].uri, + chunkSize: 5 * 1024, // 5 MB, in KB (must be a multiple of 256 KB) + }); + uploadRef.current = upload; + + upload.on("progress", ({ percentage }) => setPercentage(percentage)); + upload.on("success", () => setStatus("success")); + upload.on("error", ({ message }) => { + setError(message); + setStatus("error"); + }); + + setStatus("uploading"); + await upload.start(); + } + + function togglePause() { + const upload = uploadRef.current; + if (!upload) return; + if (status === "uploading") { + upload.pause(); + setStatus("paused"); + } else if (status === "paused") { + upload.resume(); + setStatus("uploading"); + } + } + + const statusLine = + status === "success" + ? "Upload complete" + : status === "error" + ? `Failed: ${error}` + : status === "idle" + ? "Pick a video to start" + : `${percentage}%`; + + return ( + + + FastPix React Native Uploads + +