Примеры SDK IZI: JavaScript и Python
Примеры SDK IZI: JavaScript и Python
Заголовок раздела «Примеры SDK IZI: JavaScript и Python»Официального SDK у IZI нет — API стандартный GraphQL и работает с любым HTTP-клиентом. Здесь собраны готовые к использованию классы-обёртки для Node.js и Python с авторизацией, автоматическим обновлением токена и обработкой ошибок.
JavaScript / Node.js
Заголовок раздела «JavaScript / Node.js»Зависимости
Заголовок раздела «Зависимости»npm install graphql-request graphqlКласс IZIClient
Заголовок раздела «Класс IZIClient»const { GraphQLClient, gql } = require('graphql-request');
const API_URL = 'https://api.izi.is/graphql';
class IZIClient { constructor() { this.client = new GraphQLClient(API_URL); this.accessToken = null; this.refreshToken = null; this.tokenExpiresAt = null; }
async login(email, password) { const mutation = gql` mutation Login($email: String!, $password: String!) { loginWithEmailPassword(email: $email, password: $password) { accessToken refreshToken } } `;
const data = await this.client.request(mutation, { email, password }); this._setTokens(data.loginWithEmailPassword); return data.loginWithEmailPassword; }
async _refreshTokens() { const mutation = gql` mutation Refresh($token: String!) { refreshToken(token: $token) { accessToken refreshToken } } `;
const data = await this.client.request(mutation, { token: this.refreshToken, }); this._setTokens(data.refreshToken); }
_setTokens({ accessToken, refreshToken }) { this.accessToken = accessToken; this.refreshToken = refreshToken; // accessToken живёт 15 минут, обновляем за 2 минуты до истечения this.tokenExpiresAt = Date.now() + 13 * 60 * 1000; this.client.setHeader('Authorization', `Bearer ${accessToken}`); }
async request(query, variables = {}) { // Обновляем токен если скоро истечёт if (this.tokenExpiresAt && Date.now() > this.tokenExpiresAt) { await this._refreshTokens(); }
try { return await this.client.request(query, variables); } catch (error) { const code = error.response?.errors?.[0]?.extensions?.code;
if (code === 'TOKEN_EXPIRED') { await this._refreshTokens(); return await this.client.request(query, variables); }
throw error; } }}
module.exports = IZIClient;Использование
Заголовок раздела «Использование»const IZIClient = require('./izi-client');const { gql } = require('graphql-request');
async function main() { const izi = new IZIClient();
await izi.login( process.env.IZI_EMAIL, process.env.IZI_PASSWORD );
// Получить список клубов const { clubs } = await izi.request(gql` query GetClubs { clubs { id name timezone } } `);
console.log('Клубы:', clubs);
// Получить активные сессии const { activeSessions } = await izi.request( gql` query ActiveSessions($clubId: ID!) { activeSessions(clubId: $clubId) { id deviceName clientName startedAt balance } } `, { clubId: clubs[0].id } );
console.log('Активных сессий:', activeSessions.length);}
main().catch(console.error);Обработчик webhook
Заголовок раздела «Обработчик webhook»const express = require('express');const crypto = require('crypto');
const app = express();const WEBHOOK_SECRET = process.env.IZI_WEBHOOK_SECRET;
app.post( '/izi/events', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-izi-signature']; const expected = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(req.body) .digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return res.status(401).json({ error: 'Invalid signature' }); }
const event = JSON.parse(req.body); console.log(`Event: ${event.type}`, event.data);
// Обработка по типу события switch (event.type) { case 'SESSION_ENDED': handleSessionEnded(event.data); break; case 'BALANCE_TOPPED_UP': handleTopUp(event.data); break; }
res.status(200).send('OK'); });
app.listen(3000, () => console.log('Webhook server on :3000'));Apollo Client (React / Next.js)
Заголовок раздела «Apollo Client (React / Next.js)»import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';import { setContext } from '@apollo/client/link/context';
const httpLink = createHttpLink({ uri: 'https://api.izi.is/graphql',});
const authLink = setContext((_, { headers }) => { const token = localStorage.getItem('izi_access_token'); return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '', }, };});
export const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(),});Зависимости
Заголовок раздела «Зависимости»pip install gql[httpx] httpxКласс IZIClient
Заголовок раздела «Класс IZIClient»import timeimport hmacimport hashlibfrom gql import gql, Clientfrom gql.transport.httpx import HTTPXTransport
API_URL = "https://api.izi.is/graphql"
class IZIClient: def __init__(self): self.access_token = None self.refresh_token = None self.token_expires_at = 0 self._transport = None self._client = None
def _build_client(self): headers = {} if self.access_token: headers["Authorization"] = f"Bearer {self.access_token}"
self._transport = HTTPXTransport(url=API_URL, headers=headers) self._client = Client(transport=self._transport, fetch_schema_from_transport=False)
def login(self, email: str, password: str) -> dict: self._build_client() mutation = gql(""" mutation Login($email: String!, $password: String!) { loginWithEmailPassword(email: $email, password: $password) { accessToken refreshToken } } """)
result = self._client.execute(mutation, {"email": email, "password": password}) tokens = result["loginWithEmailPassword"] self._set_tokens(tokens) return tokens
def _refresh_tokens(self): self._build_client() mutation = gql(""" mutation Refresh($token: String!) { refreshToken(token: $token) { accessToken refreshToken } } """) result = self._client.execute(mutation, {"token": self.refresh_token}) self._set_tokens(result["refreshToken"])
def _set_tokens(self, tokens: dict): self.access_token = tokens["accessToken"] self.refresh_token = tokens["refreshToken"] # Обновляем за 2 минуты до истечения (TTL 15 мин) self.token_expires_at = time.time() + 13 * 60 self._build_client()
def request(self, query_str: str, variables: dict = None) -> dict: if self.token_expires_at and time.time() > self.token_expires_at: self._refresh_tokens()
query = gql(query_str) return self._client.execute(query, variables or {})Использование
Заголовок раздела «Использование»import osfrom izi_client import IZIClient
def main(): izi = IZIClient() izi.login( email=os.environ["IZI_EMAIL"], password=os.environ["IZI_PASSWORD"] )
# Получить клубы result = izi.request(""" query GetClubs { clubs { id name timezone } } """) clubs = result["clubs"] print(f"Клубов: {len(clubs)}")
# Активные сессии по первому клубу sessions = izi.request(""" query ActiveSessions($clubId: ID!) { activeSessions(clubId: $clubId) { id deviceName clientName startedAt } } """, {"clubId": clubs[0]["id"]})
print(f"Активных сессий: {len(sessions['activeSessions'])}")
if __name__ == "__main__": main()Верификация webhook на Python
Заголовок раздела «Верификация webhook на Python»import hmacimport hashlibimport jsonfrom flask import Flask, request, abort
app = Flask(__name__)WEBHOOK_SECRET = os.environ["IZI_WEBHOOK_SECRET"]
def verify_signature(payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected)
@app.post("/izi/events")def handle_event(): signature = request.headers.get("X-IZI-Signature", "")
if not verify_signature(request.data, signature, WEBHOOK_SECRET): abort(401)
event = request.get_json(force=True) print(f"Event: {event['type']}", event.get("data"))
match event["type"]: case "SESSION_ENDED": handle_session_ended(event["data"]) case "BALANCE_TOPPED_UP": handle_top_up(event["data"])
return "OK", 200Переменные окружения
Заголовок раздела «Переменные окружения»Для обоих примеров используйте .env файл:
IZI_EMAIL=partner@example.comIZI_PASSWORD=your_secure_passwordIZI_WEBHOOK_SECRET=your_webhook_secretНикогда не храните токены и пароли в коде репозитория.
Связанные страницы
Заголовок раздела «Связанные страницы»- IZI API: обзор — общая архитектура
- Авторизация и токены — детали JWT-авторизации
- Webhooks — полный гайд по вебхукам
- Коды ошибок API — справочник кодов ошибок
См. также
Заголовок раздела «См. также»Частые вопросы
Есть ли официальный SDK от IZI?
Официального пакета нет. IZI API — стандартный GraphQL, поэтому работает с любой GraphQL-библиотекой: graphql-request, Apollo Client, gql + httpx. Примеры ниже покрывают основные сценарии.
Какие зависимости нужны для Node.js интеграции?
Минимально — graphql-request. Для продакшена добавьте node-cron (ротация токенов) и pino или winston для логирования.
Как передать токен в Apollo Client?
Используйте authLink из @apollo/client/link/context. Пример есть в разделе Apollo Client ниже.