@fanapps/react-native 0.2.0 → 0.3.1

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/README.md CHANGED
@@ -7,22 +7,24 @@ React Native SDK for the Fanapps API — Firebase-powered auth, paginated conten
7
7
  ```sh
8
8
  pnpm add @fanapps/react-native \
9
9
  @tanstack/react-query \
10
- @react-native-firebase/app \
11
- @react-native-firebase/auth
10
+ @react-native-firebase/app
12
11
  ```
13
12
 
14
- `react`, `react-native`, `@tanstack/react-query`, `@react-native-firebase/app`, and `@react-native-firebase/auth` are **required** peer dependencies.
13
+ iOS:
15
14
 
16
- Optional peers (install only if you use the matching feature):
15
+ ```sh
16
+ cd ios && pod install
17
+ ```
18
+
19
+ The SDK auto-installs all required native modules (`@react-native-firebase/auth`, `/messaging`, Google sign-in, Apple sign-in, AsyncStorage) — autolinking handles native registration.
20
+
21
+ You still need Firebase's native config:
22
+ - `google-services.json` (Android) → `android/app/`
23
+ - `GoogleService-Info.plist` (iOS) → `ios/<AppName>/`
17
24
 
18
- | Feature | Package |
19
- | ----------------------------------- | ---------------------------------------------- |
20
- | Push registration + topic subscribe | `@react-native-firebase/messaging` |
21
- | Persist `targetId` across launches | `@react-native-async-storage/async-storage` |
22
- | Google sign-in | `@react-native-google-signin/google-signin` |
23
- | Apple sign-in (iOS) | `@invertase/react-native-apple-authentication` |
25
+ See the [React Native Firebase setup docs](https://rnfirebase.io/#installation).
24
26
 
25
- You also need Firebase's native config (`google-services.json` on Android, `GoogleService-Info.plist` on iOS) see the [React Native Firebase docs](https://rnfirebase.io).
27
+ If you use Google sign-in, configure OAuth client IDs per the [google-signin docs](https://github.com/react-native-google-signin/google-signin). For Apple sign-in, enable the "Sign in with Apple" capability in Xcode.
26
28
 
27
29
  ## Quick start
28
30
 
@@ -180,7 +182,7 @@ useArticles(filters?: ArticleFilters, options?) // Paginated<Article>
180
182
  useArticle(id, options?) // Article
181
183
  ```
182
184
 
183
- Filters: `is_published`, `sort`, `perPage`, `page`.
185
+ Filters: `status` (`draft` | `scheduled` | `published`), `sort`, `perPage`, `page`.
184
186
 
185
187
  ### Trivia
186
188
 
@@ -225,7 +227,7 @@ All client calls carry the same `Authorization`, `X-Tenant`, and `X-Firebase-Tok
225
227
  ## Errors
226
228
 
227
229
  - Non-2xx HTTP responses throw `FanappsError` with `{ status, body, message }`.
228
- - Auth flows throw `FanappsAuthError` with a `code` field. Common codes: `peer/load-failed` (module can't be loaded — usually a Metro symlink issue, see below), `peer/unexpected-shape` (loaded but wrong interop shape), `auth/platform-not-supported`, `auth/not-supported`, `auth/no-token`.
230
+ - Auth flows throw `FanappsAuthError` with a `code` field. Codes: `auth/platform-not-supported`, `auth/not-supported`, `auth/no-token`.
229
231
 
230
232
  ```tsx
231
233
  import { FanappsError, FanappsAuthError } from '@fanapps/react-native';
@@ -238,8 +240,8 @@ if (error instanceof FanappsError && error.status === 401) {
238
240
  try {
239
241
  await signInWithGoogle();
240
242
  } catch (e) {
241
- if (e instanceof FanappsAuthError && e.code === 'peer/load-failed') {
242
- // Google package couldn't be loaded — see "Symlinked installs" section
243
+ if (e instanceof FanappsAuthError && e.code === 'auth/platform-not-supported') {
244
+ // Apple sign-in on Android, etc.
243
245
  }
244
246
  }
245
247
  ```
@@ -256,64 +258,7 @@ interface StorageAdapter {
256
258
  }
257
259
  ```
258
260
 
259
- `@react-native-async-storage/async-storage`'s default export matches this exactly. If you want to use `expo-secure-store` or a custom backend, wrap it in an adapter.
260
-
261
- If no `storage` prop is passed and AsyncStorage isn't installed, the SDK falls back to in-memory storage — the `targetId` will be lost on app restart, and a new push target row will be created on the next `register()` call.
262
-
263
- ## Symlinked installs (local development)
264
-
265
- If you link the SDK via `pnpm link`, a filesystem symlink, or `file:` protocol, Metro will often fail to resolve peer dependencies like `@react-native-firebase/auth` from the SDK's location — even though they're installed in your app. You'll see:
266
-
267
- ```
268
- FanappsAuthError: @fanapps/react-native could not load @react-native-firebase/auth:
269
- Unable to resolve module '@react-native-firebase/auth'
270
- ```
271
-
272
- The error code on the thrown `FanappsAuthError` is `peer/load-failed`. Two fixes:
273
-
274
- ### Option A — `yalc` (recommended)
275
-
276
- `yalc` copies files instead of symlinking, so Metro has nothing special to resolve. From the SDK directory:
277
-
278
- ```sh
279
- yalc publish
280
- ```
281
-
282
- In your app:
283
-
284
- ```sh
285
- yalc add @fanapps/react-native
286
- pnpm install
287
- ```
288
-
289
- When you change the SDK: `pnpm build && yalc push` — all linked apps update immediately.
290
-
291
- ### Option B — Metro config with `extraNodeModules`
292
-
293
- If you must keep the symlink (e.g. you want live SDK changes without a push step), tell Metro to always resolve peer deps from the app's `node_modules`:
294
-
295
- ```js
296
- // metro.config.js
297
- const { getDefaultConfig } = require('@react-native/metro-config');
298
- const path = require('path');
299
-
300
- const config = getDefaultConfig(__dirname);
301
-
302
- config.resolver.unstable_enableSymlinks = true;
303
- config.resolver.extraNodeModules = new Proxy(
304
- {},
305
- { get: (_, name) => path.resolve(__dirname, 'node_modules', String(name)) },
306
- );
307
- config.watchFolders = [
308
- path.resolve(__dirname, '../path/to/fanapps/resources/sdk/react-native'),
309
- ];
310
-
311
- module.exports = config;
312
- ```
313
-
314
- Then clear Metro cache: `npx react-native start --reset-cache` (or `expo start -c`).
315
-
316
- The `extraNodeModules` Proxy forces every module lookup — including ones originating inside the symlinked SDK — to resolve against your app's `node_modules`. This avoids duplicate copies of `react` / `react-native` / `@react-native-firebase/*` too, which would otherwise cause "Invalid hook call" errors.
261
+ Defaults to `@react-native-async-storage/async-storage` (which the SDK auto-installs). To use `expo-secure-store` or a custom backend, pass it via the `storage` prop wrapped in an adapter.
317
262
 
318
263
  ## Build
319
264
 
package/dist/index.d.mts CHANGED
@@ -24,11 +24,13 @@ interface Entry {
24
24
  locale: Locale;
25
25
  available_locales?: Locale[];
26
26
  }
27
+ type ArticleStatus = 'draft' | 'scheduled' | 'published';
27
28
  interface Article {
28
29
  id: string | number;
29
30
  title: string;
30
31
  content: string;
31
- is_published: boolean;
32
+ status: ArticleStatus;
33
+ published_at: string | null;
32
34
  featured_image_url: string | null;
33
35
  created_at: string;
34
36
  updated_at: string;
@@ -99,7 +101,7 @@ interface EntryFilters extends PaginationParams {
99
101
  published?: boolean;
100
102
  }
101
103
  interface ArticleFilters extends PaginationParams {
102
- is_published?: boolean;
104
+ status?: ArticleStatus;
103
105
  }
104
106
  interface HookOptions {
105
107
  locale?: Locale;
package/dist/index.d.ts CHANGED
@@ -24,11 +24,13 @@ interface Entry {
24
24
  locale: Locale;
25
25
  available_locales?: Locale[];
26
26
  }
27
+ type ArticleStatus = 'draft' | 'scheduled' | 'published';
27
28
  interface Article {
28
29
  id: string | number;
29
30
  title: string;
30
31
  content: string;
31
- is_published: boolean;
32
+ status: ArticleStatus;
33
+ published_at: string | null;
32
34
  featured_image_url: string | null;
33
35
  created_at: string;
34
36
  updated_at: string;
@@ -99,7 +101,7 @@ interface EntryFilters extends PaginationParams {
99
101
  published?: boolean;
100
102
  }
101
103
  interface ArticleFilters extends PaginationParams {
102
- is_published?: boolean;
104
+ status?: ArticleStatus;
103
105
  }
104
106
  interface HookOptions {
105
107
  locale?: Locale;
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -42,67 +52,13 @@ module.exports = __toCommonJS(index_exports);
42
52
  var import_react_query = require("@tanstack/react-query");
43
53
  var import_react2 = require("react");
44
54
 
45
- // src/auth/errors.ts
46
- var FanappsAuthError = class extends Error {
47
- constructor(message, code) {
48
- super(message);
49
- this.name = "FanappsAuthError";
50
- this.code = code;
51
- }
52
- };
53
- function peerLoadFailed(pkg, cause) {
54
- const reason = cause instanceof Error ? cause.message : String(cause);
55
- return new FanappsAuthError(
56
- `@fanapps/react-native could not load ${pkg}: ${reason}. If the package is installed, Metro may be failing to resolve it from the SDK's location. See the README "Symlinked installs" section for a metro.config.js fix.`,
57
- "peer/load-failed"
58
- );
59
- }
60
- function peerUnexpectedShape(pkg, details) {
61
- return new FanappsAuthError(
62
- `@fanapps/react-native loaded ${pkg} but its shape was unexpected: ${details}. This usually indicates a bundler/interop mismatch or a duplicate copy of the package.`,
63
- "peer/unexpected-shape"
64
- );
65
- }
66
-
67
55
  // src/auth/firebaseApp.ts
68
- var cachedAuth = null;
56
+ var import_auth = __toESM(require("@react-native-firebase/auth"));
57
+ var cached = null;
69
58
  function firebaseAuth() {
70
- if (cachedAuth) {
71
- return cachedAuth;
72
- }
73
- let mod;
74
- try {
75
- mod = require("@react-native-firebase/auth");
76
- } catch (e) {
77
- throw peerLoadFailed("@react-native-firebase/auth", e);
78
- }
79
- const factory = resolveFactory(mod);
80
- if (!factory) {
81
- throw peerUnexpectedShape(
82
- "@react-native-firebase/auth",
83
- `expected a factory function, got ${describeShape(mod)}`
84
- );
85
- }
86
- cachedAuth = factory();
87
- return cachedAuth;
88
- }
89
- function resolveFactory(mod) {
90
- if (typeof mod === "function") {
91
- return mod;
92
- }
93
- if (mod && typeof mod === "object" && "default" in mod) {
94
- const def = mod.default;
95
- if (typeof def === "function") {
96
- return def;
97
- }
98
- }
99
- return null;
100
- }
101
- function describeShape(mod) {
102
- if (mod === null) return "null";
103
- if (typeof mod !== "object") return typeof mod;
104
- const keys = Object.keys(mod).slice(0, 5);
105
- return `object with keys [${keys.join(", ")}]`;
59
+ if (cached) return cached;
60
+ cached = (0, import_auth.default)();
61
+ return cached;
106
62
  }
107
63
 
108
64
  // src/auth/authStore.ts
@@ -207,7 +163,9 @@ var FanappsClient = class {
207
163
  return response.data;
208
164
  }
209
165
  async deletePushTarget(targetId) {
210
- await this.delete(`/api/account/targets/push/${encodeURIComponent(targetId)}`);
166
+ await this.delete(
167
+ `/api/account/targets/push/${encodeURIComponent(targetId)}`
168
+ );
211
169
  }
212
170
  async request(method, path, opts) {
213
171
  const url = this.buildUrl(path, opts.params);
@@ -250,11 +208,15 @@ function serializeParams(params) {
250
208
  if (Array.isArray(value)) {
251
209
  for (const item of value) {
252
210
  if (item === void 0 || item === null) continue;
253
- parts.push(`${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`);
211
+ parts.push(
212
+ `${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`
213
+ );
254
214
  }
255
215
  continue;
256
216
  }
257
- parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
217
+ parts.push(
218
+ `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
219
+ );
258
220
  }
259
221
  return parts.join("&");
260
222
  }
@@ -280,35 +242,9 @@ function useFanapps() {
280
242
  }
281
243
 
282
244
  // src/push/storage.ts
283
- var memoryFallbackWarned = false;
245
+ var import_async_storage = __toESM(require("@react-native-async-storage/async-storage"));
284
246
  function resolveStorage(explicit) {
285
- if (explicit) return explicit;
286
- try {
287
- const mod = require("@react-native-async-storage/async-storage");
288
- return mod.default;
289
- } catch {
290
- if (!memoryFallbackWarned && typeof __DEV__ !== "undefined" && __DEV__) {
291
- console.warn(
292
- "[@fanapps/react-native] No storage adapter provided and @react-native-async-storage/async-storage not installed. Push target IDs will be lost on app restart."
293
- );
294
- memoryFallbackWarned = true;
295
- }
296
- return createMemoryStorage();
297
- }
298
- }
299
- function createMemoryStorage() {
300
- const store = /* @__PURE__ */ new Map();
301
- return {
302
- async getItem(key) {
303
- return store.get(key) ?? null;
304
- },
305
- async setItem(key, value) {
306
- store.set(key, value);
307
- },
308
- async removeItem(key) {
309
- store.delete(key);
310
- }
311
- };
247
+ return explicit ?? import_async_storage.default;
312
248
  }
313
249
 
314
250
  // src/provider.tsx
@@ -353,6 +289,15 @@ function FanappsProvider({
353
289
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_query.QueryClientProvider, { client: resolvedQueryClient, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FanappsContext.Provider, { value, children }) });
354
290
  }
355
291
 
292
+ // src/auth/errors.ts
293
+ var FanappsAuthError = class extends Error {
294
+ constructor(message, code) {
295
+ super(message);
296
+ this.name = "FanappsAuthError";
297
+ this.code = code;
298
+ }
299
+ };
300
+
356
301
  // src/hooks/useFanappsClient.ts
357
302
  function useFanappsClient() {
358
303
  return useFanapps().client;
@@ -362,32 +307,22 @@ function useFanappsClient() {
362
307
  var import_react3 = require("react");
363
308
 
364
309
  // src/auth/providers/google.ts
310
+ var import_auth2 = __toESM(require("@react-native-firebase/auth"));
311
+ var import_google_signin = require("@react-native-google-signin/google-signin");
365
312
  async function signInWithGoogle() {
366
- let mod;
367
- try {
368
- mod = require("@react-native-google-signin/google-signin");
369
- } catch (e) {
370
- throw peerLoadFailed("@react-native-google-signin/google-signin", e);
371
- }
372
- let authMod;
373
- try {
374
- authMod = require("@react-native-firebase/auth");
375
- } catch (e) {
376
- throw peerLoadFailed("@react-native-firebase/auth", e);
377
- }
378
- await mod.GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
379
- const result = await mod.GoogleSignin.signIn();
313
+ await import_google_signin.GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
314
+ const result = await import_google_signin.GoogleSignin.signIn();
380
315
  const idToken = "data" in result ? result.data?.idToken ?? null : result.idToken;
381
316
  if (!idToken) {
382
317
  throw new Error("Google sign-in returned no ID token.");
383
318
  }
384
- const credential = authMod.GoogleAuthProvider.credential(idToken);
385
- return firebaseAuth().signInWithCredential(
386
- credential
387
- );
319
+ const credential = import_auth2.default.GoogleAuthProvider.credential(idToken);
320
+ return firebaseAuth().signInWithCredential(credential);
388
321
  }
389
322
 
390
323
  // src/auth/providers/apple.ts
324
+ var import_auth3 = __toESM(require("@react-native-firebase/auth"));
325
+ var import_react_native_apple_authentication = require("@invertase/react-native-apple-authentication");
391
326
  var import_react_native = require("react-native");
392
327
  async function signInWithApple() {
393
328
  if (import_react_native.Platform.OS !== "ios") {
@@ -396,35 +331,27 @@ async function signInWithApple() {
396
331
  "auth/platform-not-supported"
397
332
  );
398
333
  }
399
- let mod;
400
- try {
401
- mod = require("@invertase/react-native-apple-authentication");
402
- } catch (e) {
403
- throw peerLoadFailed("@invertase/react-native-apple-authentication", e);
404
- }
405
- let authMod;
406
- try {
407
- authMod = require("@react-native-firebase/auth");
408
- } catch (e) {
409
- throw peerLoadFailed("@react-native-firebase/auth", e);
410
- }
411
- if (!mod.appleAuth.isSupported) {
412
- throw new FanappsAuthError("Apple sign-in not supported on this device.", "auth/not-supported");
334
+ if (!import_react_native_apple_authentication.appleAuth.isSupported) {
335
+ throw new FanappsAuthError(
336
+ "Apple sign-in not supported on this device.",
337
+ "auth/not-supported"
338
+ );
413
339
  }
414
- const result = await mod.appleAuth.performRequest({
415
- requestedOperation: mod.appleAuth.Operation.LOGIN,
416
- requestedScopes: [mod.appleAuth.Scope.EMAIL, mod.appleAuth.Scope.FULL_NAME]
340
+ const result = await import_react_native_apple_authentication.appleAuth.performRequest({
341
+ requestedOperation: import_react_native_apple_authentication.appleAuth.Operation.LOGIN,
342
+ requestedScopes: [import_react_native_apple_authentication.appleAuth.Scope.EMAIL, import_react_native_apple_authentication.appleAuth.Scope.FULL_NAME]
417
343
  });
418
344
  if (!result.identityToken) {
419
- throw new FanappsAuthError("Apple did not return an identity token.", "auth/no-token");
345
+ throw new FanappsAuthError(
346
+ "Apple did not return an identity token.",
347
+ "auth/no-token"
348
+ );
420
349
  }
421
- const credential = authMod.AppleAuthProvider.credential(
350
+ const credential = import_auth3.default.AppleAuthProvider.credential(
422
351
  result.identityToken,
423
352
  result.nonce ?? void 0
424
353
  );
425
- return firebaseAuth().signInWithCredential(
426
- credential
427
- );
354
+ return firebaseAuth().signInWithCredential(credential);
428
355
  }
429
356
 
430
357
  // src/hooks/useAuth.ts
@@ -553,42 +480,12 @@ var import_react4 = require("react");
553
480
  var import_react_native2 = require("react-native");
554
481
 
555
482
  // src/push/messaging.ts
556
- var cached = null;
483
+ var import_messaging = __toESM(require("@react-native-firebase/messaging"));
484
+ var cached2 = null;
557
485
  function firebaseMessaging() {
558
- if (cached) return cached;
559
- let mod;
560
- try {
561
- mod = require("@react-native-firebase/messaging");
562
- } catch (e) {
563
- throw peerLoadFailed("@react-native-firebase/messaging", e);
564
- }
565
- const factory = resolveFactory2(mod);
566
- if (!factory) {
567
- throw peerUnexpectedShape(
568
- "@react-native-firebase/messaging",
569
- `expected a factory function, got ${describeShape2(mod)}`
570
- );
571
- }
572
- cached = factory();
573
- return cached;
574
- }
575
- function resolveFactory2(mod) {
576
- if (typeof mod === "function") {
577
- return mod;
578
- }
579
- if (mod && typeof mod === "object" && "default" in mod) {
580
- const def = mod.default;
581
- if (typeof def === "function") {
582
- return def;
583
- }
584
- }
585
- return null;
586
- }
587
- function describeShape2(mod) {
588
- if (mod === null) return "null";
589
- if (typeof mod !== "object") return typeof mod;
590
- const keys = Object.keys(mod).slice(0, 5);
591
- return `object with keys [${keys.join(", ")}]`;
486
+ if (cached2) return cached2;
487
+ cached2 = (0, import_messaging.default)();
488
+ return cached2;
592
489
  }
593
490
 
594
491
  // src/push/targetIdStore.ts