@uploadista/react-native-bare 0.0.20-beta.7 → 0.0.20-beta.9

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/package.json CHANGED
@@ -2,30 +2,45 @@
2
2
  "name": "@uploadista/react-native-bare",
3
3
  "type": "module",
4
4
  "description": "Bare React Native Client for Uploadista",
5
- "version": "0.0.20-beta.7",
5
+ "version": "0.0.20-beta.9",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
8
8
  "exports": {
9
9
  ".": "./src/index.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@uploadista/client-core": "0.0.20-beta.7",
13
- "@uploadista/react-native-core": "0.0.20-beta.7"
12
+ "@uploadista/client-core": "0.0.20-beta.9",
13
+ "@uploadista/react-native-core": "0.0.20-beta.9"
14
14
  },
15
15
  "peerDependencies": {
16
+ "js-base64": ">=3.7.0",
17
+ "uuid": ">=13.0.0",
16
18
  "react": ">=16.8.0",
17
19
  "react-native": ">=0.71.0",
18
20
  "@react-native-documents/picker": ">=10.0.0",
19
21
  "react-native-image-picker": ">=7.0.0",
20
- "rn-fetch-blob": ">=0.12.0"
22
+ "react-native-quick-crypto": ">=0.7.0",
23
+ "rn-fetch-blob": ">=0.12.0",
24
+ "@react-native-async-storage/async-storage": ">=1.19.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "@react-native-documents/picker": {
28
+ "optional": true
29
+ },
30
+ "react-native-image-picker": {
31
+ "optional": true
32
+ },
33
+ "@react-native-async-storage/async-storage": {
34
+ "optional": true
35
+ }
21
36
  },
22
37
  "devDependencies": {
23
38
  "@types/react": ">=18.0.0",
24
- "tsdown": "0.17.2",
25
- "@uploadista/typescript-config": "0.0.20-beta.7"
39
+ "tsdown": "0.18.0",
40
+ "@uploadista/typescript-config": "0.0.20-beta.9"
26
41
  },
