@playbasis-ai/qwikcard-sdk 2.3.13 → 2.3.15
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/CHANGELOG.md +12 -0
- package/dist/PlaybasisProvider.d.ts +3 -1
- package/dist/PlaybasisProvider.d.ts.map +1 -1
- package/dist/PlaybasisProvider.js +8 -2
- package/dist/QwikCardApp.d.ts +2 -0
- package/dist/QwikCardApp.d.ts.map +1 -1
- package/dist/QwikCardApp.js +2 -1
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +65 -38
- package/dist/data/qwik-badge-assets.d.ts +2 -0
- package/dist/data/qwik-badge-assets.d.ts.map +1 -0
- package/dist/data/qwik-badge-assets.js +37 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/PlaybasisProvider.tsx +13 -2
- package/src/QwikCardApp.tsx +4 -1
- package/src/api/client.ts +70 -39
- package/src/data/qwik-badge-assets.ts +40 -0
- package/src/types/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -61,6 +61,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
61
61
|
|
|
62
62
|
- Support `items` responses for quests, badges, and rewards to avoid empty dashboards.
|
|
63
63
|
|
|
64
|
+
## [2.3.14] - 2026-01-30
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Fallback badge image mapping for Qwik slugs when API omits image URLs.
|
|
69
|
+
|
|
70
|
+
## [2.3.15] - 2026-01-30
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- Lightweight request retries/timeouts with opt-in configuration for smoother client UX.
|
|
75
|
+
|
|
64
76
|
## [2.3.11] - 2026-01-29
|
|
65
77
|
|
|
66
78
|
### Changed
|
|
@@ -12,8 +12,10 @@ interface PlaybasisProviderProps {
|
|
|
12
12
|
tenantId?: string;
|
|
13
13
|
playerId?: string;
|
|
14
14
|
baseUrl?: string;
|
|
15
|
+
requestTimeoutMs?: number;
|
|
16
|
+
requestRetries?: number;
|
|
15
17
|
}
|
|
16
|
-
export declare function PlaybasisProvider({ children, apiKey, tenantId, playerId, baseUrl, }: PlaybasisProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function PlaybasisProvider({ children, apiKey, tenantId, playerId, baseUrl, requestTimeoutMs, requestRetries, }: PlaybasisProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
17
19
|
export declare function usePlaybasis(): PlaybasisContextValue;
|
|
18
20
|
export default PlaybasisProvider;
|
|
19
21
|
//# sourceMappingURL=PlaybasisProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybasisProvider.d.ts","sourceRoot":"","sources":["../src/PlaybasisProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAO/C,UAAU,qBAAqB;IAC7B,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"PlaybasisProvider.d.ts","sourceRoot":"","sources":["../src/PlaybasisProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAO/C,UAAU,qBAAqB;IAC7B,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAiBD,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,gBAAgB,EAChB,cAAc,GACf,EAAE,sBAAsB,2CA+BxB;AAMD,wBAAgB,YAAY,IAAI,qBAAqB,CAMpD;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -17,10 +17,16 @@ const resolveBaseUrl = (tenantId, baseUrl) => {
|
|
|
17
17
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
18
|
// Provider Component
|
|
19
19
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
-
export function PlaybasisProvider({ children, apiKey, tenantId, playerId, baseUrl, }) {
|
|
20
|
+
export function PlaybasisProvider({ children, apiKey, tenantId, playerId, baseUrl, requestTimeoutMs, requestRetries, }) {
|
|
21
21
|
const resolvedTenantId = tenantId ?? (baseUrl && baseUrl.includes('production') ? 'qwik-prod' : 'qwikcard');
|
|
22
22
|
const resolvedBaseUrl = resolveBaseUrl(resolvedTenantId, baseUrl);
|
|
23
|
-
const client = useMemo(() => new PlaybasisClient({
|
|
23
|
+
const client = useMemo(() => new PlaybasisClient({
|
|
24
|
+
apiKey,
|
|
25
|
+
tenantId: resolvedTenantId,
|
|
26
|
+
baseUrl: resolvedBaseUrl,
|
|
27
|
+
timeoutMs: requestTimeoutMs,
|
|
28
|
+
retries: requestRetries,
|
|
29
|
+
}), [apiKey, resolvedTenantId, resolvedBaseUrl, requestTimeoutMs, requestRetries]);
|
|
24
30
|
const value = useMemo(() => ({
|
|
25
31
|
client,
|
|
26
32
|
tenantId: resolvedTenantId,
|
package/dist/QwikCardApp.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export interface QwikCardAppProps {
|
|
|
4
4
|
playerId: string;
|
|
5
5
|
baseUrl?: string;
|
|
6
6
|
leaderboardId?: string;
|
|
7
|
+
requestTimeoutMs?: number;
|
|
8
|
+
requestRetries?: number;
|
|
7
9
|
}
|
|
8
10
|
export declare function QwikCardApp(props: QwikCardAppProps): import("react/jsx-runtime").JSX.Element;
|
|
9
11
|
//# sourceMappingURL=QwikCardApp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QwikCardApp.d.ts","sourceRoot":"","sources":["../src/QwikCardApp.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"QwikCardApp.d.ts","sourceRoot":"","sources":["../src/QwikCardApp.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAwRD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CAMlD"}
|
package/dist/QwikCardApp.js
CHANGED
|
@@ -4,6 +4,7 @@ import { View, ActivityIndicator, Platform, Alert, TouchableOpacity, Text } from
|
|
|
4
4
|
import { WebView } from 'react-native-webview';
|
|
5
5
|
import { PlaybasisProvider, usePlaybasis } from './PlaybasisProvider';
|
|
6
6
|
import { useTheme } from './theme/context';
|
|
7
|
+
import { getQwikBadgeAssetUrl } from './data/qwik-badge-assets';
|
|
7
8
|
import { getWidgetHtml } from './web/widgetHtml';
|
|
8
9
|
function AppContent({ leaderboardId }) {
|
|
9
10
|
const theme = useTheme();
|
|
@@ -93,7 +94,7 @@ function AppContent({ leaderboardId }) {
|
|
|
93
94
|
name: b.name,
|
|
94
95
|
rarity: 'common',
|
|
95
96
|
unlocked: playerBadges.some((pb) => pb.id === b.id && pb.isEarned),
|
|
96
|
-
imageUrl: b.imageUrl,
|
|
97
|
+
imageUrl: b.imageUrl || (b.slug ? getQwikBadgeAssetUrl(b.slug) : undefined),
|
|
97
98
|
})),
|
|
98
99
|
rewards: rewards.map((r) => ({
|
|
99
100
|
id: r.id,
|
package/dist/api/client.d.ts
CHANGED
|
@@ -4,12 +4,16 @@ interface ClientConfig {
|
|
|
4
4
|
tenantId: string;
|
|
5
5
|
baseUrl?: string;
|
|
6
6
|
fetchImpl?: typeof fetch;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
retries?: number;
|
|
7
9
|
}
|
|
8
10
|
export declare class PlaybasisClient {
|
|
9
11
|
private readonly baseUrl;
|
|
10
12
|
private readonly fetchImpl;
|
|
11
13
|
private readonly apiKey;
|
|
12
14
|
private readonly tenantId;
|
|
15
|
+
private readonly timeoutMs;
|
|
16
|
+
private readonly retries;
|
|
13
17
|
constructor(config: ClientConfig);
|
|
14
18
|
/**
|
|
15
19
|
* Generate a unique ID for idempotency keys.
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,YAAY,EACZ,KAAK,EACL,gBAAgB,EAChB,MAAM,EACN,eAAe,EAChB,MAAM,UAAU,CAAC;AAMlB,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
package/dist/api/client.js
CHANGED
|
@@ -8,6 +8,8 @@ export class PlaybasisClient {
|
|
|
8
8
|
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
9
9
|
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
10
10
|
this.apiKey = config.apiKey;
|
|
11
|
+
this.timeoutMs = config.timeoutMs ?? 10000;
|
|
12
|
+
this.retries = config.retries ?? 2;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Generate a unique ID for idempotency keys.
|
|
@@ -22,49 +24,74 @@ export class PlaybasisClient {
|
|
|
22
24
|
}
|
|
23
25
|
async request(method, path, options) {
|
|
24
26
|
const url = `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
39
|
-
};
|
|
40
|
-
const response = await this.fetchImpl(url, init);
|
|
41
|
-
const text = await response.text();
|
|
42
|
-
// Safely parse JSON - server may return non-JSON (e.g., HTML error page)
|
|
43
|
-
let json;
|
|
44
|
-
if (text) {
|
|
27
|
+
const attemptRequest = async () => {
|
|
28
|
+
const headers = {
|
|
29
|
+
'Ocp-Apim-Subscription-Key': this.apiKey,
|
|
30
|
+
'X-Tenant-ID': this.tenantId,
|
|
31
|
+
...options?.headers,
|
|
32
|
+
};
|
|
33
|
+
if (options?.body !== undefined) {
|
|
34
|
+
headers['Content-Type'] = headers['Content-Type'] ?? 'application/json';
|
|
35
|
+
}
|
|
36
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
37
|
+
const timeoutId = this.timeoutMs
|
|
38
|
+
? setTimeout(() => controller?.abort(), this.timeoutMs)
|
|
39
|
+
: null;
|
|
45
40
|
try {
|
|
46
|
-
|
|
41
|
+
const init = {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
45
|
+
signal: controller?.signal,
|
|
46
|
+
};
|
|
47
|
+
const response = await this.fetchImpl(url, init);
|
|
48
|
+
const text = await response.text();
|
|
49
|
+
let json;
|
|
50
|
+
if (text) {
|
|
51
|
+
try {
|
|
52
|
+
json = JSON.parse(text);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
json = undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const getErrorMessage = (value) => {
|
|
59
|
+
if (!value || typeof value !== 'object')
|
|
60
|
+
return undefined;
|
|
61
|
+
if (!('message' in value))
|
|
62
|
+
return undefined;
|
|
63
|
+
const messageValue = value.message;
|
|
64
|
+
return typeof messageValue === 'string' ? messageValue : undefined;
|
|
65
|
+
};
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const errorMessage = getErrorMessage(json) ||
|
|
68
|
+
(text && !json ? text.slice(0, 200) : null) ||
|
|
69
|
+
`Request failed: ${response.status} ${response.statusText}`;
|
|
70
|
+
const error = new Error(String(errorMessage));
|
|
71
|
+
error.status = response.status;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return json;
|
|
47
75
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
finally {
|
|
77
|
+
if (timeoutId)
|
|
78
|
+
clearTimeout(timeoutId);
|
|
51
79
|
}
|
|
52
|
-
}
|
|
53
|
-
const getErrorMessage = (value) => {
|
|
54
|
-
if (!value || typeof value !== 'object')
|
|
55
|
-
return undefined;
|
|
56
|
-
if (!('message' in value))
|
|
57
|
-
return undefined;
|
|
58
|
-
const messageValue = value.message;
|
|
59
|
-
return typeof messageValue === 'string' ? messageValue : undefined;
|
|
60
80
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
for (let attempt = 0; attempt <= this.retries; attempt += 1) {
|
|
82
|
+
try {
|
|
83
|
+
return await attemptRequest();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const status = error.status;
|
|
87
|
+
const shouldRetry = attempt < this.retries &&
|
|
88
|
+
(status === undefined || status === 429 || (status >= 500 && status <= 599));
|
|
89
|
+
if (!shouldRetry)
|
|
90
|
+
throw error;
|
|
91
|
+
await new Promise((resolve) => setTimeout(resolve, 400 * (attempt + 1)));
|
|
92
|
+
}
|
|
66
93
|
}
|
|
67
|
-
|
|
94
|
+
throw new Error('Request failed after retries');
|
|
68
95
|
}
|
|
69
96
|
// ─────────────────────────────────────────────────────────────────────────
|
|
70
97
|
// Players
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qwik-badge-assets.d.ts","sourceRoot":"","sources":["../../src/data/qwik-badge-assets.ts"],"names":[],"mappings":"AAoCA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAA+B,UAGxF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const DEFAULT_BADGE_ASSET_BASE_URL = 'https://sapbstg001.blob.core.windows.net/playbasis-assets/tenants/qwik/badges';
|
|
2
|
+
const QWIK_BADGE_FILE_MAP = {
|
|
3
|
+
'badge.qwik.starter': 'badge_starter',
|
|
4
|
+
'badge.qwik.profile_complete': 'badge_profile_pro',
|
|
5
|
+
'badge.qwik.budget_boss': 'badge_budget_boss',
|
|
6
|
+
'badge.qwik.credit_curious': 'badge_credit_curious',
|
|
7
|
+
'badge.qwik.social_spark': 'badge_social_spark',
|
|
8
|
+
'badge.qwik.streak_7': 'badge_streak_7',
|
|
9
|
+
'badge.qwik.streak_21': 'badge_streak_master',
|
|
10
|
+
'badge.qwik.learner_3': 'badge_learner',
|
|
11
|
+
'badge.qwik.ambassador': 'badge_campus_ambassador',
|
|
12
|
+
'badge.qwik.saver_500': 'badge_savings_spark',
|
|
13
|
+
'badge.qwik.saver_1000': 'badge_savings_champion',
|
|
14
|
+
'badge.qwik.txn_10': 'badge_card_explorer',
|
|
15
|
+
'badge.qwik.txn_50': 'badge_card_pro',
|
|
16
|
+
'badge.qwik.credit_safe': 'badge_credit_guardian',
|
|
17
|
+
'badge.qwik.campus_mvp': 'badge_campus_mvp',
|
|
18
|
+
'badge.qwik.login_30': 'badge_consistency_champ',
|
|
19
|
+
'badge.qwik.login_90': 'badge_quarter_streak',
|
|
20
|
+
'badge.qwik.learn_10': 'badge_knowledge_builder',
|
|
21
|
+
'badge.qwik.learn_25': 'badge_finance_scholar',
|
|
22
|
+
'badge.qwik.budget_3': 'badge_budget_builder',
|
|
23
|
+
'badge.qwik.budget_5': 'badge_budget_architect',
|
|
24
|
+
'badge.qwik.credit_check_4': 'badge_credit_watcher',
|
|
25
|
+
'badge.qwik.credit_check_8': 'badge_credit_sentinel',
|
|
26
|
+
'badge.qwik.qwikit_25': 'badge_social_connector',
|
|
27
|
+
'badge.qwik.qwikit_50': 'badge_campus_networker',
|
|
28
|
+
'badge.qwik.txn_100': 'badge_card_veteran',
|
|
29
|
+
'badge.qwik.txn_week_5': 'badge_weekly_explorer',
|
|
30
|
+
'badge.qwik.referral_5': 'badge_connector',
|
|
31
|
+
'badge.qwik.referral_10': 'badge_campus_influencer',
|
|
32
|
+
'badge.qwik.saver_2500': 'badge_savings_hero',
|
|
33
|
+
};
|
|
34
|
+
export function getQwikBadgeAssetUrl(slug, baseUrl = DEFAULT_BADGE_ASSET_BASE_URL) {
|
|
35
|
+
const fileName = QWIK_BADGE_FILE_MAP[slug] ?? slug;
|
|
36
|
+
return `${baseUrl.replace(/\/$/, '')}/${fileName}.webp`;
|
|
37
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -27,6 +27,8 @@ interface PlaybasisProviderProps {
|
|
|
27
27
|
tenantId?: string;
|
|
28
28
|
playerId?: string;
|
|
29
29
|
baseUrl?: string;
|
|
30
|
+
requestTimeoutMs?: number;
|
|
31
|
+
requestRetries?: number;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const QWIK_BASE_URLS = {
|
|
@@ -50,13 +52,22 @@ export function PlaybasisProvider({
|
|
|
50
52
|
tenantId,
|
|
51
53
|
playerId,
|
|
52
54
|
baseUrl,
|
|
55
|
+
requestTimeoutMs,
|
|
56
|
+
requestRetries,
|
|
53
57
|
}: PlaybasisProviderProps) {
|
|
54
58
|
const resolvedTenantId =
|
|
55
59
|
tenantId ?? (baseUrl && baseUrl.includes('production') ? 'qwik-prod' : 'qwikcard');
|
|
56
60
|
const resolvedBaseUrl = resolveBaseUrl(resolvedTenantId, baseUrl);
|
|
57
61
|
const client = useMemo(
|
|
58
|
-
() =>
|
|
59
|
-
|
|
62
|
+
() =>
|
|
63
|
+
new PlaybasisClient({
|
|
64
|
+
apiKey,
|
|
65
|
+
tenantId: resolvedTenantId,
|
|
66
|
+
baseUrl: resolvedBaseUrl,
|
|
67
|
+
timeoutMs: requestTimeoutMs,
|
|
68
|
+
retries: requestRetries,
|
|
69
|
+
}),
|
|
70
|
+
[apiKey, resolvedTenantId, resolvedBaseUrl, requestTimeoutMs, requestRetries],
|
|
60
71
|
);
|
|
61
72
|
|
|
62
73
|
const value = useMemo(
|
package/src/QwikCardApp.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
|
4
4
|
|
|
5
5
|
import { PlaybasisProvider, usePlaybasis } from './PlaybasisProvider';
|
|
6
6
|
import { useTheme } from './theme/context';
|
|
7
|
+
import { getQwikBadgeAssetUrl } from './data/qwik-badge-assets';
|
|
7
8
|
import { getWidgetHtml } from './web/widgetHtml';
|
|
8
9
|
import type { WidgetBootstrapData, WidgetBridgeMessage } from './web/widgetTypes';
|
|
9
10
|
|
|
@@ -13,6 +14,8 @@ export interface QwikCardAppProps {
|
|
|
13
14
|
playerId: string;
|
|
14
15
|
baseUrl?: string;
|
|
15
16
|
leaderboardId?: string;
|
|
17
|
+
requestTimeoutMs?: number;
|
|
18
|
+
requestRetries?: number;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
function AppContent({ leaderboardId }: { leaderboardId?: string }) {
|
|
@@ -112,7 +115,7 @@ function AppContent({ leaderboardId }: { leaderboardId?: string }) {
|
|
|
112
115
|
name: b.name,
|
|
113
116
|
rarity: 'common',
|
|
114
117
|
unlocked: playerBadges.some((pb) => pb.id === b.id && pb.isEarned),
|
|
115
|
-
imageUrl: b.imageUrl,
|
|
118
|
+
imageUrl: b.imageUrl || (b.slug ? getQwikBadgeAssetUrl(b.slug) : undefined),
|
|
116
119
|
})),
|
|
117
120
|
rewards: rewards.map((r) => ({
|
|
118
121
|
id: r.id,
|
package/src/api/client.ts
CHANGED
|
@@ -19,6 +19,8 @@ interface ClientConfig {
|
|
|
19
19
|
tenantId: string;
|
|
20
20
|
baseUrl?: string;
|
|
21
21
|
fetchImpl?: typeof fetch;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
retries?: number;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
const DEFAULT_BASE_URL = 'https://apim-pb-staging.azure-api.net/playbasis/v1';
|
|
@@ -32,12 +34,16 @@ export class PlaybasisClient {
|
|
|
32
34
|
private readonly fetchImpl: typeof fetch;
|
|
33
35
|
private readonly apiKey: string;
|
|
34
36
|
private readonly tenantId: string;
|
|
37
|
+
private readonly timeoutMs: number;
|
|
38
|
+
private readonly retries: number;
|
|
35
39
|
|
|
36
40
|
constructor(config: ClientConfig) {
|
|
37
41
|
this.tenantId = config.tenantId;
|
|
38
42
|
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
39
43
|
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
40
44
|
this.apiKey = config.apiKey;
|
|
45
|
+
this.timeoutMs = config.timeoutMs ?? 10000;
|
|
46
|
+
this.retries = config.retries ?? 2;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/**
|
|
@@ -59,54 +65,79 @@ export class PlaybasisClient {
|
|
|
59
65
|
): Promise<T> {
|
|
60
66
|
const url = `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
61
67
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Set Content-Type before assigning to init to avoid mutation after reference
|
|
70
|
-
if (options?.body !== undefined) {
|
|
71
|
-
headers['Content-Type'] = headers['Content-Type'] ?? 'application/json';
|
|
72
|
-
}
|
|
68
|
+
const attemptRequest = async (): Promise<T> => {
|
|
69
|
+
const headers: Record<string, string> = {
|
|
70
|
+
'Ocp-Apim-Subscription-Key': this.apiKey,
|
|
71
|
+
'X-Tenant-ID': this.tenantId,
|
|
72
|
+
...options?.headers,
|
|
73
|
+
};
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
78
|
-
};
|
|
75
|
+
if (options?.body !== undefined) {
|
|
76
|
+
headers['Content-Type'] = headers['Content-Type'] ?? 'application/json';
|
|
77
|
+
}
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
80
|
+
const timeoutId = this.timeoutMs
|
|
81
|
+
? setTimeout(() => controller?.abort(), this.timeoutMs)
|
|
82
|
+
: null;
|
|
82
83
|
|
|
83
|
-
// Safely parse JSON - server may return non-JSON (e.g., HTML error page)
|
|
84
|
-
let json: unknown;
|
|
85
|
-
if (text) {
|
|
86
84
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const init: RequestInit = {
|
|
86
|
+
method,
|
|
87
|
+
headers,
|
|
88
|
+
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
89
|
+
signal: controller?.signal,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const response = await this.fetchImpl(url, init);
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
|
|
95
|
+
let json: unknown;
|
|
96
|
+
if (text) {
|
|
97
|
+
try {
|
|
98
|
+
json = JSON.parse(text);
|
|
99
|
+
} catch {
|
|
100
|
+
json = undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const getErrorMessage = (value: unknown): string | undefined => {
|
|
105
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
106
|
+
if (!('message' in value)) return undefined;
|
|
107
|
+
const messageValue = (value as { message?: unknown }).message;
|
|
108
|
+
return typeof messageValue === 'string' ? messageValue : undefined;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const errorMessage =
|
|
113
|
+
getErrorMessage(json) ||
|
|
114
|
+
(text && !json ? text.slice(0, 200) : null) ||
|
|
115
|
+
`Request failed: ${response.status} ${response.statusText}`;
|
|
116
|
+
const error = new Error(String(errorMessage)) as Error & { status?: number };
|
|
117
|
+
error.status = response.status;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return json as T;
|
|
122
|
+
} finally {
|
|
123
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
91
124
|
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const getErrorMessage = (value: unknown): string | undefined => {
|
|
95
|
-
if (!value || typeof value !== 'object') return undefined;
|
|
96
|
-
if (!('message' in value)) return undefined;
|
|
97
|
-
const messageValue = (value as { message?: unknown }).message;
|
|
98
|
-
return typeof messageValue === 'string' ? messageValue : undefined;
|
|
99
125
|
};
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
127
|
+
for (let attempt = 0; attempt <= this.retries; attempt += 1) {
|
|
128
|
+
try {
|
|
129
|
+
return await attemptRequest();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const status = (error as { status?: number }).status;
|
|
132
|
+
const shouldRetry =
|
|
133
|
+
attempt < this.retries &&
|
|
134
|
+
(status === undefined || status === 429 || (status >= 500 && status <= 599));
|
|
135
|
+
if (!shouldRetry) throw error;
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 400 * (attempt + 1)));
|
|
137
|
+
}
|
|
107
138
|
}
|
|
108
139
|
|
|
109
|
-
|
|
140
|
+
throw new Error('Request failed after retries');
|
|
110
141
|
}
|
|
111
142
|
|
|
112
143
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const DEFAULT_BADGE_ASSET_BASE_URL =
|
|
2
|
+
'https://sapbstg001.blob.core.windows.net/playbasis-assets/tenants/qwik/badges';
|
|
3
|
+
|
|
4
|
+
const QWIK_BADGE_FILE_MAP: Record<string, string> = {
|
|
5
|
+
'badge.qwik.starter': 'badge_starter',
|
|
6
|
+
'badge.qwik.profile_complete': 'badge_profile_pro',
|
|
7
|
+
'badge.qwik.budget_boss': 'badge_budget_boss',
|
|
8
|
+
'badge.qwik.credit_curious': 'badge_credit_curious',
|
|
9
|
+
'badge.qwik.social_spark': 'badge_social_spark',
|
|
10
|
+
'badge.qwik.streak_7': 'badge_streak_7',
|
|
11
|
+
'badge.qwik.streak_21': 'badge_streak_master',
|
|
12
|
+
'badge.qwik.learner_3': 'badge_learner',
|
|
13
|
+
'badge.qwik.ambassador': 'badge_campus_ambassador',
|
|
14
|
+
'badge.qwik.saver_500': 'badge_savings_spark',
|
|
15
|
+
'badge.qwik.saver_1000': 'badge_savings_champion',
|
|
16
|
+
'badge.qwik.txn_10': 'badge_card_explorer',
|
|
17
|
+
'badge.qwik.txn_50': 'badge_card_pro',
|
|
18
|
+
'badge.qwik.credit_safe': 'badge_credit_guardian',
|
|
19
|
+
'badge.qwik.campus_mvp': 'badge_campus_mvp',
|
|
20
|
+
'badge.qwik.login_30': 'badge_consistency_champ',
|
|
21
|
+
'badge.qwik.login_90': 'badge_quarter_streak',
|
|
22
|
+
'badge.qwik.learn_10': 'badge_knowledge_builder',
|
|
23
|
+
'badge.qwik.learn_25': 'badge_finance_scholar',
|
|
24
|
+
'badge.qwik.budget_3': 'badge_budget_builder',
|
|
25
|
+
'badge.qwik.budget_5': 'badge_budget_architect',
|
|
26
|
+
'badge.qwik.credit_check_4': 'badge_credit_watcher',
|
|
27
|
+
'badge.qwik.credit_check_8': 'badge_credit_sentinel',
|
|
28
|
+
'badge.qwik.qwikit_25': 'badge_social_connector',
|
|
29
|
+
'badge.qwik.qwikit_50': 'badge_campus_networker',
|
|
30
|
+
'badge.qwik.txn_100': 'badge_card_veteran',
|
|
31
|
+
'badge.qwik.txn_week_5': 'badge_weekly_explorer',
|
|
32
|
+
'badge.qwik.referral_5': 'badge_connector',
|
|
33
|
+
'badge.qwik.referral_10': 'badge_campus_influencer',
|
|
34
|
+
'badge.qwik.saver_2500': 'badge_savings_hero',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function getQwikBadgeAssetUrl(slug: string, baseUrl = DEFAULT_BADGE_ASSET_BASE_URL) {
|
|
38
|
+
const fileName = QWIK_BADGE_FILE_MAP[slug] ?? slug;
|
|
39
|
+
return `${baseUrl.replace(/\/$/, '')}/${fileName}.webp`;
|
|
40
|
+
}
|