Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/documentation_issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/question_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
24 changes: 17 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ project.xcworkspace
**/.xcode.env.local

# Example App
test-example/.env
example/react-native/.env

# Android/IJ
#
Expand All @@ -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
Expand All @@ -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*
Expand All @@ -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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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

Expand Down
41 changes: 41 additions & 0 deletions example/expo/.gitignore
Original file line number Diff line number Diff line change
@@ -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
121 changes: 121 additions & 0 deletions example/expo/App.tsx
Original file line number Diff line number Diff line change
@@ -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<string> {
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<FastPixUpload | null>(null);
const [status, setStatus] = useState<Status>("idle");
const [percentage, setPercentage] = useState(0);
const [error, setError] = useState<string | null>(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 (
<View style={styles.container}>
<StatusBar style="auto" />
<Text style={styles.title}>FastPix React Native Uploads</Text>

<Button title="Pick a video to upload" onPress={pickAndUpload} />

{status !== "idle" && (
<View style={styles.progressBlock}>
<Text style={styles.status}>{statusLine}</Text>
<View style={styles.track}>
<View style={[styles.fill, { width: `${percentage}%` }]} />
</View>
{(status === "uploading" || status === "paused") && (
<Button
title={status === "paused" ? "Resume" : "Pause"}
onPress={togglePause}
/>
)}
</View>
)}
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 24,
gap: 20,
backgroundColor: "#fff",
},
title: { fontSize: 18, fontWeight: "600", textAlign: "center" },
progressBlock: { gap: 10 },
status: { fontSize: 16, textAlign: "center" },
track: {
height: 8,
borderRadius: 4,
backgroundColor: "#e0e0e0",
overflow: "hidden",
},
fill: { height: "100%", backgroundColor: "#0a84ff" },
});
Loading