@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 CHANGED
@@ -42,20 +42,62 @@ const results = await gc.catalog.search("roblox");
42
42
 
43
43
  ## Authentication
44
44
 
45
- ### Telegram Auth Flow
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
- // 1. Start auth get bot link
49
- const { token, botLink } = await gc.auth.initTelegram();
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
- // 2. Open Telegram bot (user clicks "Start")
52
- window.open(botLink, "_blank");
66
+ // Later (React unmount, SPA route change):
67
+ cleanup();
68
+ ```
53
69
 
54
- // 3. Poll until user authenticates
55
- const user = await gc.auth.pollTelegramStatus(token);
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 { TelegramInitResponse, TelegramWidgetUser, TelegramAuthResponse, User, SiteConfig, ExchangeRates, LegalDocument, Game, GameDetail, Product, SearchResult, Category, CartItem, CheckoutRequest, CheckoutResponse, Conversation, ConversationDetail, Order, PaymentInfo, CompleteWithBalanceResult, UserBalance, WebPushSubscriptionInput, LevelStatus, Transaction, Notification, Favorite, Review, ReviewCreateResult, ReviewStats, CouponResult, ReferralStats, ReferralLink, ReferralCommission, ReferralPerformance, TopupMethod, TopupResponse, TopupStatus, GiftCard, Announcement, AnnouncementBar, SiteUIConfig, PaymentMethod, CatalogSection, SiteStats, ProductFilters, PaginatedResponse, PagedGamesResponse } from "./types";
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", { currentPassword, newPassword }),
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", { endpoint })
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamecore-api/sdk",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "TypeScript SDK for GameCore API — browser-safe, zero dependencies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",