react-achievements 4.4.1 → 5.0.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/web.esm.js CHANGED
@@ -46,6 +46,16 @@ function __rest(s, e) {
46
46
  return t;
47
47
  }
48
48
 
49
+ function __awaiter(thisArg, _arguments, P, generator) {
50
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
51
+ return new (P || (P = Promise))(function (resolve, reject) {
52
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
53
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
54
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
55
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
56
+ });
57
+ }
58
+
49
59
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
60
  var e = new Error(message);
51
61
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
@@ -67,18 +77,74 @@ const AchievementContext = createContext(undefined);
67
77
  const getAllAchievementRecord = (achievements) => {
68
78
  return Object.fromEntries(achievements.map((achievement) => [achievement.achievementId, achievement]));
69
79
  };
70
- const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'local', children, onError, useBuiltInUI, restApiConfig, engine: externalEngine, eventMapping, icons = {}, }) => {
80
+ const emptySnapshot = () => ({
81
+ metrics: {},
82
+ unlockedIds: [],
83
+ unlockedAchievements: [],
84
+ allAchievements: [],
85
+ unlockedCount: 0,
86
+ totalCount: 0,
87
+ });
88
+ const normalizeAchievementDto = (achievement) => {
89
+ const source = achievement;
90
+ const achievementId = source.id || source.achievementId || '';
91
+ const achievementTitle = source.title || source.achievementTitle || achievementId;
92
+ const achievementDescription = source.description || source.achievementDescription || '';
93
+ const achievementIconKey = source.iconKey || source.icon || source.achievementIconKey;
94
+ return {
95
+ achievementId,
96
+ achievementTitle,
97
+ achievementDescription,
98
+ achievementIconKey,
99
+ confetti: source.confetti,
100
+ isUnlocked: Boolean(source.isUnlocked),
101
+ unlockedAt: source.unlockedAt,
102
+ progress: source.progress,
103
+ metadata: source.metadata,
104
+ };
105
+ };
106
+ const normalizeClientSnapshot = (snapshot) => {
107
+ var _a, _b, _c;
108
+ const allAchievements = (snapshot.achievements || []).map(normalizeAchievementDto);
109
+ const unlockedIds = snapshot.unlockedIds ||
110
+ allAchievements
111
+ .filter((achievement) => achievement.isUnlocked)
112
+ .map((achievement) => achievement.achievementId);
113
+ const unlockedIdSet = new Set(unlockedIds);
114
+ const unlockedAchievements = ((_a = snapshot.unlockedAchievements) === null || _a === void 0 ? void 0 : _a.length)
115
+ ? snapshot.unlockedAchievements.map(normalizeAchievementDto)
116
+ : allAchievements.filter((achievement) => unlockedIdSet.has(achievement.achievementId));
117
+ return {
118
+ metrics: Object.assign({}, (snapshot.metrics || {})),
119
+ unlockedIds,
120
+ unlockedAchievements,
121
+ allAchievements,
122
+ unlockedCount: (_b = snapshot.unlockedCount) !== null && _b !== void 0 ? _b : unlockedAchievements.length,
123
+ totalCount: (_c = snapshot.totalCount) !== null && _c !== void 0 ? _c : allAchievements.length,
124
+ };
125
+ };
126
+ const toError = (unknownError, fallbackMessage) => (unknownError instanceof Error ? unknownError : new Error(fallbackMessage));
127
+ const AchievementProvider$1 = ({ client, achievements: achievementsConfig, storage = 'local', children, onError, useBuiltInUI, restApiConfig, engine: externalEngine, eventMapping, icons = {}, }) => {
71
128
  if (useBuiltInUI !== undefined) {
72
129
  warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in 5.0.');
73
130
  }
131
+ if (client && (achievementsConfig || externalEngine)) {
132
+ throw new Error('Cannot provide "client" with "achievements" or "engine" props to AchievementProvider.\n\n' +
133
+ 'Use one pattern:\n' +
134
+ '1. Server-backed display: <AchievementProvider client={achievementClient}>\n' +
135
+ '2. Legacy in-browser engine: <AchievementProvider achievements={config}>');
136
+ }
74
137
  if (achievementsConfig && externalEngine) {
75
138
  throw new Error('Cannot provide both "achievements" and "engine" props to AchievementProvider.\n\n' +
76
139
  'Choose one pattern:\n' +
77
140
  '1. Direct metric tracking: <AchievementProvider achievements={config}>\n' +
78
141
  '2. Event-based tracking: <AchievementProvider engine={myEngine}>');
79
142
  }
80
- const isProviderCreatedEngine = Boolean(achievementsConfig);
143
+ const isProviderCreatedEngine = Boolean(achievementsConfig) && !client;
81
144
  const [engine] = useState(() => {
145
+ if (client) {
146
+ return undefined;
147
+ }
82
148
  if (externalEngine) {
83
149
  return externalEngine;
84
150
  }
@@ -95,18 +161,63 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
95
161
  eventMapping,
96
162
  });
97
163
  });