27
42
  "scripts": {
28
- "build": "tsdown",
43
+ "build": "tsc --noEmit && tsdown",
29
44
  "format": "biome format --write ./src",
30
45
  "lint": "biome lint --write ./src",
31
46
  "check": "biome check --write ./src"
@@ -0,0 +1,14 @@
1
+ import type { ChecksumService } from "@uploadista/client-core";
2
+ import { computeUint8ArraySha256 } from "../utils/hash-util";
3
+
4
+ /**
5
+ * Creates a ChecksumService for Expo environments
6
+ * Computes SHA-256 checksums of file data using Web Crypto API
7
+ */
8
+ export function createReactNativeChecksumService(): ChecksumService {
9
+ return {
10
+ computeChecksum: async (data: Uint8Array<ArrayBuffer>) => {
11
+ return computeUint8ArraySha256(data);
12
+ },
13
+ };
14
+ }
@@ -3,12 +3,15 @@ import {
3
3
  createInMemoryStorageService,
4
4
  type ServiceContainer,
5
5
  } from "@uploadista/client-core";
6
- import type { ReactNativeUploadInput } from "@/types/upload-input";
6
+ import type { ReactNativeUploadInput } from "@uploadista/react-native-core";
7
7
  import { createReactNativeAbortControllerFactory } from "./abort-controller-factory";
8
8
  import { createReactNativeBase64Service } from "./base64-service";
9
+ import { createReactNativeChecksumService } from "./checksum-service";
9
10
  import { createReactNativeFileReaderService } from "./file-reader-service";
11
+ import { createReactNativeFingerprintService } from "./fingerprint-service";
10
12
  import { createReactNativeHttpClient } from "./http-client";
11
13
  import { createReactNativeIdGenerationService } from "./id-generation-service";
14
+ import { createReactNativePlatformService } from "./platform-service";
12
15
  import { createAsyncStorageService } from "./storage-service";
13
16
  import { createReactNativeWebSocketFactory } from "./websocket-factory";
14
17
 
@@ -63,6 +66,9 @@ export function createReactNativeServices(
63
66
  const base64 = createReactNativeBase64Service();
64
67
  const websocket = createReactNativeWebSocketFactory();
65
68
  const abortController = createReactNativeAbortControllerFactory();
69
+ const platform = createReactNativePlatformService();
70
+ const checksumService = createReactNativeChecksumService();
71
+ const fingerprintService = createReactNativeFingerprintService();
66
72
 
67
73
  return {
68
74
  storage,
@@ -70,6 +76,9 @@ export function createReactNativeServices(
70
76
  httpClient,
71
77
  fileReader,
72
78
  base64,
79
+ platform,
80
+ checksumService,
81
+ fingerprintService,
73
82
  websocket,
74
83
  abortController,
75
84
  };
@@ -3,7 +3,7 @@ import type {
3
3
  FileSource,
4
4
  SliceResult,
5
5
  } from "@uploadista/client-core";
6
- import type { ReactNativeUploadInput } from "@/types/upload-input";
6
+ import type { ReactNativeUploadInput } from "@uploadista/react-native-core";
7
7
 
8
8
  /**
9
9
  * React Native-specific implementation of FileReaderService
@@ -0,0 +1,114 @@
1
+ import type { FingerprintService } from "@uploadista/client-core";
2
+ import type { ReactNativeUploadInput } from "@uploadista/react-native-core";
3
+ import { createHash } from "react-native-quick-crypto";
4
+ import { computeblobSha256 } from "../utils/hash-util";
5
+
6
+ /**
7
+ * Creates a FingerprintService for bare React Native environments
8
+ * Computes file fingerprints using SHA-256 hashing
9
+ * Supports Blob, File, and URI-based inputs
10
+ */
11
+ export function createReactNativeFingerprintService(): FingerprintService<ReactNativeUploadInput> {
12
+ return {
13
+ computeFingerprint: async (input, _endpoint) => {
14
+ // Handle Blob/File objects directly
15
+ if (input instanceof Blob) {
16
+ return computeblobSha256(input);
17
+ }
18
+
19
+ // For URI inputs (string or {uri: string}), we need to convert to Blob first
20
+ if (
21
+ typeof input === "string" ||
22
+ (input && typeof input === "object" && "uri" in input)
23
+ ) {
24
+ const uri =
25
+ typeof input === "string" ? input : (input as { uri: string }).uri;
26
+ return computeFingerprintFromUri(uri);
27
+ }
28
+
29
+ throw new Error(
30
+ "Unsupported file input type for fingerprinting. Expected Blob, File, URI string, or {uri: string}",
31
+ );
32
+ },
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Compute fingerprint from a file URI
38
+ * Uses rn-fetch-blob to read the file and compute its SHA-256 hash
39
+ */
40
+ async function computeFingerprintFromUri(uri: string): Promise<string> {
41
+ try {
42
+ // Use rn-fetch-blob to read the file as base64
43
+ const RNFetchBlob = getRNFetchBlob();
44
+
45
+ // Normalize URI path for rn-fetch-blob
46
+ const normalizedPath = normalizeUri(uri);
47
+
48
+ // Check if file exists
49
+ const exists = await RNFetchBlob.fs.exists(normalizedPath);
50
+ if (!exists) {
51
+ throw new Error(`File does not exist at URI: ${uri}`);
52
+ }
53
+
54
+ // Read the entire file as base64
55
+ const base64String = await RNFetchBlob.fs.readFile(normalizedPath, "base64");
56
+
57
+ // Convert base64 to Uint8Array
58
+ const uint8Array = base64ToUint8Array(base64String);
59
+
60
+ // Compute SHA-256 hash using react-native-quick-crypto
61
+ const hash = createHash("sha256");
62
+ hash.update(uint8Array);
63
+ return hash.digest("hex");
64
+ } catch (error) {
65
+ throw new Error(
66
+ `Failed to compute fingerprint from URI ${uri}: ${error instanceof Error ? error.message : "Unknown error"}`,
67
+ );
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Normalize URI for rn-fetch-blob
73
+ * Strips file:// prefix and handles platform-specific paths
74
+ */
75
+ function normalizeUri(uri: string): string {
76
+ // Remove file:// prefix if present
77
+ if (uri.startsWith("file://")) {
78
+ return uri.substring(7);
79
+ }
80
+ return uri;
81
+ }
82
+
83
+ /**
84
+ * Get rn-fetch-blob module
85
+ * This allows the service to work even if rn-fetch-blob is not installed
86
+ */
87
+ function getRNFetchBlob() {
88
+ try {
89
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
90
+ return require("rn-fetch-blob").default;
91
+ } catch (_error) {
92
+ throw new Error(
93
+ "rn-fetch-blob is required for URI-based fingerprinting. " +
94
+ "Please install it with: npm install rn-fetch-blob",
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Convert base64 string to Uint8Array
101
+ * Uses js-base64 library for cross-platform compatibility
102
+ */
103
+ function base64ToUint8Array(base64: string): Uint8Array {
104
+ // Use js-base64 for decoding (works in all environments)
105
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
106
+ const { fromBase64 } = require("js-base64");
107
+ const binaryString = fromBase64(base64);
108
+
109
+ const bytes = new Uint8Array(binaryString.length);
110
+ for (let i = 0; i < binaryString.length; i++) {
111
+ bytes[i] = binaryString.charCodeAt(i);
112
+ }
113
+ return bytes;
114
+ }
@@ -1,17 +1,35 @@
1
- import AsyncStorage from "@react-native-async-storage/async-storage";
2
1
  import type { StorageService } from "@uploadista/client-core";
2
+
3
+ /**
4
+ * Get AsyncStorage module dynamically
5
+ * This allows the service to work even if AsyncStorage is not installed
6
+ */
7
+ function getAsyncStorage() {
8
+ try {
9
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
+ return require("@react-native-async-storage/async-storage").default;
11
+ } catch (_error) {
12
+ throw new Error(
13
+ "@react-native-async-storage/async-storage is required for persistent storage. " +
14
+ "Please install it with: npm install @react-native-async-storage/async-storage",
15
+ );
16
+ }
17
+ }
18
+
3
19
  /**
4
- * Expo-specific implementation of StorageService using AsyncStorage
20
+ * React Native-specific implementation of StorageService using AsyncStorage
5
21
  * AsyncStorage is provided as an optional peer dependency and must be installed separately
6
22
  */
7
23
  export function createAsyncStorageService(): StorageService {
24
+ const AsyncStorage = getAsyncStorage();
25
+
8
26
  const findEntries = async (
9
27
  prefix: string,
10
28
  ): Promise<Record<string, string>> => {
11
29
  const results: Record<string, string> = {};
12
30
 
13
31
  const keys = await AsyncStorage.getAllKeys();
14
- for (const key in keys) {
32
+ for (const key of keys) {
15
33
  if (key.startsWith(prefix)) {
16
34
  const item = await AsyncStorage.getItem(key);
17
35
  if (item) {
@@ -10,35 +10,42 @@ class ReactNativeWebSocket implements WebSocketLike {
10
10
  readonly CLOSING = 2;
11
11
  readonly CLOSED = 3;
12
12
 
13
- readonly readyState: number;
14
- onopen: ((event: unknown) => void) | null = null;
15
- onclose: ((event: unknown) => void) | null = null;
16
- onerror: ((event: unknown) => void) | null = null;
17
- onmessage: ((event: unknown) => void) | null = null;
13
+ onopen: (() => void) | null = null;
14
+ onclose: ((event: { code: number; reason: string }) => void) | null = null;
15
+ onerror: ((event: { message: string }) => void) | null = null;
16
+ onmessage: ((event: { data: string }) => void) | null = null;
18
17
 
19
18
  private native: WebSocket;
20
19
 
20
+ get readyState(): number {
21
+ return this.native.readyState;
22
+ }
23
+
21
24
  constructor(url: string) {
22
25
  this.native = new WebSocket(url);
23
- this.readyState = this.native.readyState;
24
26
 
25
27
  // Proxy event handlers
26
- this.native.onopen = (event) => {
27
- this.readyState = this.native.readyState;
28
- this.onopen?.(event);
28
+ this.native.onopen = () => {
29
+ this.onopen?.();
29
30
  };
30
31
 
31
32
  this.native.onclose = (event) => {
32
- this.readyState = this.native.readyState;
33
- this.onclose?.(event);
33
+ this.onclose?.({
34
+ code: (event as CloseEvent).code ?? 1000,
35
+ reason: (event as CloseEvent).reason ?? "",
36
+ });
34
37
  };
35
38
 
36
39
  this.native.onerror = (event) => {
37
- this.onerror?.(event);
40
+ this.onerror?.({
41
+ message: (event as ErrorEvent).message ?? "WebSocket error",
42
+ });
38
43
  };
39
44
 
40
45
  this.native.onmessage = (event) => {
41
- this.onmessage?.(event);
46
+ this.onmessage?.({
47
+ data: (event as MessageEvent).data as string,
48
+ });
42
49
  };
43
50
  }
44
51
 
@@ -0,0 +1,60 @@
1
+ import { createHash } from "react-native-quick-crypto";
2
+
3
+ /**
4
+ * Compute SHA-256 checksum using Node.js crypto API
5
+ * Compatible with bare React Native environments via react-native-quick-crypto polyfill
6
+ *
7
+ * @param data - Uint8Array to hash
8
+ * @returns Promise that resolves to hex-encoded SHA-256 checksum
9
+ */
10
+ export async function computeUint8ArraySha256(
11
+ data: Uint8Array,
12
+ ): Promise<string> {
13
+ try {
14
+ const hash = createHash("sha256");
15
+ hash.update(data);
16
+ return hash.digest("hex");
17
+ } catch (error) {
18
+ throw new Error(
19
+ `Failed to compute checksum: ${error instanceof Error ? error.message : "Unknown error"}`,
20
+ );
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Compute SHA-256 checksum of a Blob using Node.js crypto API
26
+ * Compatible with bare React Native environments via react-native-quick-crypto polyfill
27
+ *
28
+ * @param blob - Blob to hash
29
+ * @returns Promise that resolves to hex-encoded SHA-256 checksum
30
+ */
31
+ export async function computeblobSha256(blob: Blob): Promise<string> {
32
+ try {
33
+ // Convert Blob to Uint8Array using FileReader for compatibility
34
+ const uint8Array = await blobToUint8Array(blob);
35
+ return computeUint8ArraySha256(uint8Array);
36
+ } catch (error) {
37
+ throw new Error(
38
+ `Failed to compute file checksum: ${error instanceof Error ? error.message : "Unknown error"}`,
39
+ );
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Convert Blob to Uint8Array using FileReader
45
+ * Works in React Native environments
46
+ */
47
+ async function blobToUint8Array(blob: Blob): Promise<Uint8Array> {
48
+ return new Promise((resolve, reject) => {
49
+ const reader = new FileReader();
50
+ reader.onload = () => {
51
+ if (reader.result instanceof ArrayBuffer) {
52
+ resolve(new Uint8Array(reader.result));
53
+ } else {
54
+ reject(new Error("FileReader result is not an ArrayBuffer"));
55
+ }
56
+ };
57
+ reader.onerror = () => reject(reader.error);
58
+ reader.readAsArrayBuffer(blob);
59
+ });
60
+ }