Перейти к содержимому

Примеры SDK IZI: JavaScript и Python

Опубликовано: · IZI Team

Официального SDK у IZI нет — API стандартный GraphQL и работает с любым HTTP-клиентом. Здесь собраны готовые к использованию классы-обёртки для Node.js и Python с авторизацией, автоматическим обновлением токена и обработкой ошибок.

Окно терминала
npm install graphql-request graphql
izi-client.js
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-handler.js
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.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
izi_client.py
import time
import hmac
import hashlib
from gql import gql, Client
from 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 {})
main.py
import os
from 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_server.py
import hmac
import hashlib
import json
from 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.com
IZI_PASSWORD=your_secure_password
IZI_WEBHOOK_SECRET=your_webhook_secret

Никогда не храните токены и пароли в коде репозитория.

Частые вопросы

Есть ли официальный 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 ниже.