@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/dist/index.js ADDED
@@ -0,0 +1,804 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FanappsAuthError: () => FanappsAuthError,
24
+ FanappsClient: () => FanappsClient,
25
+ FanappsError: () => FanappsError,
26
+ FanappsProvider: () => FanappsProvider,
27
+ useArticle: () => useArticle,
28
+ useArticles: () => useArticles,
29
+ useAuth: () => useAuth,
30
+ useEntries: () => useEntries,
31
+ useEntry: () => useEntry,
32
+ useFanappsClient: () => useFanappsClient,
33
+ useRegisterDevice: () => useRegisterDevice,
34
+ useSubmitTriviaResponse: () => useSubmitTriviaResponse,
35
+ useTopicSubscription: () => useTopicSubscription,
36
+ useTrivia: () => useTrivia,
37
+ useTrivias: () => useTrivias
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/provider.tsx
42
+ var import_react_query = require("@tanstack/react-query");
43
+ var import_react2 = require("react");
44
+
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
+ // src/auth/firebaseApp.ts
68
+ var cachedAuth = null;
69
+ 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(", ")}]`;
106
+ }
107
+
108
+ // src/auth/authStore.ts
109
+ function mapUser(fb) {
110
+ if (!fb) return null;
111
+ return {
112
+ uid: fb.uid,
113
+ email: fb.email,
114
+ displayName: fb.displayName,
115
+ photoURL: fb.photoURL,
116
+ phoneNumber: fb.phoneNumber,
117
+ isAnonymous: fb.isAnonymous,
118
+ providerId: fb.providerId ?? null
119
+ };
120
+ }
121
+ var AuthStore = class {
122
+ constructor() {
123
+ this.snapshot = {
124
+ user: null,
125
+ firebaseUser: null,
126
+ status: "loading"
127
+ };
128
+ this.listeners = /* @__PURE__ */ new Set();
129
+ this.unsubscribe = null;
130
+ }
131
+ start() {
132
+ if (this.unsubscribe) return;
133
+ this.unsubscribe = firebaseAuth().onAuthStateChanged((fb) => {
134
+ this.snapshot = {
135
+ user: mapUser(fb),
136
+ firebaseUser: fb,
137
+ status: fb ? "signed-in" : "signed-out"
138
+ };
139
+ this.emit();
140
+ });
141
+ }
142
+ stop() {
143
+ this.unsubscribe?.();
144
+ this.unsubscribe = null;
145
+ }
146
+ subscribe(listener) {
147
+ this.listeners.add(listener);
148
+ return () => {
149
+ this.listeners.delete(listener);
150
+ };
151
+ }
152
+ getSnapshot() {
153
+ return this.snapshot;
154
+ }
155
+ async getCurrentIdToken() {
156
+ const user = firebaseAuth().currentUser;
157
+ if (!user) return null;
158
+ return user.getIdToken();
159
+ }
160
+ emit() {
161
+ for (const l of this.listeners) l(this.snapshot);
162
+ }
163
+ };
164
+
165
+ // src/errors.ts
166
+ var FanappsError = class extends Error {
167
+ constructor(message, status, body) {
168
+ super(message);
169
+ this.name = "FanappsError";
170
+ this.status = status;
171
+ this.body = body;
172
+ }
173
+ };
174
+
175
+ // src/client.ts
176
+ var FanappsClient = class {
177
+ constructor(options) {
178
+ this.apiKey = options.apiKey;
179
+ this.project = options.project;
180
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
181
+ this.getIdToken = options.getIdToken;
182
+ }
183
+ get(path, params, signal) {
184
+ return this.request("GET", path, { params, signal });
185
+ }
186
+ post(path, body, signal) {
187
+ return this.request("POST", path, { body, signal });
188
+ }
189
+ patch(path, body, signal) {
190
+ return this.request("PATCH", path, { body, signal });
191
+ }
192
+ delete(path, signal) {
193
+ return this.request("DELETE", path, { signal });
194
+ }
195
+ async createPushTarget(input) {
196
+ const response = await this.post(
197
+ "/api/account/targets/push",
198
+ input
199
+ );
200
+ return response.data;
201
+ }
202
+ async updatePushTarget(targetId, identifier) {
203
+ const response = await this.patch(
204
+ `/api/account/targets/push/${encodeURIComponent(targetId)}`,
205
+ { identifier }
206
+ );
207
+ return response.data;
208
+ }
209
+ async deletePushTarget(targetId) {
210
+ await this.delete(`/api/account/targets/push/${encodeURIComponent(targetId)}`);
211
+ }
212
+ async request(method, path, opts) {
213
+ const url = this.buildUrl(path, opts.params);
214
+ const headers = {
215
+ Authorization: `Bearer ${this.apiKey}`,
216
+ "X-Tenant": this.project,
217
+ Accept: "application/json"
218
+ };
219
+ if (this.getIdToken) {
220
+ const idToken = await this.getIdToken();
221
+ if (idToken) {
222
+ headers["X-Firebase-Token"] = idToken;
223
+ }
224
+ }
225
+ const init = { method, headers, signal: opts.signal };
226
+ if (opts.body !== void 0) {
227
+ headers["Content-Type"] = "application/json";
228
+ init.body = JSON.stringify(opts.body);
229
+ }
230
+ const response = await fetch(url, init);
231
+ const raw = await response.text();
232
+ const payload = raw.length > 0 ? safeJson(raw) : null;
233
+ if (!response.ok) {
234
+ const message = (payload && typeof payload === "object" && "message" in payload ? String(payload.message) : null) ?? `Fanapps request failed with status ${response.status}`;
235
+ throw new FanappsError(message, response.status, payload);
236
+ }
237
+ return payload;
238
+ }
239
+ buildUrl(path, params) {
240
+ const base = path.startsWith("/") ? `${this.baseUrl}${path}` : `${this.baseUrl}/${path}`;
241
+ if (!params) return base;
242
+ const query = serializeParams(params);
243
+ return query.length > 0 ? `${base}?${query}` : base;
244
+ }
245
+ };
246
+ function serializeParams(params) {
247
+ const parts = [];
248
+ for (const [key, value] of Object.entries(params)) {
249
+ if (value === void 0 || value === null) continue;
250
+ if (Array.isArray(value)) {
251
+ for (const item of value) {
252
+ if (item === void 0 || item === null) continue;
253
+ parts.push(`${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`);
254
+ }
255
+ continue;
256
+ }
257
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
258
+ }
259
+ return parts.join("&");
260
+ }
261
+ function safeJson(raw) {
262
+ try {
263
+ return JSON.parse(raw);
264
+ } catch {
265
+ return raw;
266
+ }
267
+ }
268
+
269
+ // src/context.ts
270
+ var import_react = require("react");
271
+ var FanappsContext = (0, import_react.createContext)(null);
272
+ function useFanapps() {
273
+ const ctx = (0, import_react.useContext)(FanappsContext);
274
+ if (!ctx) {
275
+ throw new Error(
276
+ "@fanapps/react-native: useFanapps must be used inside <FanappsProvider>."
277
+ );
278
+ }
279
+ return ctx;
280
+ }
281
+
282
+ // src/push/storage.ts
283
+ var memoryFallbackWarned = false;
284
+ 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
+ };
312
+ }
313
+
314
+ // src/provider.tsx
315
+ var import_jsx_runtime = require("react/jsx-runtime");
316
+ var DEFAULT_BASE_URL = "https://fanapps.app";
317
+ function FanappsProvider({
318
+ apiKey,
319
+ project,
320
+ locale,
321
+ baseUrl = DEFAULT_BASE_URL,
322
+ queryClient,
323
+ storage,
324
+ children
325
+ }) {
326
+ const authStoreRef = (0, import_react2.useRef)(null);
327
+ if (!authStoreRef.current) {
328
+ authStoreRef.current = new AuthStore();
329
+ }
330
+ const authStore = authStoreRef.current;
331
+ (0, import_react2.useEffect)(() => {
332
+ authStore.start();
333
+ return () => authStore.stop();
334
+ }, [authStore]);
335
+ const resolvedStorage = (0, import_react2.useMemo)(() => resolveStorage(storage), [storage]);
336
+ const client = (0, import_react2.useMemo)(
337
+ () => new FanappsClient({
338
+ apiKey,
339
+ project,
340
+ baseUrl,
341
+ getIdToken: () => authStore.getCurrentIdToken()
342
+ }),
343
+ [apiKey, project, baseUrl, authStore]
344
+ );
345
+ const resolvedQueryClient = (0, import_react2.useMemo)(
346
+ () => queryClient ?? new import_react_query.QueryClient(),
347
+ [queryClient]
348
+ );
349
+ const value = (0, import_react2.useMemo)(
350
+ () => ({ client, authStore, storage: resolvedStorage, locale }),
351
+ [client, authStore, resolvedStorage, locale]
352
+ );
353
+ 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
+ }
355
+
356
+ // src/hooks/useFanappsClient.ts
357
+ function useFanappsClient() {
358
+ return useFanapps().client;
359
+ }
360
+
361
+ // src/hooks/useAuth.ts
362
+ var import_react3 = require("react");
363
+
364
+ // src/auth/providers/google.ts
365
+ 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();
380
+ const idToken = "data" in result ? result.data?.idToken ?? null : result.idToken;
381
+ if (!idToken) {
382
+ throw new Error("Google sign-in returned no ID token.");
383
+ }
384
+ const credential = authMod.GoogleAuthProvider.credential(idToken);
385
+ return firebaseAuth().signInWithCredential(
386
+ credential
387
+ );
388
+ }
389
+
390
+ // src/auth/providers/apple.ts
391
+ var import_react_native = require("react-native");
392
+ async function signInWithApple() {
393
+ if (import_react_native.Platform.OS !== "ios") {
394
+ throw new FanappsAuthError(
395
+ "Apple sign-in is only supported on iOS in v1. Use web OAuth on Android.",
396
+ "auth/platform-not-supported"
397
+ );
398
+ }
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");
413
+ }
414
+ const result = await mod.appleAuth.performRequest({
415
+ requestedOperation: mod.appleAuth.Operation.LOGIN,
416
+ requestedScopes: [mod.appleAuth.Scope.EMAIL, mod.appleAuth.Scope.FULL_NAME]
417
+ });
418
+ if (!result.identityToken) {
419
+ throw new FanappsAuthError("Apple did not return an identity token.", "auth/no-token");
420
+ }
421
+ const credential = authMod.AppleAuthProvider.credential(
422
+ result.identityToken,
423
+ result.nonce ?? void 0
424
+ );
425
+ return firebaseAuth().signInWithCredential(
426
+ credential
427
+ );
428
+ }
429
+
430
+ // src/hooks/useAuth.ts
431
+ function useAuth() {
432
+ const { authStore } = useFanapps();
433
+ const snapshot = (0, import_react3.useSyncExternalStore)(
434
+ (listener) => authStore.subscribe(listener),
435
+ () => authStore.getSnapshot(),
436
+ () => authStore.getSnapshot()
437
+ );
438
+ const signInWithEmail = (0, import_react3.useCallback)(async (email, password) => {
439
+ const cred = await firebaseAuth().signInWithEmailAndPassword(email, password);
440
+ return authStore.getSnapshot().user ?? mapMinimal(cred.user);
441
+ }, [authStore]);
442
+ const signUpWithEmail = (0, import_react3.useCallback)(async (email, password) => {
443
+ const cred = await firebaseAuth().createUserWithEmailAndPassword(email, password);
444
+ return authStore.getSnapshot().user ?? mapMinimal(cred.user);
445
+ }, [authStore]);
446
+ const sendPasswordResetEmail = (0, import_react3.useCallback)(async (email) => {
447
+ await firebaseAuth().sendPasswordResetEmail(email);
448
+ }, []);
449
+ const sendEmailVerification = (0, import_react3.useCallback)(async () => {
450
+ const current = firebaseAuth().currentUser;
451
+ if (!current) throw new Error("No signed-in user to verify.");
452
+ await current.sendEmailVerification();
453
+ }, []);
454
+ const signInWithGoogle2 = (0, import_react3.useCallback)(async () => {
455
+ const cred = await signInWithGoogle();
456
+ return authStore.getSnapshot().user ?? mapMinimal(cred.user);
457
+ }, [authStore]);
458
+ const signInWithApple2 = (0, import_react3.useCallback)(async () => {
459
+ const cred = await signInWithApple();
460
+ return authStore.getSnapshot().user ?? mapMinimal(cred.user);
461
+ }, [authStore]);
462
+ const signOut = (0, import_react3.useCallback)(async () => {
463
+ await firebaseAuth().signOut();
464
+ }, []);
465
+ return {
466
+ user: snapshot.user,
467
+ status: snapshot.status,
468
+ signInWithEmail,
469
+ signUpWithEmail,
470
+ sendPasswordResetEmail,
471
+ sendEmailVerification,
472
+ signInWithGoogle: signInWithGoogle2,
473
+ signInWithApple: signInWithApple2,
474
+ signOut
475
+ };
476
+ }
477
+ function mapMinimal(fb) {
478
+ return {
479
+ uid: fb.uid,
480
+ email: fb.email,
481
+ displayName: fb.displayName,
482
+ photoURL: fb.photoURL,
483
+ phoneNumber: fb.phoneNumber,
484
+ isAnonymous: fb.isAnonymous,
485
+ providerId: fb.providerId ?? null
486
+ };
487
+ }
488
+
489
+ // src/hooks/useEntries.ts
490
+ var import_react_query2 = require("@tanstack/react-query");
491
+ function useEntries(filters = {}, options = {}) {
492
+ const { client, locale: providerLocale } = useFanapps();
493
+ const locale = options.locale ?? providerLocale;
494
+ return (0, import_react_query2.useQuery)({
495
+ queryKey: ["fanapps", "entries", { locale, ...filters }],
496
+ queryFn: ({ signal }) => client.get("/api/entries", { ...filters, locale }, signal),
497
+ enabled: options.enabled ?? true
498
+ });
499
+ }
500
+
501
+ // src/hooks/useEntry.ts
502
+ var import_react_query3 = require("@tanstack/react-query");
503
+ function useEntry(id, options = {}) {
504
+ const { client, locale: providerLocale } = useFanapps();
505
+ const locale = options.locale ?? providerLocale;
506
+ return (0, import_react_query3.useQuery)({
507
+ queryKey: ["fanapps", "entry", id, { locale }],
508
+ queryFn: async ({ signal }) => {
509
+ const response = await client.get(
510
+ `/api/entries/${id}`,
511
+ { locale },
512
+ signal
513
+ );
514
+ return response.data;
515
+ },
516
+ enabled: (options.enabled ?? true) && id != null && id !== ""
517
+ });
518
+ }
519
+
520
+ // src/hooks/useArticles.ts
521
+ var import_react_query4 = require("@tanstack/react-query");
522
+ function useArticles(filters = {}, options = {}) {
523
+ const { client, locale: providerLocale } = useFanapps();
524
+ const locale = options.locale ?? providerLocale;
525
+ return (0, import_react_query4.useQuery)({
526
+ queryKey: ["fanapps", "articles", { locale, ...filters }],
527
+ queryFn: ({ signal }) => client.get("/api/articles", { ...filters, locale }, signal),
528
+ enabled: options.enabled ?? true
529
+ });
530
+ }
531
+
532
+ // src/hooks/useArticle.ts
533
+ var import_react_query5 = require("@tanstack/react-query");
534
+ function useArticle(id, options = {}) {
535
+ const { client, locale: providerLocale } = useFanapps();
536
+ const locale = options.locale ?? providerLocale;
537
+ return (0, import_react_query5.useQuery)({
538
+ queryKey: ["fanapps", "article", id, { locale }],
539
+ queryFn: async ({ signal }) => {
540
+ const response = await client.get(
541
+ `/api/articles/${id}`,
542
+ { locale },
543
+ signal
544
+ );
545
+ return response.data;
546
+ },
547
+ enabled: (options.enabled ?? true) && id != null && id !== ""
548
+ });
549
+ }
550
+
551
+ // src/hooks/useRegisterDevice.ts
552
+ var import_react4 = require("react");
553
+ var import_react_native2 = require("react-native");
554
+
555
+ // src/push/messaging.ts
556
+ var cached = null;
557
+ 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(", ")}]`;
592
+ }
593
+
594
+ // src/push/targetIdStore.ts
595
+ var KEY = "fanapps.pushTargetId";
596
+ var TargetIdStore = class {
597
+ constructor(storage) {
598
+ this.storage = storage;
599
+ }
600
+ get() {
601
+ return this.storage.getItem(KEY);
602
+ }
603
+ async set(id) {
604
+ await this.storage.setItem(KEY, id);
605
+ }
606
+ async clear() {
607
+ await this.storage.removeItem(KEY);
608
+ }
609
+ };
610
+ function generateTargetId() {
611
+ const bytes = new Uint8Array(16);
612
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
613
+ globalThis.crypto.getRandomValues(bytes);
614
+ } else {
615
+ for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
616
+ }
617
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
618
+ }
619
+
620
+ // src/hooks/useRegisterDevice.ts
621
+ function useRegisterDevice() {
622
+ const { client, storage } = useFanapps();
623
+ const [targetId, setTargetId] = (0, import_react4.useState)(null);
624
+ const [token, setToken] = (0, import_react4.useState)(null);
625
+ const [target, setTarget] = (0, import_react4.useState)(null);
626
+ const [isRegistering, setIsRegistering] = (0, import_react4.useState)(false);
627
+ const [error, setError] = (0, import_react4.useState)(null);
628
+ const targetStoreRef = (0, import_react4.useRef)(null);
629
+ if (!targetStoreRef.current) {
630
+ targetStoreRef.current = new TargetIdStore(storage);
631
+ }
632
+ const targetStore = targetStoreRef.current;
633
+ const tokenRefreshUnsubRef = (0, import_react4.useRef)(null);
634
+ (0, import_react4.useEffect)(() => {
635
+ return () => {
636
+ tokenRefreshUnsubRef.current?.();
637
+ tokenRefreshUnsubRef.current = null;
638
+ };
639
+ }, []);
640
+ const register = (0, import_react4.useCallback)(async () => {
641
+ setIsRegistering(true);
642
+ setError(null);
643
+ try {
644
+ const messaging = firebaseMessaging();
645
+ await messaging.requestPermission();
646
+ const fcmToken = await messaging.getToken();
647
+ setToken(fcmToken);
648
+ const existingId = await targetStore.get();
649
+ let result;
650
+ if (existingId) {
651
+ try {
652
+ result = await client.updatePushTarget(existingId, fcmToken);
653
+ } catch {
654
+ result = await client.createPushTarget({
655
+ targetId: existingId,
656
+ identifier: fcmToken,
657
+ osName: import_react_native2.Platform.OS === "ios" ? "iOS" : "Android"
658
+ });
659
+ }
660
+ } else {
661
+ const newId = generateTargetId();
662
+ result = await client.createPushTarget({
663
+ targetId: newId,
664
+ identifier: fcmToken,
665
+ osName: import_react_native2.Platform.OS === "ios" ? "iOS" : "Android"
666
+ });
667
+ await targetStore.set(result.id);
668
+ }
669
+ setTargetId(result.id);
670
+ setTarget(result);
671
+ if (!tokenRefreshUnsubRef.current) {
672
+ tokenRefreshUnsubRef.current = messaging.onTokenRefresh(async (newToken) => {
673
+ try {
674
+ const id = await targetStore.get();
675
+ if (!id) return;
676
+ const updated = await client.updatePushTarget(id, newToken);
677
+ setToken(newToken);
678
+ setTarget(updated);
679
+ } catch (e) {
680
+ setError(e instanceof Error ? e : new Error(String(e)));
681
+ }
682
+ });
683
+ }
684
+ return result;
685
+ } catch (e) {
686
+ const err = e instanceof Error ? e : new Error(String(e));
687
+ setError(err);
688
+ throw err;
689
+ } finally {
690
+ setIsRegistering(false);
691
+ }
692
+ }, [client, targetStore]);
693
+ const unregister = (0, import_react4.useCallback)(async () => {
694
+ const id = await targetStore.get();
695
+ if (id) {
696
+ try {
697
+ await client.deletePushTarget(id);
698
+ } catch {
699
+ }
700
+ await targetStore.clear();
701
+ }
702
+ tokenRefreshUnsubRef.current?.();
703
+ tokenRefreshUnsubRef.current = null;
704
+ setTargetId(null);
705
+ setToken(null);
706
+ setTarget(null);
707
+ }, [client, targetStore]);
708
+ return {
709
+ targetId,
710
+ token,
711
+ target,
712
+ isRegistering,
713
+ error,
714
+ register,
715
+ unregister
716
+ };
717
+ }
718
+
719
+ // src/hooks/useTrivias.ts
720
+ var import_react_query6 = require("@tanstack/react-query");
721
+ function useTrivias(options = {}) {
722
+ const { client, locale: providerLocale } = useFanapps();
723
+ const locale = options.locale ?? providerLocale;
724
+ return (0, import_react_query6.useQuery)({
725
+ queryKey: ["fanapps", "trivias", { locale }],
726
+ queryFn: async ({ signal }) => {
727
+ const response = await client.get(
728
+ "/api/trivias",
729
+ { locale },
730
+ signal
731
+ );
732
+ return response.data;
733
+ },
734
+ enabled: options.enabled ?? true
735
+ });
736
+ }
737
+
738
+ // src/hooks/useTrivia.ts
739
+ var import_react_query7 = require("@tanstack/react-query");
740
+ function useTrivia(id, options = {}) {
741
+ const { client, locale: providerLocale } = useFanapps();
742
+ const locale = options.locale ?? providerLocale;
743
+ return (0, import_react_query7.useQuery)({
744
+ queryKey: ["fanapps", "trivia", id, { locale }],
745
+ queryFn: async ({ signal }) => {
746
+ const response = await client.get(
747
+ `/api/trivias/${id}`,
748
+ { locale },
749
+ signal
750
+ );
751
+ return response.data;
752
+ },
753
+ enabled: (options.enabled ?? true) && id != null && id !== ""
754
+ });
755
+ }
756
+
757
+ // src/hooks/useSubmitTriviaResponse.ts
758
+ var import_react_query8 = require("@tanstack/react-query");
759
+ function useSubmitTriviaResponse() {
760
+ const { client } = useFanapps();
761
+ const queryClient = (0, import_react_query8.useQueryClient)();
762
+ return (0, import_react_query8.useMutation)({
763
+ mutationFn: ({ triviaId, answerId }) => client.post(`/api/trivias/${triviaId}/respond`, {
764
+ answer_id: answerId
765
+ }),
766
+ onSuccess: (_result, variables) => {
767
+ queryClient.invalidateQueries({
768
+ queryKey: ["fanapps", "trivia", variables.triviaId]
769
+ });
770
+ queryClient.invalidateQueries({ queryKey: ["fanapps", "trivias"] });
771
+ }
772
+ });
773
+ }
774
+
775
+ // src/hooks/useTopicSubscription.ts
776
+ var import_react5 = require("react");
777
+ function useTopicSubscription() {
778
+ const subscribe = (0, import_react5.useCallback)(async (topic) => {
779
+ await firebaseMessaging().subscribeToTopic(topic);
780
+ }, []);
781
+ const unsubscribe = (0, import_react5.useCallback)(async (topic) => {
782
+ await firebaseMessaging().unsubscribeFromTopic(topic);
783
+ }, []);
784
+ return { subscribe, unsubscribe };
785
+ }
786
+ // Annotate the CommonJS export names for ESM import in node:
787
+ 0 && (module.exports = {
788
+ FanappsAuthError,
789
+ FanappsClient,
790
+ FanappsError,
791
+ FanappsProvider,
792
+ useArticle,
793
+ useArticles,
794
+ useAuth,
795
+ useEntries,
796
+ useEntry,
797
+ useFanappsClient,
798
+ useRegisterDevice,
799
+ useSubmitTriviaResponse,
800
+ useTopicSubscription,
801
+ useTrivia,
802
+ useTrivias
803
+ });
804
+ //# sourceMappingURL=index.js.map