@gamecore-api/sdk 0.14.0 → 0.16.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 +49 -7
- package/dist/client.d.ts +38 -1
- package/dist/index.js +92 -2
- package/dist/types.d.ts +101 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,20 +42,62 @@ const results = await gc.catalog.search("roblox");
|
|
|
42
42
|
|
|
43
43
|
## Authentication
|
|
44
44
|
|
|
45
|
-
### Telegram Auth
|
|
45
|
+
### Telegram Auth — two paths, pick both
|
|
46
|
+
|
|
47
|
+
Two flavours, designed to coexist as two buttons in the UI:
|
|
48
|
+
|
|
49
|
+
**1. Official Login Widget** (fastest, needs official Telegram Web session)
|
|
46
50
|
|
|
47
51
|
```typescript
|
|
48
|
-
//
|
|
49
|
-
|
|
52
|
+
// Mount the blue "Log in with Telegram" button into your own <div>.
|
|
53
|
+
// Bot username is pulled from /site/config; no hardcoding.
|
|
54
|
+
// BotFather /setdomain must point at your storefront's origin, or
|
|
55
|
+
// telegram.org refuses to render the widget.
|
|
56
|
+
const cleanup = await gc.auth.renderTelegramWidget({
|
|
57
|
+
container: document.querySelector("#tg-login")!,
|
|
58
|
+
size: "large",
|
|
59
|
+
onAuth: (user) => {
|
|
60
|
+
console.log("Logged in:", user.firstName);
|
|
61
|
+
window.location.href = "/profile";
|
|
62
|
+
},
|
|
63
|
+
onError: (err) => console.error(err),
|
|
64
|
+
});
|
|
50
65
|
|
|
51
|
-
//
|
|
52
|
-
|
|
66
|
+
// Later (React unmount, SPA route change):
|
|
67
|
+
cleanup();
|
|
68
|
+
```
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
**2. Bot-link flow** (works in every Telegram client including 3rd-party)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const user = await gc.auth.loginViaTelegramBot({
|
|
74
|
+
onBotLinkReady: (botLink) => {
|
|
75
|
+
// Open in new tab — every TG client handles tg:// deep links.
|
|
76
|
+
// For desktop-only users you could also render botLink as a QR.
|
|
77
|
+
window.open(botLink, "_blank");
|
|
78
|
+
},
|
|
79
|
+
pollMs: 2000,
|
|
80
|
+
timeoutMs: 120_000,
|
|
81
|
+
});
|
|
56
82
|
console.log("Logged in:", user.firstName);
|
|
57
83
|
```
|
|
58
84
|
|
|
85
|
+
**Low-level pieces (for custom flows)**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Manual init+poll — equivalent to loginViaTelegramBot above
|
|
89
|
+
const { token, botLink } = await gc.auth.initTelegram();
|
|
90
|
+
window.open(botLink, "_blank");
|
|
91
|
+
const user = await gc.auth.pollTelegramStatus(token);
|
|
92
|
+
|
|
93
|
+
// Manual widget verification — when you render Telegram's <script> yourself
|
|
94
|
+
// and wire data-onauth to your own JS callback
|
|
95
|
+
const auth = await gc.auth.verifyTelegramWidget(telegramWidgetUser);
|
|
96
|
+
|
|
97
|
+
// Mini App (inside the Telegram bot's built-in WebApp)
|
|
98
|
+
const auth = await gc.auth.verifyMiniApp(window.Telegram.WebApp.initData);
|
|
99
|
+
```
|
|
100
|
+
|
|
59
101
|
### VK Auth
|
|
60
102
|
|
|
61
103
|
```typescript
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Announcement, AnnouncementBar, CartItem, CatalogSection, Category, CheckoutRequest, CheckoutResponse, CompleteWithBalanceResult, Conversation, ConversationDetail, CouponResult, ExchangeRates, Favorite, Game, GameDetail, GiftCard, LegalDocument, LevelStatus, Notification, Order, PagedGamesResponse, PaginatedResponse, PaymentInfo, PaymentMethod, Product, ProductFilters, ReferralCommission, ReferralLink, ReferralPerformance, ReferralStats, Review, ReviewCreateResult, ReviewStats, SearchResult, SiteConfig, SiteStats, SiteUIConfig, TelegramAuthResponse, TelegramBotLoginOptions, TelegramInitResponse, TelegramWidgetRenderOptions, TelegramWidgetUser, TopupMethod, TopupResponse, TopupStatus, Transaction, User, UserBalance, WebPushSubscriptionInput } from "./types";
|
|
2
2
|
export interface GameCoreOptions {
|
|
3
3
|
/** Site API key (gc_live_xxx or gc_test_xxx) */
|
|
4
4
|
apiKey: string;
|
|
@@ -124,6 +124,43 @@ export declare class GameCoreClient {
|
|
|
124
124
|
* directly to this method.
|
|
125
125
|
*/
|
|
126
126
|
verifyTelegramWidget: (data: TelegramWidgetUser, ref?: string) => Promise<TelegramAuthResponse>;
|
|
127
|
+
/**
|
|
128
|
+
* High-level helper: mount the official Telegram Login Widget into
|
|
129
|
+
* your own DOM element and hand the resulting User to `onAuth`
|
|
130
|
+
* after server-side HMAC verification. Browser-only.
|
|
131
|
+
*
|
|
132
|
+
* Compared to dropping the raw `<script>` on the page:
|
|
133
|
+
* - Bot username is pulled from `gc.site.getConfig().authConfig.
|
|
134
|
+
* telegram.botUsername` (no hardcoding).
|
|
135
|
+
* - The widget's `data-onauth` bridge is plumbed to
|
|
136
|
+
* {@link verifyTelegramWidget} so the caller only sees a
|
|
137
|
+
* verified User.
|
|
138
|
+
* - Errors (hash mismatch, missing bot, expired payload) surface
|
|
139
|
+
* through `onError` instead of becoming silent no-ops.
|
|
140
|
+
*
|
|
141
|
+
* BotFather prerequisite: `/setdomain` must be set for the bot to
|
|
142
|
+
* the storefront's origin; the widget refuses to render otherwise.
|
|
143
|
+
*
|
|
144
|
+
* Returns a cleanup function that removes the widget from the
|
|
145
|
+
* container.
|
|
146
|
+
*/
|
|
147
|
+
renderTelegramWidget: (opts: TelegramWidgetRenderOptions) => Promise<() => void>;
|
|
148
|
+
/**
|
|
149
|
+
* High-level helper: run the bot-link auth flow (initTelegram →
|
|
150
|
+
* poll) and resolve with the authenticated User. Works in every
|
|
151
|
+
* Telegram client (including third-party like Nicegram, Plus
|
|
152
|
+
* Messenger) because it doesn't depend on the Login Widget — the
|
|
153
|
+
* user just opens the deep link and presses Start in the bot.
|
|
154
|
+
*
|
|
155
|
+
* The caller's `onBotLinkReady` callback receives the generated
|
|
156
|
+
* `https://t.me/<bot>?start=<token>` link exactly once; use it to
|
|
157
|
+
* `window.open(url, "_blank")`, render a QR, or redirect the
|
|
158
|
+
* user. Polling for the auth handshake starts immediately after.
|
|
159
|
+
*
|
|
160
|
+
* Rejects with `GameCoreError("Timeout")` if the user doesn't
|
|
161
|
+
* press Start within `timeoutMs`.
|
|
162
|
+
*/
|
|
163
|
+
loginViaTelegramBot: (opts: TelegramBotLoginOptions) => Promise<User>;
|
|
127
164
|
/**
|
|
128
165
|
* Get VK OAuth authorize URL (authorization code flow).
|
|
129
166
|
*
|
package/dist/index.js
CHANGED
|
@@ -109,6 +109,91 @@ class GameCoreClient {
|
|
|
109
109
|
}),
|
|
110
110
|
verifyMiniApp: (initData, ref) => this.request("POST", "/auth/telegram/miniapp", { initData, ref }, { rawResponse: true }),
|
|
111
111
|
verifyTelegramWidget: (data, ref) => this.request("POST", "/auth/telegram/widget", { ...data, ref }, { rawResponse: true }),
|
|
112
|
+
renderTelegramWidget: async (opts) => {
|
|
113
|
+
if (typeof document === "undefined") {
|
|
114
|
+
throw new GameCoreError("renderTelegramWidget requires a browser environment", 0, "NO_DOM");
|
|
115
|
+
}
|
|
116
|
+
let botUsername = opts.botUsername;
|
|
117
|
+
if (!botUsername) {
|
|
118
|
+
const config = await this.request("GET", "/site/config");
|
|
119
|
+
botUsername = config.authConfig?.telegram?.botUsername ?? undefined;
|
|
120
|
+
}
|
|
121
|
+
if (!botUsername) {
|
|
122
|
+
throw new GameCoreError("Telegram bot is not configured for this site — contact the operator to set a bot in master-admin", 0, "TG_NOT_CONFIGURED");
|
|
123
|
+
}
|
|
124
|
+
const callbackName = `__gcTgWidget_${Math.random().toString(36).slice(2)}`;
|
|
125
|
+
const onAuth = opts.onAuth;
|
|
126
|
+
const onError = opts.onError;
|
|
127
|
+
const ref = opts.ref;
|
|
128
|
+
const verify = this.auth.verifyTelegramWidget.bind(this.auth);
|
|
129
|
+
window[callbackName] = async (user) => {
|
|
130
|
+
try {
|
|
131
|
+
const res = await verify(user, ref);
|
|
132
|
+
if (!res.user) {
|
|
133
|
+
throw new GameCoreError(res.error || "Telegram verification returned no user", 401, "VERIFY_FAILED");
|
|
134
|
+
}
|
|
135
|
+
await onAuth(res.user);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (onError)
|
|
138
|
+
onError(err);
|
|
139
|
+
else
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const script = document.createElement("script");
|
|
144
|
+
script.async = true;
|
|
145
|
+
script.src = "https://telegram.org/js/telegram-widget.js?22";
|
|
146
|
+
script.setAttribute("data-telegram-login", botUsername);
|
|
147
|
+
script.setAttribute("data-size", opts.size ?? "large");
|
|
148
|
+
if (typeof opts.cornerRadius === "number") {
|
|
149
|
+
script.setAttribute("data-radius", String(opts.cornerRadius));
|
|
150
|
+
}
|
|
151
|
+
if (opts.showUserPic === false) {
|
|
152
|
+
script.setAttribute("data-userpic", "false");
|
|
153
|
+
}
|
|
154
|
+
if (opts.requestWriteAccess !== false) {
|
|
155
|
+
script.setAttribute("data-request-access", "write");
|
|
156
|
+
}
|
|
157
|
+
script.setAttribute("data-onauth", `${callbackName}(user)`);
|
|
158
|
+
opts.container.replaceChildren();
|
|
159
|
+
opts.container.appendChild(script);
|
|
160
|
+
return () => {
|
|
161
|
+
opts.container.replaceChildren();
|
|
162
|
+
delete window[callbackName];
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
loginViaTelegramBot: async (opts) => {
|
|
166
|
+
const init = await this.auth.initTelegram();
|
|
167
|
+
opts.onBotLinkReady(init.botLink, init.token);
|
|
168
|
+
const pollMs = opts.pollMs ?? 2000;
|
|
169
|
+
const timeoutMs = opts.timeoutMs ?? 120000;
|
|
170
|
+
const deadline = Date.now() + timeoutMs;
|
|
171
|
+
const refParam = opts.ref ? `?ref=${encodeURIComponent(opts.ref)}` : "";
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const tick = async () => {
|
|
174
|
+
if (Date.now() > deadline) {
|
|
175
|
+
reject(new GameCoreError("Telegram login timed out", 408, "TIMEOUT"));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const res = await this.request("GET", `/auth/telegram/status/${init.token}${refParam}`, undefined, { rawResponse: true });
|
|
180
|
+
if (res.status === "authenticated" && res.user) {
|
|
181
|
+
resolve(res.user);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (res.status === "expired" || res.status === "used") {
|
|
185
|
+
reject(new GameCoreError("Telegram auth token expired", 410, "TOKEN_EXPIRED"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
reject(err);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
setTimeout(tick, pollMs);
|
|
193
|
+
};
|
|
194
|
+
setTimeout(tick, pollMs);
|
|
195
|
+
});
|
|
196
|
+
},
|
|
112
197
|
getVkAuthUrl: (redirectUri, ref) => {
|
|
113
198
|
const params = new URLSearchParams;
|
|
114
199
|
params.set("redirect", redirectUri);
|
|
@@ -120,7 +205,10 @@ class GameCoreClient {
|
|
|
120
205
|
verifyVk: (accessToken, ref) => this.request("POST", "/auth/vk/verify", { accessToken, ref }, { rawResponse: true }),
|
|
121
206
|
register: (email, password, firstName, ref) => this.request("POST", "/auth/register", { email, password, firstName, ref }, { rawResponse: true }),
|
|
122
207
|
login: (email, password) => this.request("POST", "/auth/login", { email, password }, { rawResponse: true }),
|
|
123
|
-
changePassword: (currentPassword, newPassword) => this.request("POST", "/auth/change-password", {
|
|
208
|
+
changePassword: (currentPassword, newPassword) => this.request("POST", "/auth/change-password", {
|
|
209
|
+
currentPassword,
|
|
210
|
+
newPassword
|
|
211
|
+
}),
|
|
124
212
|
getMe: () => this.request("GET", "/auth/me", undefined, { rawResponse: true }),
|
|
125
213
|
logout: () => this.request("POST", "/auth/logout"),
|
|
126
214
|
getIdentities: () => this.request("GET", "/auth/identities", undefined, { rawResponse: true }),
|
|
@@ -261,7 +349,9 @@ class GameCoreClient {
|
|
|
261
349
|
},
|
|
262
350
|
getPushPublicKey: () => this.request("GET", "/profile/push/public-key"),
|
|
263
351
|
subscribePush: (subscription) => this.request("POST", "/profile/push/subscribe", subscription),
|
|
264
|
-
unsubscribePush: (endpoint) => this.request("POST", "/profile/push/unsubscribe", {
|
|
352
|
+
unsubscribePush: (endpoint) => this.request("POST", "/profile/push/unsubscribe", {
|
|
353
|
+
endpoint
|
|
354
|
+
})
|
|
265
355
|
};
|
|
266
356
|
favorites = {
|
|
267
357
|
list: () => this.request("GET", "/favorites"),
|
package/dist/types.d.ts
CHANGED
|
@@ -69,6 +69,17 @@ export interface SiteConfig {
|
|
|
69
69
|
};
|
|
70
70
|
modules: Record<string, boolean>;
|
|
71
71
|
auth: string[];
|
|
72
|
+
/**
|
|
73
|
+
* Structured per-method auth config. Companion to the flat `auth` array
|
|
74
|
+
* — `auth` tells you WHICH methods are available, `authConfig` gives
|
|
75
|
+
* the details you need to render each method on the client (bot
|
|
76
|
+
* username for the Telegram Login Widget, etc).
|
|
77
|
+
*/
|
|
78
|
+
authConfig?: {
|
|
79
|
+
telegram: {
|
|
80
|
+
botUsername: string;
|
|
81
|
+
} | null;
|
|
82
|
+
};
|
|
72
83
|
payments: string[];
|
|
73
84
|
displayCurrency: string;
|
|
74
85
|
rateMode: "auto" | "manual";
|
|
@@ -87,6 +98,76 @@ export interface SiteConfig {
|
|
|
87
98
|
} | null;
|
|
88
99
|
};
|
|
89
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Options for the high-level {@link GameCoreClient.auth.renderTelegramWidget}
|
|
103
|
+
* helper. Values map 1:1 to Telegram Login Widget `data-*` attributes
|
|
104
|
+
* (see https://core.telegram.org/widgets/login).
|
|
105
|
+
*/
|
|
106
|
+
export interface TelegramWidgetRenderOptions {
|
|
107
|
+
/** DOM element the widget renders into. Previous children are cleared. */
|
|
108
|
+
container: HTMLElement;
|
|
109
|
+
/** Button size. Default: "large". */
|
|
110
|
+
size?: "small" | "medium" | "large";
|
|
111
|
+
/** Corner radius in pixels. Default: Telegram's own (20). */
|
|
112
|
+
cornerRadius?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Show user photo next to the button. Default: true (matches Telegram's
|
|
115
|
+
* own default of `data-userpic="true"`).
|
|
116
|
+
*/
|
|
117
|
+
showUserPic?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Request permission to send messages to the user from the bot. Sets
|
|
120
|
+
* `data-request-access="write"`. Default: true.
|
|
121
|
+
*/
|
|
122
|
+
requestWriteAccess?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Called after the widget reports an authenticated user AND the
|
|
125
|
+
* payload has been verified by GameCore server-side (HMAC-SHA256).
|
|
126
|
+
* The `user` object is GameCore's {@link TelegramAuthUser} —
|
|
127
|
+
* the same shape the underlying `POST /auth/telegram/widget`
|
|
128
|
+
* endpoint returns.
|
|
129
|
+
*/
|
|
130
|
+
onAuth: (user: TelegramAuthUser) => void | Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Called if verification fails (expired hash, wrong signature, or
|
|
133
|
+
* network error). When omitted the error is thrown to the browser's
|
|
134
|
+
* unhandled-promise handler.
|
|
135
|
+
*/
|
|
136
|
+
onError?: (err: Error) => void;
|
|
137
|
+
/** Optional referral code to thread through to the signup flow. */
|
|
138
|
+
ref?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Bot username override. Defaults to the value from `gc.site.getConfig()`
|
|
141
|
+
* — only pass this when you have not called getConfig() yet and want
|
|
142
|
+
* to skip that round trip.
|
|
143
|
+
*/
|
|
144
|
+
botUsername?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Options for the high-level {@link GameCoreClient.auth.loginViaTelegramBot}
|
|
148
|
+
* helper. Orchestrates the init → poll flow for users whose Telegram
|
|
149
|
+
* client can't render the Login Widget (third-party mobile clients with
|
|
150
|
+
* no active Telegram Web session, desktop browsers without TG Web
|
|
151
|
+
* logged in, etc).
|
|
152
|
+
*/
|
|
153
|
+
export interface TelegramBotLoginOptions {
|
|
154
|
+
/**
|
|
155
|
+
* Callback that receives the generated bot deep-link. Use it to open
|
|
156
|
+
* a popup, render a QR code, or redirect the user. This is called
|
|
157
|
+
* exactly once per login attempt, as soon as the server returns the
|
|
158
|
+
* link.
|
|
159
|
+
*/
|
|
160
|
+
onBotLinkReady: (botLink: string, token: string) => void;
|
|
161
|
+
/** Poll interval in milliseconds. Default: 2000. */
|
|
162
|
+
pollMs?: number;
|
|
163
|
+
/**
|
|
164
|
+
* Give up after this many milliseconds. Default: 120000 (2 minutes,
|
|
165
|
+
* matches the server-side token TTL).
|
|
166
|
+
*/
|
|
167
|
+
timeoutMs?: number;
|
|
168
|
+
/** Optional referral code to thread through to the signup flow. */
|
|
169
|
+
ref?: string;
|
|
170
|
+
}
|
|
90
171
|
export interface ExchangeRates {
|
|
91
172
|
usdToRub: number;
|
|
92
173
|
base: string;
|
|
@@ -130,8 +211,28 @@ export interface GameDetail {
|
|
|
130
211
|
/** Gallery of product screenshots (resized to 800x600). */
|
|
131
212
|
images?: string[];
|
|
132
213
|
description: string | null;
|
|
214
|
+
/**
|
|
215
|
+
* Short teaser (<= 200 chars) suitable for list cards and meta
|
|
216
|
+
* descriptions. Distinct from `description` which may be multi-paragraph.
|
|
217
|
+
*/
|
|
218
|
+
shortDescription?: string | null;
|
|
133
219
|
/** Editorial badges like "hit", "new", "popular", "sale". */
|
|
134
220
|
tags?: string[];
|
|
221
|
+
/** Game developers (studio names). Empty when supplier doesn't send it. */
|
|
222
|
+
developers?: string[];
|
|
223
|
+
/** Game publishers. Empty when supplier doesn't send it. */
|
|
224
|
+
publishers?: string[];
|
|
225
|
+
/** Genre labels ("Strategy", "RPG", ...). Empty for mobile-donation games. */
|
|
226
|
+
genres?: string[];
|
|
227
|
+
/**
|
|
228
|
+
* Upstream release date as the supplier sends it — may be an ISO date
|
|
229
|
+
* "YYYY-MM-DD", a localized natural-language string ("6 июн. 2016 г."),
|
|
230
|
+
* or null when unknown. Storefronts should render verbatim; do not
|
|
231
|
+
* parse as a reliable Date.
|
|
232
|
+
*/
|
|
233
|
+
releaseDate?: string | null;
|
|
234
|
+
/** Steam app id — null for non-Steam titles and mobile games. */
|
|
235
|
+
steamAppid?: number | null;
|
|
135
236
|
/** Availability lifecycle. Shares the union from {@link Game}. */
|
|
136
237
|
availabilityStatus?: "available" | "coming_soon" | "maintenance" | "discontinued";
|
|
137
238
|
/** Human-readable message for non-available games. */
|