98
- const [achievementSnapshot, setAchievementSnapshot] = useState(() => engine.getSnapshot());
164
+ const [achievementSnapshot, setAchievementSnapshot] = useState(() => engine ? engine.getSnapshot() : emptySnapshot());
165
+ const [recentlyUnlocked, setRecentlyUnlocked] = useState([]);
166
+ const [isLoading, setIsLoading] = useState(Boolean(client));
167
+ const [error, setError] = useState(null);
99
168
  const syncAchievementState = useCallback((snapshot) => {
100
- setAchievementSnapshot(snapshot || engine.getSnapshot());
169
+ if (snapshot) {
170
+ setAchievementSnapshot(snapshot);
171
+ return;
172
+ }
173
+ if (engine) {
174
+ setAchievementSnapshot(engine.getSnapshot());
175
+ }
101
176
  }, [engine]);
177
+ const applyClientMutationResult = useCallback((result) => {
178
+ setRecentlyUnlocked((result.newlyUnlocked || []).map(normalizeAchievementDto));
179
+ const normalizedSnapshot = normalizeClientSnapshot(result.snapshot);
180
+ setAchievementSnapshot(normalizedSnapshot);
181
+ return result;
182
+ }, []);
183
+ const refresh = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
184
+ if (!client) {
185
+ const snapshot = engine ? engine.getSnapshot() : emptySnapshot();
186
+ setAchievementSnapshot(snapshot);
187
+ return snapshot;
188
+ }
189
+ setIsLoading(true);
190
+ setError(null);
191
+ try {
192
+ const snapshot = normalizeClientSnapshot(yield client.getSnapshot());
193
+ setAchievementSnapshot(snapshot);
194
+ return snapshot;
195
+ }
196
+ catch (unknownError) {
197
+ const nextError = toError(unknownError, 'Failed to load achievements');
198
+ setError(nextError);
199
+ throw nextError;
200
+ }
201
+ finally {
202
+ setIsLoading(false);
203
+ }
204
+ }), [client, engine]);
205
+ const handleClientMutationFailure = useCallback((unknownError) => {
206
+ const nextError = toError(unknownError, 'Failed to update achievements');
207
+ setError(nextError);
208
+ throw nextError;
209
+ }, []);
102
210
  useEffect(() => {
103
211
  return () => {
104
- if (!externalEngine) {
212
+ if (engine && !externalEngine) {
105
213
  engine.destroy();
106
214
  }
107
215
  };
108
216
  }, [engine, externalEngine]);
109
217
  useEffect(() => {
218
+ if (!engine) {
219
+ return;
220
+ }
110
221
  let isMounted = true;
111
222
  const unsubscribeStateChanged = engine.on('state:changed', (event) => {
112
223
  syncAchievementState(event);
@@ -121,29 +232,84 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
121
232
  unsubscribeStateChanged();
122
233
  };
123
234
  }, [engine, syncAchievementState]);
235
+ useEffect(() => {
236
+ if (!client) {
237
+ return;
238
+ }
239
+ refresh().catch(() => {
240
+ });
241
+ return () => {
242
+ };
243
+ }, [client, refresh]);
124
244
  const update = (newMetrics) => {
125
- engine.update(newMetrics);
245
+ if (client) {
246
+ setError(null);
247
+ const mutation = client.trackMany
248
+ ? client.trackMany(newMetrics)
249
+ : Promise.all(Object.entries(newMetrics).map(([metric, value]) => client.track(metric, value))).then((results) => results[results.length - 1]);
250
+ return mutation.then((result) => {
251
+ if (result) {
252
+ applyClientMutationResult(result);
253
+ }
254
+ return result;
255
+ }).catch(handleClientMutationFailure);
256
+ }
257
+ return engine === null || engine === void 0 ? void 0 : engine.update(newMetrics);
258
+ };
259
+ const increment = (metric, amount = 1) => {
260
+ if (client) {
261
+ setError(null);
262
+ return client.increment(metric, amount)
263
+ .then(applyClientMutationResult)
264
+ .catch(handleClientMutationFailure);
265
+ }
266
+ return engine === null || engine === void 0 ? void 0 : engine.increment(metric, amount);
267
+ };
268
+ const event = (name, payload) => {
269
+ if (client) {
270
+ setError(null);
271
+ return client.event(name, payload)
272
+ .then(applyClientMutationResult)
273
+ .catch(handleClientMutationFailure);
274
+ }
275
+ engine === null || engine === void 0 ? void 0 : engine.emit(name, payload);
126
276
  };
127
277
  const reset = () => {
128
- engine.reset();
278
+ if (client === null || client === void 0 ? void 0 : client.reset) {
279
+ setError(null);
280
+ return client.reset().then((snapshot) => {
281
+ setRecentlyUnlocked([]);
282
+ setAchievementSnapshot(normalizeClientSnapshot(snapshot));
283
+ }).catch(handleClientMutationFailure);
284
+ }
285
+ if (client) {
286
+ return refresh().then(() => undefined);
287
+ }
288
+ engine === null || engine === void 0 ? void 0 : engine.reset();
129
289
  };
130
290
  const getState = () => {
131
- const snapshot = engine.getSnapshot();
291
+ const snapshot = engine ? engine.getSnapshot() : achievementSnapshot;
132
292
  return {
133
293
  metrics: snapshot.metrics,
134
294
  unlocked: snapshot.unlockedIds,
135
295
  };
136
296
  };
137
297
  const exportData = () => {
138
- return engine.export();
298
+ return engine ? engine.export() : JSON.stringify(achievementSnapshot);
139
299
  };
140
300
  const importData = (jsonString, options) => {
301
+ if (!engine) {
302
+ return {
303
+ success: false,
304
+ errors: ['importData is not supported by remote achievement clients. Import on the server instead.'],
305
+ };
306
+ }
141
307
  const result = engine.import(jsonString, options);
142
308
  syncAchievementState();
143
309
  return result;
144
310
  };
145
311
  const getAllAchievements = () => {
146
- return engine.getSnapshot().allAchievements;
312
+ return engine ? engine.getSnapshot().allAchievements : achievementSnapshot.allAchievements;
147
313
  };
148
314
  const achievements = {
149
315
  unlocked: achievementSnapshot.unlockedIds,
@@ -151,6 +317,9 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
151
317
  };
152
318
  return (React.createElement(AchievementContext.Provider, { value: {
153
319
  update,
320
+ increment,
321
+ event,
322
+ refresh,
154
323
  achievements,
155
324
  snapshot: achievementSnapshot,
156
325
  reset,
@@ -159,27 +328,21 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
159
328
  importData,
160
329
  getAllAchievements,
161
330
  engine,
331
+ client,
162
332
  icons,
333
+ recentlyUnlocked,
334
+ isLoading,
335
+ error,
163
336
  _isLegacyPattern: isProviderCreatedEngine,
164
337
  } }, children));
165
338
  };
166
339
 
167
- /**
168
- * Access the active AchievementEngine instance.
169
- *
170
- * In v4 this works with both provider-created engines (`achievements` prop) and
171
- * injected engines (`engine` prop).
172
- */
173
- const useAchievementEngine = () => {
340
+ const useAchievements = () => {
174
341
  const context = useContext(AchievementContext);
175
342
  if (!context) {
176
- throw new Error('useAchievementEngine must be used within an AchievementProvider.\n\n' +
177
- 'Wrap your component tree:\n' +
178
- '<AchievementProvider achievements={achievements}>\n' +
179
- ' <YourComponent />\n' +
180
- '</AchievementProvider>');
343
+ throw new Error('useAchievements must be used within an AchievementProvider');
181
344
  }
182
- return context.engine;
345
+ return context;
183
346
  };
184
347
 
185
348
  /**
@@ -557,8 +720,9 @@ const AchievementUIContext = createContext({
557
720
  });
558
721
  const AchievementEffects = ({ icons, ui, achievementConfetti }) => {
559
722
  var _a, _b;
560
- const engine = useAchievementEngine();
561
- const seenAchievementsRef = useRef(new Set(engine.getUnlocked()));
723
+ const achievementContext = useAchievements();
724
+ const engine = achievementContext.engine;
725
+ const seenAchievementsRef = useRef(new Set(achievementContext.snapshot.unlockedIds));
562
726
  const confettiTimerRef = useRef(null);
563
727
  const achievementConfettiRef = useRef(achievementConfetti);
564
728
  const [showConfetti, setShowConfetti] = useState(false);
@@ -587,42 +751,47 @@ const AchievementEffects = ({ icons, ui, achievementConfetti }) => {
587
751
  setActiveConfettiConfig(null);
588
752
  }
589
753
  }, [ui.enableConfetti]);
754
+ const showUnlockedAchievement = useCallback((unlockedAchievement) => {
755
+ var _a, _b;
756
+ if (seenAchievementsRef.current.has(unlockedAchievement.achievementId)) {
757
+ return;
758
+ }
759
+ seenAchievementsRef.current.add(unlockedAchievement.achievementId);
760
+ if (ui.enableNotifications !== false) {
761
+ setNotifications((currentNotifications) => {
762
+ if (currentNotifications.some((notification) => notification.achievementId === unlockedAchievement.achievementId)) {
763
+ return currentNotifications;
764
+ }
765
+ return [...currentNotifications, unlockedAchievement];
766
+ });
767
+ }
768
+ const rewardConfetti = (_a = unlockedAchievement.confetti) !== null && _a !== void 0 ? _a : achievementConfettiRef.current.get(unlockedAchievement.achievementId);
769
+ if (ui.enableConfetti !== false && rewardConfetti !== false) {
770
+ if (confettiTimerRef.current) {
771
+ clearTimeout(confettiTimerRef.current);
772
+ }
773
+ const resolvedConfettiConfig = Object.assign(Object.assign({}, globalConfettiConfigRef.current), (rewardConfetti || {}));
774
+ const resolvedConfettiDuration = (_b = resolvedConfettiConfig.duration) !== null && _b !== void 0 ? _b : CONFETTI_DURATION_MS;
775
+ setActiveConfettiConfig(resolvedConfettiConfig);
776
+ setShowConfetti(true);
777
+ confettiTimerRef.current = setTimeout(() => {
778
+ setShowConfetti(false);
779
+ confettiTimerRef.current = null;
780
+ }, resolvedConfettiDuration);
781
+ }
782
+ }, [ui.enableConfetti, ui.enableNotifications]);
590
783
  useEffect(() => {
784
+ if (!engine) {
785
+ return;
786
+ }
591
787
  const unsubscribeUnlocked = engine.on('achievement:unlocked', (event) => {
592
- var _a;
593
- if (seenAchievementsRef.current.has(event.achievementId)) {
594
- return;
595
- }
596
- seenAchievementsRef.current.add(event.achievementId);
597
- const unlockedAchievement = {
788
+ showUnlockedAchievement({
598
789
  achievementId: event.achievementId,
599
790
  achievementTitle: event.achievementTitle,
600
791
  achievementDescription: event.achievementDescription,
601
792
  achievementIconKey: event.achievementIconKey,
602
793
  isUnlocked: true,
603
- };
604
- if (ui.enableNotifications !== false) {
605
- setNotifications((currentNotifications) => {
606
- if (currentNotifications.some((notification) => notification.achievementId === unlockedAchievement.achievementId)) {
607
- return currentNotifications;
608
- }
609
- return [...currentNotifications, unlockedAchievement];
610
- });
611
- }
612
- const rewardConfetti = achievementConfettiRef.current.get(event.achievementId);
613
- if (ui.enableConfetti !== false && rewardConfetti !== false) {
614
- if (confettiTimerRef.current) {
615
- clearTimeout(confettiTimerRef.current);
616
- }
617
- const resolvedConfettiConfig = Object.assign(Object.assign({}, globalConfettiConfigRef.current), (rewardConfetti || {}));
618
- const resolvedConfettiDuration = (_a = resolvedConfettiConfig.duration) !== null && _a !== void 0 ? _a : CONFETTI_DURATION_MS;
619
- setActiveConfettiConfig(resolvedConfettiConfig);
620
- setShowConfetti(true);
621
- confettiTimerRef.current = setTimeout(() => {
622
- setShowConfetti(false);
623
- confettiTimerRef.current = null;
624
- }, resolvedConfettiDuration);
625
- }
794
+ });
626
795
  });
627
796
  const unsubscribeStateChanged = engine.on('state:changed', () => {
628
797
  const unlocked = new Set(engine.getUnlocked());
@@ -639,7 +808,18 @@ const AchievementEffects = ({ icons, ui, achievementConfetti }) => {
639
808
  clearTimeout(confettiTimerRef.current);
640
809
  }
641
810
  };
642
- }, [engine, ui.enableConfetti, ui.enableNotifications]);
811
+ }, [engine, showUnlockedAchievement]);
812
+ useEffect(() => {
813
+ achievementContext.recentlyUnlocked.forEach(showUnlockedAchievement);
814
+ }, [achievementContext.recentlyUnlocked, showUnlockedAchievement]);
815
+ useEffect(() => {
816
+ const unlocked = new Set(achievementContext.snapshot.unlockedIds);
817
+ seenAchievementsRef.current.forEach((achievementId) => {
818
+ if (!unlocked.has(achievementId)) {
819
+ seenAchievementsRef.current.delete(achievementId);
820
+ }
821
+ });
822
+ }, [achievementContext.snapshot.unlockedIds]);
643
823
  const NotificationComponent = ui.NotificationComponent || BuiltInNotification;
644
824
  const ConfettiComponentResolved = ui.ConfettiComponent || BuiltInConfetti;
645
825
  return (React.createElement(React.Fragment, null,
@@ -660,14 +840,6 @@ const AchievementProvider = (_a) => {
660
840
  React.createElement(AchievementEffects, { achievementConfetti: confettiByAchievementId, icons: icons, ui: ui }))));
661
841
  };
662
842
 
663
- const useAchievements = () => {
664
- const context = useContext(AchievementContext);
665
- if (!context) {
666
- throw new Error('useAchievements must be used within an AchievementProvider');
667
- }
668
- return context;
669
- };
670
-
671
843
  const useAchievementState = () => {
672
844
  const { snapshot } = useAchievements();
673
845
  return {
@@ -1187,11 +1359,11 @@ const LevelProgress = ({ level, currentXP, nextLevelXP, label = 'Level', valueLa
1187
1359
  * Provides the v4 happy path for direct metric updates plus explicit state names.
1188
1360
  */
1189
1361
  const useSimpleAchievements = () => {
1190
- const { update, reset, getState, exportData, importData, engine } = useAchievements();
1362
+ const { update, increment: incrementMetric, event, refresh, reset, getState, exportData, importData, isLoading, error, } = useAchievements();
1191
1363
  const achievementState = useAchievementState();
1192
1364
  const track = (metric, value) => update({ [metric]: value });
1193
1365
  const increment = (metric, amount = 1) => {
1194
- return engine.increment(metric, amount);
1366
+ return incrementMetric(metric, amount);
1195
1367
  };
1196
1368
  const trackMultiple = (metrics) => update(metrics);
1197
1369
  return {
@@ -1204,6 +1376,11 @@ const useSimpleAchievements = () => {
1204
1376
  unlockedCount: achievementState.unlockedCount,
1205
1377
  totalCount: achievementState.totalCount,
1206
1378
  metrics: achievementState.metrics,
1379
+ isLoading,
1380
+ error,
1381
+ event,
1382
+ emit: event,
1383
+ refresh,
1207
1384
  reset,
1208
1385
  getState,
1209
1386
  exportData,
@@ -1220,6 +1397,110 @@ const useSimpleAchievements = () => {
1220
1397
  };
1221
1398
  };
1222
1399
 
1400
+ /**
1401
+ * Access the active AchievementEngine instance.
1402
+ *
1403
+ * In v4 this works with both provider-created engines (`achievements` prop) and
1404
+ * injected engines (`engine` prop).
1405
+ */
1406
+ const useAchievementEngine = () => {
1407
+ const context = useContext(AchievementContext);
1408
+ if (!context) {
1409
+ throw new Error('useAchievementEngine must be used within an AchievementProvider.\n\n' +
1410
+ 'Wrap your component tree:\n' +
1411
+ '<AchievementProvider achievements={achievements}>\n' +
1412
+ ' <YourComponent />\n' +
1413
+ '</AchievementProvider>');
1414
+ }
1415
+ if (!context.engine) {
1416
+ throw new Error('useAchievementEngine is only available when AchievementProvider is using the legacy in-browser engine. ' +
1417
+ 'For server-backed achievements, use useSimpleAchievements().event(), track(), or increment().');
1418
+ }
1419
+ return context.engine;
1420
+ };
1421
+
1422
+ const trimTrailingSlash = (value) => value.replace(/\/$/, '');
1423
+ class RestAchievementClient {
1424
+ constructor(config) {
1425
+ this.baseUrl = trimTrailingSlash(config.baseUrl);
1426
+ this.fetcher = config.fetcher || fetch.bind(globalThis);
1427
+ this.headers = config.headers;
1428
+ this.credentials = config.credentials;
1429
+ this.timeout = config.timeout;
1430
+ }
1431
+ getSnapshot() {
1432
+ return __awaiter(this, void 0, void 0, function* () {
1433
+ return this.request('', { method: 'GET' });
1434
+ });
1435
+ }
1436
+ track(metric, value) {
1437
+ return __awaiter(this, void 0, void 0, function* () {
1438
+ return this.request('/track', {
1439
+ method: 'POST',
1440
+ body: JSON.stringify({ metric, value }),
1441
+ });
1442
+ });
1443
+ }
1444
+ trackMany(metrics) {
1445
+ return __awaiter(this, void 0, void 0, function* () {
1446
+ return this.request('/track', {
1447
+ method: 'POST',
1448
+ body: JSON.stringify({ metrics }),
1449
+ });
1450
+ });
1451
+ }
1452
+ increment(metric_1) {
1453
+ return __awaiter(this, arguments, void 0, function* (metric, amount = 1) {
1454
+ return this.request('/increment', {
1455
+ method: 'POST',
1456
+ body: JSON.stringify({ metric, amount }),
1457
+ });
1458
+ });
1459
+ }
1460
+ event(name, payload) {
1461
+ return __awaiter(this, void 0, void 0, function* () {
1462
+ return this.request('/event', {
1463
+ method: 'POST',
1464
+ body: JSON.stringify({ name, payload }),
1465
+ });
1466
+ });
1467
+ }
1468
+ reset() {
1469
+ return __awaiter(this, void 0, void 0, function* () {
1470
+ return this.request('/reset', { method: 'POST' });
1471
+ });
1472
+ }
1473
+ request(path, init) {
1474
+ return __awaiter(this, void 0, void 0, function* () {
1475
+ const controller = this.timeout ? new AbortController() : undefined;
1476
+ const timeoutId = controller
1477
+ ? setTimeout(() => controller.abort(), this.timeout)
1478
+ : undefined;
1479
+ try {
1480
+ const response = yield this.fetcher(`${this.baseUrl}${path}`, Object.assign(Object.assign({}, init), { credentials: this.credentials, headers: Object.assign(Object.assign({ 'Content-Type': 'application/json' }, (yield this.resolveHeaders())), init.headers), signal: controller === null || controller === void 0 ? void 0 : controller.signal }));
1481
+ if (!response.ok) {
1482
+ throw new Error(`Achievement request failed: HTTP ${response.status}`);
1483
+ }
1484
+ return yield response.json();
1485
+ }
1486
+ finally {
1487
+ if (timeoutId) {
1488
+ clearTimeout(timeoutId);
1489
+ }
1490
+ }
1491
+ });
1492
+ }
1493
+ resolveHeaders() {
1494
+ return __awaiter(this, void 0, void 0, function* () {
1495
+ if (!this.headers) {
1496
+ return {};
1497
+ }
1498
+ return typeof this.headers === 'function' ? this.headers() : this.headers;
1499
+ });
1500
+ }
1501
+ }
1502
+ const createRestAchievementClient = (config) => new RestAchievementClient(config);
1503
+
1223
1504
  /**
1224
1505
  * Built-in modal component
1225
1506
  * Modern, theme-aware achievement modal with smooth animations
@@ -1473,5 +1754,5 @@ function useWindowSize() {
1473
1754
  return size;
1474
1755
  }
1475
1756
 
1476
- export { AchievementContext, AchievementProvider, AchievementsList, AchievementsModal, AchievementsWidget, BadgesButton, BadgesButtonWithModal, BadgesModal, BuiltInConfetti, BuiltInModal, BuiltInNotification, ConfettiWrapper, AchievementProvider$1 as HeadlessAchievementProvider, LevelProgress, defaultAchievementIcons, defaultStyles, isAsyncStorage, useAchievementEngine, useAchievementState, useAchievements, useSimpleAchievements, useWindowSize };
1757
+ export { AchievementContext, AchievementProvider, AchievementsList, AchievementsModal, AchievementsWidget, BadgesButton, BadgesButtonWithModal, BadgesModal, BuiltInConfetti, BuiltInModal, BuiltInNotification, ConfettiWrapper, AchievementProvider$1 as HeadlessAchievementProvider, LevelProgress, RestAchievementClient, createRestAchievementClient, defaultAchievementIcons, defaultStyles, isAsyncStorage, useAchievementEngine, useAchievementState, useAchievements, useSimpleAchievements, useWindowSize };
1477
1758
  //# sourceMappingURL=web.esm.js.map