@fanapps/react-native 0.2.0
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 +324 -0
- package/dist/index.d.mts +238 -0
- package/dist/index.d.ts +238 -0
- package/dist/index.js +804 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +770 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# @fanapps/react-native
|
|
2
|
+
|
|
3
|
+
React Native SDK for the Fanapps API — Firebase-powered auth, paginated content hooks, and FCM push-target registration.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @fanapps/react-native \
|
|
9
|
+
@tanstack/react-query \
|
|
10
|
+
@react-native-firebase/app \
|
|
11
|
+
@react-native-firebase/auth
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`react`, `react-native`, `@tanstack/react-query`, `@react-native-firebase/app`, and `@react-native-firebase/auth` are **required** peer dependencies.
|
|
15
|
+
|
|
16
|
+
Optional peers (install only if you use the matching feature):
|
|
17
|
+
|
|
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` |
|
|
24
|
+
|
|
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).
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { FanappsProvider, useAuth, useEntries } from '@fanapps/react-native';
|
|
31
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
32
|
+
|
|
33
|
+
export default function App() {
|
|
34
|
+
return (
|
|
35
|
+
<FanappsProvider
|
|
36
|
+
apiKey={process.env.EXPO_PUBLIC_FANAPPS_TOKEN!}
|
|
37
|
+
project={process.env.EXPO_PUBLIC_FANAPPS_PROJECT!}
|
|
38
|
+
locale="en"
|
|
39
|
+
storage={AsyncStorage}
|
|
40
|
+
>
|
|
41
|
+
<Feed />
|
|
42
|
+
</FanappsProvider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Feed() {
|
|
47
|
+
const { user, status } = useAuth();
|
|
48
|
+
const { data, isLoading, error } = useEntries({ blueprint: 'news', perPage: 20 });
|
|
49
|
+
|
|
50
|
+
if (status === 'loading') return <Text>…</Text>;
|
|
51
|
+
if (isLoading) return <Text>Loading…</Text>;
|
|
52
|
+
if (error) return <Text>Failed: {error.message}</Text>;
|
|
53
|
+
|
|
54
|
+
return <FlatList data={data?.data} /* ... */ />;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Provider props
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Required | Description |
|
|
61
|
+
| ------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `apiKey` | `string` | yes | App-level bearer token. Sent as `Authorization: Bearer <apiKey>`. |
|
|
63
|
+
| `project` | `string` | yes | Tenant UUID. Sent as `X-Tenant` header. |
|
|
64
|
+
| `locale` | `string` | no | Default locale (e.g. `"en"`). Reactive — changing it refetches all queries. |
|
|
65
|
+
| `baseUrl` | `string` | no | API base URL. Defaults to production. |
|
|
66
|
+
| `queryClient` | `QueryClient` | no | Reuse an existing React Query client. Otherwise one is created internally. |
|
|
67
|
+
| `storage` | `StorageAdapter` | no | Storage for persisted state (e.g. push target IDs). Defaults to AsyncStorage if installed; otherwise in-memory. |
|
|
68
|
+
|
|
69
|
+
## Auth
|
|
70
|
+
|
|
71
|
+
`useAuth` wraps `@react-native-firebase/auth` and keeps the SDK in sync with Firebase's auth state. The SDK automatically attaches a fresh Firebase ID token as `X-Firebase-Token` on every request, so the backend can resolve the signed-in `AppUser`.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useAuth } from '@fanapps/react-native';
|
|
75
|
+
|
|
76
|
+
function Screen() {
|
|
77
|
+
const {
|
|
78
|
+
user, // AuthUser | null — Firebase user shape
|
|
79
|
+
status, // 'loading' | 'signed-in' | 'signed-out'
|
|
80
|
+
signInWithEmail,
|
|
81
|
+
signUpWithEmail,
|
|
82
|
+
sendPasswordResetEmail,
|
|
83
|
+
sendEmailVerification,
|
|
84
|
+
signInWithGoogle,
|
|
85
|
+
signInWithApple,
|
|
86
|
+
signOut,
|
|
87
|
+
} = useAuth();
|
|
88
|
+
|
|
89
|
+
// ...
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Notes**
|
|
94
|
+
|
|
95
|
+
- `user` is sourced entirely from Firebase (`onAuthStateChanged`) — no backend round-trip on auth state change.
|
|
96
|
+
- `signInWithGoogle` throws a `FanappsAuthError` with code `peer/missing` if `@react-native-google-signin/google-signin` isn't installed. Same for `signInWithApple`.
|
|
97
|
+
- `signInWithApple` is iOS-only in v1. On Android it throws `auth/platform-not-supported`.
|
|
98
|
+
- Token refresh is handled by Firebase; the SDK calls `currentUser.getIdToken()` before each request, which returns a cached token and only refreshes when close to expiry.
|
|
99
|
+
|
|
100
|
+
## Push notifications
|
|
101
|
+
|
|
102
|
+
The SDK registers the device's FCM token as a _push target_ on the backend, using the Appwrite-style targetId pattern: a client-generated, client-persisted ID that's stable across token rotations.
|
|
103
|
+
|
|
104
|
+
### Register a device
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { useAuth, useRegisterDevice } from '@fanapps/react-native';
|
|
108
|
+
import { useEffect } from 'react';
|
|
109
|
+
|
|
110
|
+
function PushSetup() {
|
|
111
|
+
const { status } = useAuth();
|
|
112
|
+
const push = useRegisterDevice();
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (status === 'signed-in') push.register();
|
|
116
|
+
else if (status === 'signed-out') push.unregister();
|
|
117
|
+
}, [status]);
|
|
118
|
+
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`register()` does all of:
|
|
124
|
+
|
|
125
|
+
1. Requests notification permission (iOS prompt; no-op if already granted).
|
|
126
|
+
2. Fetches the FCM token.
|
|
127
|
+
3. Creates a push target on the backend (or updates an existing one if a `targetId` is stored locally).
|
|
128
|
+
4. Persists the `targetId` via the `storage` adapter.
|
|
129
|
+
5. Subscribes to `onTokenRefresh`; when FCM rotates the token, the SDK silently PATCHes the backend.
|
|
130
|
+
|
|
131
|
+
`unregister()` deletes the backend row and clears the stored `targetId`. Call it on sign-out.
|
|
132
|
+
|
|
133
|
+
### Subscribe to topics
|
|
134
|
+
|
|
135
|
+
FCM topics are the cheapest way to broadcast — no backend round-trip, no storage needed.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useTopicSubscription } from '@fanapps/react-native';
|
|
139
|
+
|
|
140
|
+
const topics = useTopicSubscription();
|
|
141
|
+
await topics.subscribe('news');
|
|
142
|
+
await topics.unsubscribe('news');
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The backend has no visibility into who subscribed to which topic — that's FCM's job. Your dashboard just sends a message to the topic name.
|
|
146
|
+
|
|
147
|
+
### Displaying notifications
|
|
148
|
+
|
|
149
|
+
The SDK does **not** manage notification display or tap handling. Set up handlers yourself using `@react-native-firebase/messaging`:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import messaging from '@react-native-firebase/messaging';
|
|
153
|
+
|
|
154
|
+
messaging().onMessage(async remote => {
|
|
155
|
+
// foreground notification
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
messaging().onNotificationOpenedApp(remote => {
|
|
159
|
+
// user tapped a background notification
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Content hooks
|
|
164
|
+
|
|
165
|
+
All hooks accept an `options` arg with `{ locale?, enabled? }` to override the provider locale per call or pause the query.
|
|
166
|
+
|
|
167
|
+
### Entries
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
useEntries(filters?: EntryFilters, options?) // Paginated<Entry>
|
|
171
|
+
useEntry(id, options?) // Entry
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Filters: `blueprint`, `collection`, `slug`, `published`, `sort` (e.g. `-date`), `perPage`, `page`.
|
|
175
|
+
|
|
176
|
+
### Articles
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
useArticles(filters?: ArticleFilters, options?) // Paginated<Article>
|
|
180
|
+
useArticle(id, options?) // Article
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Filters: `is_published`, `sort`, `perPage`, `page`.
|
|
184
|
+
|
|
185
|
+
### Trivia
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
useTrivias(options?) // Trivia[]
|
|
189
|
+
useTrivia(id, options?) // Trivia
|
|
190
|
+
useSubmitTriviaResponse() // mutation
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
const submit = useSubmitTriviaResponse();
|
|
195
|
+
submit.mutate(
|
|
196
|
+
{ triviaId, answerId },
|
|
197
|
+
{
|
|
198
|
+
onSuccess: ({ is_correct, points_awarded }) => {
|
|
199
|
+
// ...
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
On success the mutation invalidates the related `useTrivia(id)` query so `responses_count` and other derived values refresh.
|
|
206
|
+
|
|
207
|
+
## Escape hatch
|
|
208
|
+
|
|
209
|
+
For endpoints not covered by a hook:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { useFanappsClient } from '@fanapps/react-native';
|
|
213
|
+
|
|
214
|
+
function Thing() {
|
|
215
|
+
const client = useFanappsClient();
|
|
216
|
+
// client.get('/api/custom', { foo: 'bar' })
|
|
217
|
+
// client.post('/api/custom', { ... })
|
|
218
|
+
// client.patch('/api/custom/1', { ... })
|
|
219
|
+
// client.delete('/api/custom/1')
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
All client calls carry the same `Authorization`, `X-Tenant`, and `X-Firebase-Token` headers as the hooks.
|
|
224
|
+
|
|
225
|
+
## Errors
|
|
226
|
+
|
|
227
|
+
- 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`.
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { FanappsError, FanappsAuthError } from '@fanapps/react-native';
|
|
232
|
+
|
|
233
|
+
const { error } = useEntries();
|
|
234
|
+
if (error instanceof FanappsError && error.status === 401) {
|
|
235
|
+
// sign-out, etc.
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
await signInWithGoogle();
|
|
240
|
+
} catch (e) {
|
|
241
|
+
if (e instanceof FanappsAuthError && e.code === 'peer/load-failed') {
|
|
242
|
+
// Google package couldn't be loaded — see "Symlinked installs" section
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Storage adapter
|
|
248
|
+
|
|
249
|
+
`FanappsProvider`'s `storage` prop accepts anything with this shape:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
interface StorageAdapter {
|
|
253
|
+
getItem(key: string): Promise<string | null>;
|
|
254
|
+
setItem(key: string, value: string): Promise<void>;
|
|
255
|
+
removeItem(key: string): Promise<void>;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
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.
|
|
317
|
+
|
|
318
|
+
## Build
|
|
319
|
+
|
|
320
|
+
```sh
|
|
321
|
+
pnpm install
|
|
322
|
+
pnpm build # tsup → dist/
|
|
323
|
+
pnpm typecheck # tsc --noEmit
|
|
324
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { QueryClient, UseQueryResult, UseMutationResult } from '@tanstack/react-query';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface StorageAdapter {
|
|
6
|
+
getItem(key: string): Promise<string | null>;
|
|
7
|
+
setItem(key: string, value: string): Promise<void>;
|
|
8
|
+
removeItem(key: string): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Locale = string;
|
|
12
|
+
type EntryData = Record<string, unknown>;
|
|
13
|
+
interface Entry {
|
|
14
|
+
id: string | number;
|
|
15
|
+
slug: string;
|
|
16
|
+
published: boolean;
|
|
17
|
+
date: string | null;
|
|
18
|
+
order: number | null;
|
|
19
|
+
collection: string | null;
|
|
20
|
+
blueprint: string;
|
|
21
|
+
data: EntryData;
|
|
22
|
+
created_at: string;
|
|
23
|
+
updated_at: string;
|
|
24
|
+
locale: Locale;
|
|
25
|
+
available_locales?: Locale[];
|
|
26
|
+
}
|
|
27
|
+
interface Article {
|
|
28
|
+
id: string | number;
|
|
29
|
+
title: string;
|
|
30
|
+
content: string;
|
|
31
|
+
is_published: boolean;
|
|
32
|
+
featured_image_url: string | null;
|
|
33
|
+
created_at: string;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
locale: Locale;
|
|
36
|
+
available_locales?: Locale[];
|
|
37
|
+
}
|
|
38
|
+
interface TriviaAnswer {
|
|
39
|
+
id: string | number;
|
|
40
|
+
text: string;
|
|
41
|
+
is_correct: boolean;
|
|
42
|
+
sort_order: number;
|
|
43
|
+
}
|
|
44
|
+
interface Trivia {
|
|
45
|
+
id: string | number;
|
|
46
|
+
title: string;
|
|
47
|
+
description: string | null;
|
|
48
|
+
featured_image_id: string | number | null;
|
|
49
|
+
featured_image?: {
|
|
50
|
+
id: string | number;
|
|
51
|
+
url: string;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
} | null;
|
|
54
|
+
awards_points: boolean;
|
|
55
|
+
points_amount: number;
|
|
56
|
+
max_time_seconds: number | null;
|
|
57
|
+
show_correct_after: boolean;
|
|
58
|
+
starts_at: string | null;
|
|
59
|
+
ends_at: string | null;
|
|
60
|
+
answers: TriviaAnswer[];
|
|
61
|
+
responses_count?: number;
|
|
62
|
+
created_at: string;
|
|
63
|
+
updated_at: string;
|
|
64
|
+
}
|
|
65
|
+
interface TriviaResponseResult {
|
|
66
|
+
is_correct: boolean;
|
|
67
|
+
points_awarded: number;
|
|
68
|
+
correct_answer?: TriviaAnswer;
|
|
69
|
+
}
|
|
70
|
+
interface PaginationMeta {
|
|
71
|
+
current_page: number;
|
|
72
|
+
last_page: number;
|
|
73
|
+
per_page: number;
|
|
74
|
+
total: number;
|
|
75
|
+
from: number | null;
|
|
76
|
+
to: number | null;
|
|
77
|
+
path?: string;
|
|
78
|
+
}
|
|
79
|
+
interface PaginationLinks {
|
|
80
|
+
first: string | null;
|
|
81
|
+
last: string | null;
|
|
82
|
+
prev: string | null;
|
|
83
|
+
next: string | null;
|
|
84
|
+
}
|
|
85
|
+
interface Paginated<T> {
|
|
86
|
+
data: T[];
|
|
87
|
+
meta: PaginationMeta;
|
|
88
|
+
links: PaginationLinks;
|
|
89
|
+
}
|
|
90
|
+
interface PaginationParams {
|
|
91
|
+
page?: number;
|
|
92
|
+
perPage?: number;
|
|
93
|
+
sort?: string;
|
|
94
|
+
}
|
|
95
|
+
interface EntryFilters extends PaginationParams {
|
|
96
|
+
blueprint?: string;
|
|
97
|
+
collection?: string;
|
|
98
|
+
slug?: string;
|
|
99
|
+
published?: boolean;
|
|
100
|
+
}
|
|
101
|
+
interface ArticleFilters extends PaginationParams {
|
|
102
|
+
is_published?: boolean;
|
|
103
|
+
}
|
|
104
|
+
interface HookOptions {
|
|
105
|
+
locale?: Locale;
|
|
106
|
+
enabled?: boolean;
|
|
107
|
+
}
|
|
108
|
+
type AuthStatus = 'loading' | 'signed-in' | 'signed-out';
|
|
109
|
+
interface AuthUser {
|
|
110
|
+
uid: string;
|
|
111
|
+
email: string | null;
|
|
112
|
+
displayName: string | null;
|
|
113
|
+
photoURL: string | null;
|
|
114
|
+
phoneNumber: string | null;
|
|
115
|
+
isAnonymous: boolean;
|
|
116
|
+
providerId: string | null;
|
|
117
|
+
}
|
|
118
|
+
interface PushTarget {
|
|
119
|
+
id: string;
|
|
120
|
+
app_user_id: number | string | null;
|
|
121
|
+
identifier: string;
|
|
122
|
+
provider: 'fcm' | 'apns';
|
|
123
|
+
provider_id: string | null;
|
|
124
|
+
device_brand: string | null;
|
|
125
|
+
device_model: string | null;
|
|
126
|
+
os_name: string | null;
|
|
127
|
+
os_version: string | null;
|
|
128
|
+
last_seen_at: string | null;
|
|
129
|
+
created_at: string;
|
|
130
|
+
updated_at: string;
|
|
131
|
+
}
|
|
132
|
+
interface CreatePushTargetInput {
|
|
133
|
+
targetId: string;
|
|
134
|
+
identifier: string;
|
|
135
|
+
providerId?: string;
|
|
136
|
+
deviceBrand?: string;
|
|
137
|
+
deviceModel?: string;
|
|
138
|
+
osName?: string;
|
|
139
|
+
osVersion?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface FanappsProviderProps {
|
|
143
|
+
apiKey: string;
|
|
144
|
+
project: string;
|
|
145
|
+
locale?: Locale;
|
|
146
|
+
baseUrl?: string;
|
|
147
|
+
queryClient?: QueryClient;
|
|
148
|
+
storage?: StorageAdapter;
|
|
149
|
+
children: ReactNode;
|
|
150
|
+
}
|
|
151
|
+
declare function FanappsProvider({ apiKey, project, locale, baseUrl, queryClient, storage, children, }: FanappsProviderProps): react_jsx_runtime.JSX.Element;
|
|
152
|
+
|
|
153
|
+
interface FanappsClientOptions {
|
|
154
|
+
apiKey: string;
|
|
155
|
+
project: string;
|
|
156
|
+
baseUrl: string;
|
|
157
|
+
getIdToken?: () => Promise<string | null>;
|
|
158
|
+
}
|
|
159
|
+
type QueryParams = Record<string, unknown>;
|
|
160
|
+
declare class FanappsClient {
|
|
161
|
+
private readonly apiKey;
|
|
162
|
+
private readonly project;
|
|
163
|
+
private readonly baseUrl;
|
|
164
|
+
private readonly getIdToken?;
|
|
165
|
+
constructor(options: FanappsClientOptions);
|
|
166
|
+
get<T>(path: string, params?: QueryParams, signal?: AbortSignal): Promise<T>;
|
|
167
|
+
post<T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T>;
|
|
168
|
+
patch<T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T>;
|
|
169
|
+
delete<T>(path: string, signal?: AbortSignal): Promise<T>;
|
|
170
|
+
createPushTarget(input: CreatePushTargetInput): Promise<PushTarget>;
|
|
171
|
+
updatePushTarget(targetId: string, identifier: string): Promise<PushTarget>;
|
|
172
|
+
deletePushTarget(targetId: string): Promise<void>;
|
|
173
|
+
private request;
|
|
174
|
+
private buildUrl;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
declare class FanappsError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
readonly body: unknown;
|
|
180
|
+
constructor(message: string, status: number, body: unknown);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
declare class FanappsAuthError extends Error {
|
|
184
|
+
readonly code: string;
|
|
185
|
+
constructor(message: string, code: string);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare function useFanappsClient(): FanappsClient;
|
|
189
|
+
|
|
190
|
+
interface UseAuthResult {
|
|
191
|
+
user: AuthUser | null;
|
|
192
|
+
status: AuthStatus;
|
|
193
|
+
signInWithEmail(email: string, password: string): Promise<AuthUser>;
|
|
194
|
+
signUpWithEmail(email: string, password: string): Promise<AuthUser>;
|
|
195
|
+
sendPasswordResetEmail(email: string): Promise<void>;
|
|
196
|
+
sendEmailVerification(): Promise<void>;
|
|
197
|
+
signInWithGoogle(): Promise<AuthUser>;
|
|
198
|
+
signInWithApple(): Promise<AuthUser>;
|
|
199
|
+
signOut(): Promise<void>;
|
|
200
|
+
}
|
|
201
|
+
declare function useAuth(): UseAuthResult;
|
|
202
|
+
|
|
203
|
+
declare function useEntries(filters?: EntryFilters, options?: HookOptions): UseQueryResult<Paginated<Entry>>;
|
|
204
|
+
|
|
205
|
+
declare function useEntry(id: string | number | null | undefined, options?: HookOptions): UseQueryResult<Entry>;
|
|
206
|
+
|
|
207
|
+
declare function useArticles(filters?: ArticleFilters, options?: HookOptions): UseQueryResult<Paginated<Article>>;
|
|
208
|
+
|
|
209
|
+
declare function useArticle(id: string | number | null | undefined, options?: HookOptions): UseQueryResult<Article>;
|
|
210
|
+
|
|
211
|
+
interface UseRegisterDeviceResult {
|
|
212
|
+
targetId: string | null;
|
|
213
|
+
token: string | null;
|
|
214
|
+
target: PushTarget | null;
|
|
215
|
+
isRegistering: boolean;
|
|
216
|
+
error: Error | null;
|
|
217
|
+
register(): Promise<PushTarget>;
|
|
218
|
+
unregister(): Promise<void>;
|
|
219
|
+
}
|
|
220
|
+
declare function useRegisterDevice(): UseRegisterDeviceResult;
|
|
221
|
+
|
|
222
|
+
declare function useTrivias(options?: HookOptions): UseQueryResult<Trivia[]>;
|
|
223
|
+
|
|
224
|
+
declare function useTrivia(id: string | number | null | undefined, options?: HookOptions): UseQueryResult<Trivia>;
|
|
225
|
+
|
|
226
|
+
interface SubmitTriviaResponseInput {
|
|
227
|
+
triviaId: string | number;
|
|
228
|
+
answerId: string | number;
|
|
229
|
+
}
|
|
230
|
+
declare function useSubmitTriviaResponse(): UseMutationResult<TriviaResponseResult, FanappsError, SubmitTriviaResponseInput>;
|
|
231
|
+
|
|
232
|
+
interface UseTopicSubscriptionResult {
|
|
233
|
+
subscribe(topic: string): Promise<void>;
|
|
234
|
+
unsubscribe(topic: string): Promise<void>;
|
|
235
|
+
}
|
|
236
|
+
declare function useTopicSubscription(): UseTopicSubscriptionResult;
|
|
237
|
+
|
|
238
|
+
export { type Article, type ArticleFilters, type AuthStatus, type AuthUser, type CreatePushTargetInput, type Entry, type EntryData, type EntryFilters, FanappsAuthError, FanappsClient, type FanappsClientOptions, FanappsError, FanappsProvider, type FanappsProviderProps, type HookOptions, type Locale, type Paginated, type PaginationLinks, type PaginationMeta, type PaginationParams, type PushTarget, type StorageAdapter, type SubmitTriviaResponseInput, type Trivia, type TriviaAnswer, type TriviaResponseResult, type UseAuthResult, type UseRegisterDeviceResult, type UseTopicSubscriptionResult, useArticle, useArticles, useAuth, useEntries, useEntry, useFanappsClient, useRegisterDevice, useSubmitTriviaResponse, useTopicSubscription, useTrivia, useTrivias };
|