@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/dist/index.mjs
ADDED
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/provider.tsx
|
|
9
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
10
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
11
|
+
|
|
12
|
+
// src/auth/errors.ts
|
|
13
|
+
var FanappsAuthError = class extends Error {
|
|
14
|
+
constructor(message, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "FanappsAuthError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function peerLoadFailed(pkg, cause) {
|
|
21
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
22
|
+
return new FanappsAuthError(
|
|
23
|
+
`@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.`,
|
|
24
|
+
"peer/load-failed"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function peerUnexpectedShape(pkg, details) {
|
|
28
|
+
return new FanappsAuthError(
|
|
29
|
+
`@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.`,
|
|
30
|
+
"peer/unexpected-shape"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/auth/firebaseApp.ts
|
|
35
|
+
var cachedAuth = null;
|
|
36
|
+
function firebaseAuth() {
|
|
37
|
+
if (cachedAuth) {
|
|
38
|
+
return cachedAuth;
|
|
39
|
+
}
|
|
40
|
+
let mod;
|
|
41
|
+
try {
|
|
42
|
+
mod = __require("@react-native-firebase/auth");
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw peerLoadFailed("@react-native-firebase/auth", e);
|
|
45
|
+
}
|
|
46
|
+
const factory = resolveFactory(mod);
|
|
47
|
+
if (!factory) {
|
|
48
|
+
throw peerUnexpectedShape(
|
|
49
|
+
"@react-native-firebase/auth",
|
|
50
|
+
`expected a factory function, got ${describeShape(mod)}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
cachedAuth = factory();
|
|
54
|
+
return cachedAuth;
|
|
55
|
+
}
|
|
56
|
+
function resolveFactory(mod) {
|
|
57
|
+
if (typeof mod === "function") {
|
|
58
|
+
return mod;
|
|
59
|
+
}
|
|
60
|
+
if (mod && typeof mod === "object" && "default" in mod) {
|
|
61
|
+
const def = mod.default;
|
|
62
|
+
if (typeof def === "function") {
|
|
63
|
+
return def;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function describeShape(mod) {
|
|
69
|
+
if (mod === null) return "null";
|
|
70
|
+
if (typeof mod !== "object") return typeof mod;
|
|
71
|
+
const keys = Object.keys(mod).slice(0, 5);
|
|
72
|
+
return `object with keys [${keys.join(", ")}]`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/auth/authStore.ts
|
|
76
|
+
function mapUser(fb) {
|
|
77
|
+
if (!fb) return null;
|
|
78
|
+
return {
|
|
79
|
+
uid: fb.uid,
|
|
80
|
+
email: fb.email,
|
|
81
|
+
displayName: fb.displayName,
|
|
82
|
+
photoURL: fb.photoURL,
|
|
83
|
+
phoneNumber: fb.phoneNumber,
|
|
84
|
+
isAnonymous: fb.isAnonymous,
|
|
85
|
+
providerId: fb.providerId ?? null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
var AuthStore = class {
|
|
89
|
+
constructor() {
|
|
90
|
+
this.snapshot = {
|
|
91
|
+
user: null,
|
|
92
|
+
firebaseUser: null,
|
|
93
|
+
status: "loading"
|
|
94
|
+
};
|
|
95
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
96
|
+
this.unsubscribe = null;
|
|
97
|
+
}
|
|
98
|
+
start() {
|
|
99
|
+
if (this.unsubscribe) return;
|
|
100
|
+
this.unsubscribe = firebaseAuth().onAuthStateChanged((fb) => {
|
|
101
|
+
this.snapshot = {
|
|
102
|
+
user: mapUser(fb),
|
|
103
|
+
firebaseUser: fb,
|
|
104
|
+
status: fb ? "signed-in" : "signed-out"
|
|
105
|
+
};
|
|
106
|
+
this.emit();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
stop() {
|
|
110
|
+
this.unsubscribe?.();
|
|
111
|
+
this.unsubscribe = null;
|
|
112
|
+
}
|
|
113
|
+
subscribe(listener) {
|
|
114
|
+
this.listeners.add(listener);
|
|
115
|
+
return () => {
|
|
116
|
+
this.listeners.delete(listener);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
getSnapshot() {
|
|
120
|
+
return this.snapshot;
|
|
121
|
+
}
|
|
122
|
+
async getCurrentIdToken() {
|
|
123
|
+
const user = firebaseAuth().currentUser;
|
|
124
|
+
if (!user) return null;
|
|
125
|
+
return user.getIdToken();
|
|
126
|
+
}
|
|
127
|
+
emit() {
|
|
128
|
+
for (const l of this.listeners) l(this.snapshot);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/errors.ts
|
|
133
|
+
var FanappsError = class extends Error {
|
|
134
|
+
constructor(message, status, body) {
|
|
135
|
+
super(message);
|
|
136
|
+
this.name = "FanappsError";
|
|
137
|
+
this.status = status;
|
|
138
|
+
this.body = body;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/client.ts
|
|
143
|
+
var FanappsClient = class {
|
|
144
|
+
constructor(options) {
|
|
145
|
+
this.apiKey = options.apiKey;
|
|
146
|
+
this.project = options.project;
|
|
147
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
148
|
+
this.getIdToken = options.getIdToken;
|
|
149
|
+
}
|
|
150
|
+
get(path, params, signal) {
|
|
151
|
+
return this.request("GET", path, { params, signal });
|
|
152
|
+
}
|
|
153
|
+
post(path, body, signal) {
|
|
154
|
+
return this.request("POST", path, { body, signal });
|
|
155
|
+
}
|
|
156
|
+
patch(path, body, signal) {
|
|
157
|
+
return this.request("PATCH", path, { body, signal });
|
|
158
|
+
}
|
|
159
|
+
delete(path, signal) {
|
|
160
|
+
return this.request("DELETE", path, { signal });
|
|
161
|
+
}
|
|
162
|
+
async createPushTarget(input) {
|
|
163
|
+
const response = await this.post(
|
|
164
|
+
"/api/account/targets/push",
|
|
165
|
+
input
|
|
166
|
+
);
|
|
167
|
+
return response.data;
|
|
168
|
+
}
|
|
169
|
+
async updatePushTarget(targetId, identifier) {
|
|
170
|
+
const response = await this.patch(
|
|
171
|
+
`/api/account/targets/push/${encodeURIComponent(targetId)}`,
|
|
172
|
+
{ identifier }
|
|
173
|
+
);
|
|
174
|
+
return response.data;
|
|
175
|
+
}
|
|
176
|
+
async deletePushTarget(targetId) {
|
|
177
|
+
await this.delete(`/api/account/targets/push/${encodeURIComponent(targetId)}`);
|
|
178
|
+
}
|
|
179
|
+
async request(method, path, opts) {
|
|
180
|
+
const url = this.buildUrl(path, opts.params);
|
|
181
|
+
const headers = {
|
|
182
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
183
|
+
"X-Tenant": this.project,
|
|
184
|
+
Accept: "application/json"
|
|
185
|
+
};
|
|
186
|
+
if (this.getIdToken) {
|
|
187
|
+
const idToken = await this.getIdToken();
|
|
188
|
+
if (idToken) {
|
|
189
|
+
headers["X-Firebase-Token"] = idToken;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const init = { method, headers, signal: opts.signal };
|
|
193
|
+
if (opts.body !== void 0) {
|
|
194
|
+
headers["Content-Type"] = "application/json";
|
|
195
|
+
init.body = JSON.stringify(opts.body);
|
|
196
|
+
}
|
|
197
|
+
const response = await fetch(url, init);
|
|
198
|
+
const raw = await response.text();
|
|
199
|
+
const payload = raw.length > 0 ? safeJson(raw) : null;
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
const message = (payload && typeof payload === "object" && "message" in payload ? String(payload.message) : null) ?? `Fanapps request failed with status ${response.status}`;
|
|
202
|
+
throw new FanappsError(message, response.status, payload);
|
|
203
|
+
}
|
|
204
|
+
return payload;
|
|
205
|
+
}
|
|
206
|
+
buildUrl(path, params) {
|
|
207
|
+
const base = path.startsWith("/") ? `${this.baseUrl}${path}` : `${this.baseUrl}/${path}`;
|
|
208
|
+
if (!params) return base;
|
|
209
|
+
const query = serializeParams(params);
|
|
210
|
+
return query.length > 0 ? `${base}?${query}` : base;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
function serializeParams(params) {
|
|
214
|
+
const parts = [];
|
|
215
|
+
for (const [key, value] of Object.entries(params)) {
|
|
216
|
+
if (value === void 0 || value === null) continue;
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
for (const item of value) {
|
|
219
|
+
if (item === void 0 || item === null) continue;
|
|
220
|
+
parts.push(`${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`);
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
225
|
+
}
|
|
226
|
+
return parts.join("&");
|
|
227
|
+
}
|
|
228
|
+
function safeJson(raw) {
|
|
229
|
+
try {
|
|
230
|
+
return JSON.parse(raw);
|
|
231
|
+
} catch {
|
|
232
|
+
return raw;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/context.ts
|
|
237
|
+
import { createContext, useContext } from "react";
|
|
238
|
+
var FanappsContext = createContext(null);
|
|
239
|
+
function useFanapps() {
|
|
240
|
+
const ctx = useContext(FanappsContext);
|
|
241
|
+
if (!ctx) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"@fanapps/react-native: useFanapps must be used inside <FanappsProvider>."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return ctx;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/push/storage.ts
|
|
250
|
+
var memoryFallbackWarned = false;
|
|
251
|
+
function resolveStorage(explicit) {
|
|
252
|
+
if (explicit) return explicit;
|
|
253
|
+
try {
|
|
254
|
+
const mod = __require("@react-native-async-storage/async-storage");
|
|
255
|
+
return mod.default;
|
|
256
|
+
} catch {
|
|
257
|
+
if (!memoryFallbackWarned && typeof __DEV__ !== "undefined" && __DEV__) {
|
|
258
|
+
console.warn(
|
|
259
|
+
"[@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."
|
|
260
|
+
);
|
|
261
|
+
memoryFallbackWarned = true;
|
|
262
|
+
}
|
|
263
|
+
return createMemoryStorage();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function createMemoryStorage() {
|
|
267
|
+
const store = /* @__PURE__ */ new Map();
|
|
268
|
+
return {
|
|
269
|
+
async getItem(key) {
|
|
270
|
+
return store.get(key) ?? null;
|
|
271
|
+
},
|
|
272
|
+
async setItem(key, value) {
|
|
273
|
+
store.set(key, value);
|
|
274
|
+
},
|
|
275
|
+
async removeItem(key) {
|
|
276
|
+
store.delete(key);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/provider.tsx
|
|
282
|
+
import { jsx } from "react/jsx-runtime";
|
|
283
|
+
var DEFAULT_BASE_URL = "https://fanapps.app";
|
|
284
|
+
function FanappsProvider({
|
|
285
|
+
apiKey,
|
|
286
|
+
project,
|
|
287
|
+
locale,
|
|
288
|
+
baseUrl = DEFAULT_BASE_URL,
|
|
289
|
+
queryClient,
|
|
290
|
+
storage,
|
|
291
|
+
children
|
|
292
|
+
}) {
|
|
293
|
+
const authStoreRef = useRef(null);
|
|
294
|
+
if (!authStoreRef.current) {
|
|
295
|
+
authStoreRef.current = new AuthStore();
|
|
296
|
+
}
|
|
297
|
+
const authStore = authStoreRef.current;
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
authStore.start();
|
|
300
|
+
return () => authStore.stop();
|
|
301
|
+
}, [authStore]);
|
|
302
|
+
const resolvedStorage = useMemo(() => resolveStorage(storage), [storage]);
|
|
303
|
+
const client = useMemo(
|
|
304
|
+
() => new FanappsClient({
|
|
305
|
+
apiKey,
|
|
306
|
+
project,
|
|
307
|
+
baseUrl,
|
|
308
|
+
getIdToken: () => authStore.getCurrentIdToken()
|
|
309
|
+
}),
|
|
310
|
+
[apiKey, project, baseUrl, authStore]
|
|
311
|
+
);
|
|
312
|
+
const resolvedQueryClient = useMemo(
|
|
313
|
+
() => queryClient ?? new QueryClient(),
|
|
314
|
+
[queryClient]
|
|
315
|
+
);
|
|
316
|
+
const value = useMemo(
|
|
317
|
+
() => ({ client, authStore, storage: resolvedStorage, locale }),
|
|
318
|
+
[client, authStore, resolvedStorage, locale]
|
|
319
|
+
);
|
|
320
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, { client: resolvedQueryClient, children: /* @__PURE__ */ jsx(FanappsContext.Provider, { value, children }) });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/hooks/useFanappsClient.ts
|
|
324
|
+
function useFanappsClient() {
|
|
325
|
+
return useFanapps().client;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/hooks/useAuth.ts
|
|
329
|
+
import { useSyncExternalStore, useCallback } from "react";
|
|
330
|
+
|
|
331
|
+
// src/auth/providers/google.ts
|
|
332
|
+
async function signInWithGoogle() {
|
|
333
|
+
let mod;
|
|
334
|
+
try {
|
|
335
|
+
mod = __require("@react-native-google-signin/google-signin");
|
|
336
|
+
} catch (e) {
|
|
337
|
+
throw peerLoadFailed("@react-native-google-signin/google-signin", e);
|
|
338
|
+
}
|
|
339
|
+
let authMod;
|
|
340
|
+
try {
|
|
341
|
+
authMod = __require("@react-native-firebase/auth");
|
|
342
|
+
} catch (e) {
|
|
343
|
+
throw peerLoadFailed("@react-native-firebase/auth", e);
|
|
344
|
+
}
|
|
345
|
+
await mod.GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
|
|
346
|
+
const result = await mod.GoogleSignin.signIn();
|
|
347
|
+
const idToken = "data" in result ? result.data?.idToken ?? null : result.idToken;
|
|
348
|
+
if (!idToken) {
|
|
349
|
+
throw new Error("Google sign-in returned no ID token.");
|
|
350
|
+
}
|
|
351
|
+
const credential = authMod.GoogleAuthProvider.credential(idToken);
|
|
352
|
+
return firebaseAuth().signInWithCredential(
|
|
353
|
+
credential
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/auth/providers/apple.ts
|
|
358
|
+
import { Platform } from "react-native";
|
|
359
|
+
async function signInWithApple() {
|
|
360
|
+
if (Platform.OS !== "ios") {
|
|
361
|
+
throw new FanappsAuthError(
|
|
362
|
+
"Apple sign-in is only supported on iOS in v1. Use web OAuth on Android.",
|
|
363
|
+
"auth/platform-not-supported"
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
let mod;
|
|
367
|
+
try {
|
|
368
|
+
mod = __require("@invertase/react-native-apple-authentication");
|
|
369
|
+
} catch (e) {
|
|
370
|
+
throw peerLoadFailed("@invertase/react-native-apple-authentication", 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
|
+
if (!mod.appleAuth.isSupported) {
|
|
379
|
+
throw new FanappsAuthError("Apple sign-in not supported on this device.", "auth/not-supported");
|
|
380
|
+
}
|
|
381
|
+
const result = await mod.appleAuth.performRequest({
|
|
382
|
+
requestedOperation: mod.appleAuth.Operation.LOGIN,
|
|
383
|
+
requestedScopes: [mod.appleAuth.Scope.EMAIL, mod.appleAuth.Scope.FULL_NAME]
|
|
384
|
+
});
|
|
385
|
+
if (!result.identityToken) {
|
|
386
|
+
throw new FanappsAuthError("Apple did not return an identity token.", "auth/no-token");
|
|
387
|
+
}
|
|
388
|
+
const credential = authMod.AppleAuthProvider.credential(
|
|
389
|
+
result.identityToken,
|
|
390
|
+
result.nonce ?? void 0
|
|
391
|
+
);
|
|
392
|
+
return firebaseAuth().signInWithCredential(
|
|
393
|
+
credential
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/hooks/useAuth.ts
|
|
398
|
+
function useAuth() {
|
|
399
|
+
const { authStore } = useFanapps();
|
|
400
|
+
const snapshot = useSyncExternalStore(
|
|
401
|
+
(listener) => authStore.subscribe(listener),
|
|
402
|
+
() => authStore.getSnapshot(),
|
|
403
|
+
() => authStore.getSnapshot()
|
|
404
|
+
);
|
|
405
|
+
const signInWithEmail = useCallback(async (email, password) => {
|
|
406
|
+
const cred = await firebaseAuth().signInWithEmailAndPassword(email, password);
|
|
407
|
+
return authStore.getSnapshot().user ?? mapMinimal(cred.user);
|
|
408
|
+
}, [authStore]);
|
|
409
|
+
const signUpWithEmail = useCallback(async (email, password) => {
|
|
410
|
+
const cred = await firebaseAuth().createUserWithEmailAndPassword(email, password);
|
|
411
|
+
return authStore.getSnapshot().user ?? mapMinimal(cred.user);
|
|
412
|
+
}, [authStore]);
|
|
413
|
+
const sendPasswordResetEmail = useCallback(async (email) => {
|
|
414
|
+
await firebaseAuth().sendPasswordResetEmail(email);
|
|
415
|
+
}, []);
|
|
416
|
+
const sendEmailVerification = useCallback(async () => {
|
|
417
|
+
const current = firebaseAuth().currentUser;
|
|
418
|
+
if (!current) throw new Error("No signed-in user to verify.");
|
|
419
|
+
await current.sendEmailVerification();
|
|
420
|
+
}, []);
|
|
421
|
+
const signInWithGoogle2 = useCallback(async () => {
|
|
422
|
+
const cred = await signInWithGoogle();
|
|
423
|
+
return authStore.getSnapshot().user ?? mapMinimal(cred.user);
|
|
424
|
+
}, [authStore]);
|
|
425
|
+
const signInWithApple2 = useCallback(async () => {
|
|
426
|
+
const cred = await signInWithApple();
|
|
427
|
+
return authStore.getSnapshot().user ?? mapMinimal(cred.user);
|
|
428
|
+
}, [authStore]);
|
|
429
|
+
const signOut = useCallback(async () => {
|
|
430
|
+
await firebaseAuth().signOut();
|
|
431
|
+
}, []);
|
|
432
|
+
return {
|
|
433
|
+
user: snapshot.user,
|
|
434
|
+
status: snapshot.status,
|
|
435
|
+
signInWithEmail,
|
|
436
|
+
signUpWithEmail,
|
|
437
|
+
sendPasswordResetEmail,
|
|
438
|
+
sendEmailVerification,
|
|
439
|
+
signInWithGoogle: signInWithGoogle2,
|
|
440
|
+
signInWithApple: signInWithApple2,
|
|
441
|
+
signOut
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function mapMinimal(fb) {
|
|
445
|
+
return {
|
|
446
|
+
uid: fb.uid,
|
|
447
|
+
email: fb.email,
|
|
448
|
+
displayName: fb.displayName,
|
|
449
|
+
photoURL: fb.photoURL,
|
|
450
|
+
phoneNumber: fb.phoneNumber,
|
|
451
|
+
isAnonymous: fb.isAnonymous,
|
|
452
|
+
providerId: fb.providerId ?? null
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/hooks/useEntries.ts
|
|
457
|
+
import { useQuery } from "@tanstack/react-query";
|
|
458
|
+
function useEntries(filters = {}, options = {}) {
|
|
459
|
+
const { client, locale: providerLocale } = useFanapps();
|
|
460
|
+
const locale = options.locale ?? providerLocale;
|
|
461
|
+
return useQuery({
|
|
462
|
+
queryKey: ["fanapps", "entries", { locale, ...filters }],
|
|
463
|
+
queryFn: ({ signal }) => client.get("/api/entries", { ...filters, locale }, signal),
|
|
464
|
+
enabled: options.enabled ?? true
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/hooks/useEntry.ts
|
|
469
|
+
import { useQuery as useQuery2 } from "@tanstack/react-query";
|
|
470
|
+
function useEntry(id, options = {}) {
|
|
471
|
+
const { client, locale: providerLocale } = useFanapps();
|
|
472
|
+
const locale = options.locale ?? providerLocale;
|
|
473
|
+
return useQuery2({
|
|
474
|
+
queryKey: ["fanapps", "entry", id, { locale }],
|
|
475
|
+
queryFn: async ({ signal }) => {
|
|
476
|
+
const response = await client.get(
|
|
477
|
+
`/api/entries/${id}`,
|
|
478
|
+
{ locale },
|
|
479
|
+
signal
|
|
480
|
+
);
|
|
481
|
+
return response.data;
|
|
482
|
+
},
|
|
483
|
+
enabled: (options.enabled ?? true) && id != null && id !== ""
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/hooks/useArticles.ts
|
|
488
|
+
import { useQuery as useQuery3 } from "@tanstack/react-query";
|
|
489
|
+
function useArticles(filters = {}, options = {}) {
|
|
490
|
+
const { client, locale: providerLocale } = useFanapps();
|
|
491
|
+
const locale = options.locale ?? providerLocale;
|
|
492
|
+
return useQuery3({
|
|
493
|
+
queryKey: ["fanapps", "articles", { locale, ...filters }],
|
|
494
|
+
queryFn: ({ signal }) => client.get("/api/articles", { ...filters, locale }, signal),
|
|
495
|
+
enabled: options.enabled ?? true
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/hooks/useArticle.ts
|
|
500
|
+
import { useQuery as useQuery4 } from "@tanstack/react-query";
|
|
501
|
+
function useArticle(id, options = {}) {
|
|
502
|
+
const { client, locale: providerLocale } = useFanapps();
|
|
503
|
+
const locale = options.locale ?? providerLocale;
|
|
504
|
+
return useQuery4({
|
|
505
|
+
queryKey: ["fanapps", "article", id, { locale }],
|
|
506
|
+
queryFn: async ({ signal }) => {
|
|
507
|
+
const response = await client.get(
|
|
508
|
+
`/api/articles/${id}`,
|
|
509
|
+
{ locale },
|
|
510
|
+
signal
|
|
511
|
+
);
|
|
512
|
+
return response.data;
|
|
513
|
+
},
|
|
514
|
+
enabled: (options.enabled ?? true) && id != null && id !== ""
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/hooks/useRegisterDevice.ts
|
|
519
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
520
|
+
import { Platform as Platform2 } from "react-native";
|
|
521
|
+
|
|
522
|
+
// src/push/messaging.ts
|
|
523
|
+
var cached = null;
|
|
524
|
+
function firebaseMessaging() {
|
|
525
|
+
if (cached) return cached;
|
|
526
|
+
let mod;
|
|
527
|
+
try {
|
|
528
|
+
mod = __require("@react-native-firebase/messaging");
|
|
529
|
+
} catch (e) {
|
|
530
|
+
throw peerLoadFailed("@react-native-firebase/messaging", e);
|
|
531
|
+
}
|
|
532
|
+
const factory = resolveFactory2(mod);
|
|
533
|
+
if (!factory) {
|
|
534
|
+
throw peerUnexpectedShape(
|
|
535
|
+
"@react-native-firebase/messaging",
|
|
536
|
+
`expected a factory function, got ${describeShape2(mod)}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
cached = factory();
|
|
540
|
+
return cached;
|
|
541
|
+
}
|
|
542
|
+
function resolveFactory2(mod) {
|
|
543
|
+
if (typeof mod === "function") {
|
|
544
|
+
return mod;
|
|
545
|
+
}
|
|
546
|
+
if (mod && typeof mod === "object" && "default" in mod) {
|
|
547
|
+
const def = mod.default;
|
|
548
|
+
if (typeof def === "function") {
|
|
549
|
+
return def;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
function describeShape2(mod) {
|
|
555
|
+
if (mod === null) return "null";
|
|
556
|
+
if (typeof mod !== "object") return typeof mod;
|
|
557
|
+
const keys = Object.keys(mod).slice(0, 5);
|
|
558
|
+
return `object with keys [${keys.join(", ")}]`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/push/targetIdStore.ts
|
|
562
|
+
var KEY = "fanapps.pushTargetId";
|
|
563
|
+
var TargetIdStore = class {
|
|
564
|
+
constructor(storage) {
|
|
565
|
+
this.storage = storage;
|
|
566
|
+
}
|
|
567
|
+
get() {
|
|
568
|
+
return this.storage.getItem(KEY);
|
|
569
|
+
}
|
|
570
|
+
async set(id) {
|
|
571
|
+
await this.storage.setItem(KEY, id);
|
|
572
|
+
}
|
|
573
|
+
async clear() {
|
|
574
|
+
await this.storage.removeItem(KEY);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
function generateTargetId() {
|
|
578
|
+
const bytes = new Uint8Array(16);
|
|
579
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
580
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
581
|
+
} else {
|
|
582
|
+
for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
583
|
+
}
|
|
584
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/hooks/useRegisterDevice.ts
|
|
588
|
+
function useRegisterDevice() {
|
|
589
|
+
const { client, storage } = useFanapps();
|
|
590
|
+
const [targetId, setTargetId] = useState(null);
|
|
591
|
+
const [token, setToken] = useState(null);
|
|
592
|
+
const [target, setTarget] = useState(null);
|
|
593
|
+
const [isRegistering, setIsRegistering] = useState(false);
|
|
594
|
+
const [error, setError] = useState(null);
|
|
595
|
+
const targetStoreRef = useRef2(null);
|
|
596
|
+
if (!targetStoreRef.current) {
|
|
597
|
+
targetStoreRef.current = new TargetIdStore(storage);
|
|
598
|
+
}
|
|
599
|
+
const targetStore = targetStoreRef.current;
|
|
600
|
+
const tokenRefreshUnsubRef = useRef2(null);
|
|
601
|
+
useEffect2(() => {
|
|
602
|
+
return () => {
|
|
603
|
+
tokenRefreshUnsubRef.current?.();
|
|
604
|
+
tokenRefreshUnsubRef.current = null;
|
|
605
|
+
};
|
|
606
|
+
}, []);
|
|
607
|
+
const register = useCallback2(async () => {
|
|
608
|
+
setIsRegistering(true);
|
|
609
|
+
setError(null);
|
|
610
|
+
try {
|
|
611
|
+
const messaging = firebaseMessaging();
|
|
612
|
+
await messaging.requestPermission();
|
|
613
|
+
const fcmToken = await messaging.getToken();
|
|
614
|
+
setToken(fcmToken);
|
|
615
|
+
const existingId = await targetStore.get();
|
|
616
|
+
let result;
|
|
617
|
+
if (existingId) {
|
|
618
|
+
try {
|
|
619
|
+
result = await client.updatePushTarget(existingId, fcmToken);
|
|
620
|
+
} catch {
|
|
621
|
+
result = await client.createPushTarget({
|
|
622
|
+
targetId: existingId,
|
|
623
|
+
identifier: fcmToken,
|
|
624
|
+
osName: Platform2.OS === "ios" ? "iOS" : "Android"
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
const newId = generateTargetId();
|
|
629
|
+
result = await client.createPushTarget({
|
|
630
|
+
targetId: newId,
|
|
631
|
+
identifier: fcmToken,
|
|
632
|
+
osName: Platform2.OS === "ios" ? "iOS" : "Android"
|
|
633
|
+
});
|
|
634
|
+
await targetStore.set(result.id);
|
|
635
|
+
}
|
|
636
|
+
setTargetId(result.id);
|
|
637
|
+
setTarget(result);
|
|
638
|
+
if (!tokenRefreshUnsubRef.current) {
|
|
639
|
+
tokenRefreshUnsubRef.current = messaging.onTokenRefresh(async (newToken) => {
|
|
640
|
+
try {
|
|
641
|
+
const id = await targetStore.get();
|
|
642
|
+
if (!id) return;
|
|
643
|
+
const updated = await client.updatePushTarget(id, newToken);
|
|
644
|
+
setToken(newToken);
|
|
645
|
+
setTarget(updated);
|
|
646
|
+
} catch (e) {
|
|
647
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
} catch (e) {
|
|
653
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
654
|
+
setError(err);
|
|
655
|
+
throw err;
|
|
656
|
+
} finally {
|
|
657
|
+
setIsRegistering(false);
|
|
658
|
+
}
|
|
659
|
+
}, [client, targetStore]);
|
|
660
|
+
const unregister = useCallback2(async () => {
|
|
661
|
+
const id = await targetStore.get();
|
|
662
|
+
if (id) {
|
|
663
|
+
try {
|
|
664
|
+
await client.deletePushTarget(id);
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
await targetStore.clear();
|
|
668
|
+
}
|
|
669
|
+
tokenRefreshUnsubRef.current?.();
|
|
670
|
+
tokenRefreshUnsubRef.current = null;
|
|
671
|
+
setTargetId(null);
|
|
672
|
+
setToken(null);
|
|
673
|
+
setTarget(null);
|
|
674
|
+
}, [client, targetStore]);
|
|
675
|
+
return {
|
|
676
|
+
targetId,
|
|
677
|
+
token,
|
|
678
|
+
target,
|
|
679
|
+
isRegistering,
|
|
680
|
+
error,
|
|
681
|
+
register,
|
|
682
|
+
unregister
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/hooks/useTrivias.ts
|
|
687
|
+
import { useQuery as useQuery5 } from "@tanstack/react-query";
|
|
688
|
+
function useTrivias(options = {}) {
|
|
689
|
+
const { client, locale: providerLocale } = useFanapps();
|
|
690
|
+
const locale = options.locale ?? providerLocale;
|
|
691
|
+
return useQuery5({
|
|
692
|
+
queryKey: ["fanapps", "trivias", { locale }],
|
|
693
|
+
queryFn: async ({ signal }) => {
|
|
694
|
+
const response = await client.get(
|
|
695
|
+
"/api/trivias",
|
|
696
|
+
{ locale },
|
|
697
|
+
signal
|
|
698
|
+
);
|
|
699
|
+
return response.data;
|
|
700
|
+
},
|
|
701
|
+
enabled: options.enabled ?? true
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/hooks/useTrivia.ts
|
|
706
|
+
import { useQuery as useQuery6 } from "@tanstack/react-query";
|
|
707
|
+
function useTrivia(id, options = {}) {
|
|
708
|
+
const { client, locale: providerLocale } = useFanapps();
|
|
709
|
+
const locale = options.locale ?? providerLocale;
|
|
710
|
+
return useQuery6({
|
|
711
|
+
queryKey: ["fanapps", "trivia", id, { locale }],
|
|
712
|
+
queryFn: async ({ signal }) => {
|
|
713
|
+
const response = await client.get(
|
|
714
|
+
`/api/trivias/${id}`,
|
|
715
|
+
{ locale },
|
|
716
|
+
signal
|
|
717
|
+
);
|
|
718
|
+
return response.data;
|
|
719
|
+
},
|
|
720
|
+
enabled: (options.enabled ?? true) && id != null && id !== ""
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/hooks/useSubmitTriviaResponse.ts
|
|
725
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
726
|
+
function useSubmitTriviaResponse() {
|
|
727
|
+
const { client } = useFanapps();
|
|
728
|
+
const queryClient = useQueryClient();
|
|
729
|
+
return useMutation({
|
|
730
|
+
mutationFn: ({ triviaId, answerId }) => client.post(`/api/trivias/${triviaId}/respond`, {
|
|
731
|
+
answer_id: answerId
|
|
732
|
+
}),
|
|
733
|
+
onSuccess: (_result, variables) => {
|
|
734
|
+
queryClient.invalidateQueries({
|
|
735
|
+
queryKey: ["fanapps", "trivia", variables.triviaId]
|
|
736
|
+
});
|
|
737
|
+
queryClient.invalidateQueries({ queryKey: ["fanapps", "trivias"] });
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/hooks/useTopicSubscription.ts
|
|
743
|
+
import { useCallback as useCallback3 } from "react";
|
|
744
|
+
function useTopicSubscription() {
|
|
745
|
+
const subscribe = useCallback3(async (topic) => {
|
|
746
|
+
await firebaseMessaging().subscribeToTopic(topic);
|
|
747
|
+
}, []);
|
|
748
|
+
const unsubscribe = useCallback3(async (topic) => {
|
|
749
|
+
await firebaseMessaging().unsubscribeFromTopic(topic);
|
|
750
|
+
}, []);
|
|
751
|
+
return { subscribe, unsubscribe };
|
|
752
|
+
}
|
|
753
|
+
export {
|
|
754
|
+
FanappsAuthError,
|
|
755
|
+
FanappsClient,
|
|
756
|
+
FanappsError,
|
|
757
|
+
FanappsProvider,
|
|
758
|
+
useArticle,
|
|
759
|
+
useArticles,
|
|
760
|
+
useAuth,
|
|
761
|
+
useEntries,
|
|
762
|
+
useEntry,
|
|
763
|
+
useFanappsClient,
|
|
764
|
+
useRegisterDevice,
|
|
765
|
+
useSubmitTriviaResponse,
|
|
766
|
+
useTopicSubscription,
|
|
767
|
+
useTrivia,
|
|
768
|
+
useTrivias
|
|
769
|
+
};
|
|
770
|
+
//# sourceMappingURL=index.mjs.map
|