Skip to content

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

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

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

Terminal window
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(),
});

Terminal window
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 файл:

Terminal window
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 ниже.