SPWorlds • SPWorlds API • Discord |
Установка • Клиент • Сервер • Типы • Безопасность |
SPWMini - Библиотека для создания мини приложений для сайта SPWorlds.
Она состоит из клиентской части, серверной части и набора типов.
Ты умеешь устанавливать пакеты своим любимым менеджером пакетов. Наш называется spwmini
.
npm install spwmini
yarn add spwmini
pnpm install spwmini
Пакет предоставляет импорт класса SPWMini
из spwmini/client
import SPWMini from 'spwmini/client';
Инициализация класса требует обязательный аргумент - ID приложения, который можно взять со страницы /[server]/apps/
[id-приложения].
const spm = new SPWMini('123e4567-e89b-12d3-a456-426655440000');
При работе с фреймворками, опционально, можно отключить автоматическую инициализацию, чтобы предупредить работу с window
до появления возможности его использования.
Метод initialize
позволит произвести инициализацию в нужный момент, а метод dispose
- удалить все хендлеры по необходимости.
const spm = new SPWMini('123e4567-e89b-12d3-a456-426655440000', {
autoinit: false
});
onMounted(() => spm.initialize());
onUnmounted(() => spm.dispose());
Так как для валидации данных о пользователе требуется запрос к серверу, отдельной опцией может быть кастомизирован и метод fetch
.
const spm = new SPWMini('123e4567-e89b-12d3-a456-426655440000', {
customFetch: ofetch
});
Во время инициализации сразу же на SPWorlds отправляется запрос init
с id приложения.
Если id запущенного приложения и вашего совпадут, в ответ будет отправлено сообщение initResponse
с данными о пользователе. Иначе - initError
с данными об ошибке.
Все сообщения прослушиваются с помощью метода on
или addEventListener
.
spm.on('initResponse', user => {
console.log(`Logged in as ${user.username} / ${user.minecraftUUID}`);
});
spm.on('initError', message => console.error(`Log in error: ${message}`));
Сразу после инициализации и события ready
данные о пользователе доступны как параметр user
.
spm.on('ready', () => {
console.log('App is ready!');
console.log('Current user:', spm.user);
})
Все описанные далее методы будут работать только после инициализации приложения (после события initResponse
или ready
).
Так как эти данные может легко подделать любой пользователь, предоставлен и метод валидации информации, отправляющий запрос к API и тесно связанный с серверной частью этого пакета.
Метод validateUser
принимает URL и опциональные параметры fetch
. Он делает запрос к бекенду, передавая информацию о пользователе.
Бэкенд должен вернуть простой текстовый ответ, содержащий число 1
в случае, если всё хорошо и 0
в случае, если пользователь подделан. Подробнее в секции про серверную часть.
const isUserValid = await spm.validateUser('/validate');
const isUserValid = await spm.validateUser('/validate', { credentials: 'include' });
Для открытия ссылки в новом окне используется метод openURL
. Он принимает только ссылки с протоколом https://
. Вызов метода с валидной ссылкой открывает диалоговое окно, предлагающее пользователю перейти по ссылке.
spm.openURL('https://google.com');
В случае успеха будет отправлен ответ openURLResponse
со строкой success
. Любую ошибку открытия можно поймать событием openURLError
и получить строку с информацией о ней.
spm.on('openURLResponse', () => console.log('Окно открытия URL успешно открыто'));
spm.on('openURLError', err => console.error(`Ошибка запроса окна открытия URL: ${err}`));
Открытие окна оплаты требует создание заранее транзакции и получение кода от API SPWorlds. Затем, достаточно передать этот код в метод openPayment
и у пользователя откроется окно оплаты.
const payment = await fetch('/api/buyPremium').then(r => r.json());
spm.openPayment(payment.code);
Warning
Для корректной работы, создаваться транзация должна с redirectUri
со значением #MINIAPP
Да, нужно на сервере самостоятельно создать транзакцию. Для этого можно воспользоваться либо эндпоинтом API SPWorlds напрямую, либо одной из библиотек, созданных коммьюнити.
Оплата состоит из двух этапов, так что и пар событий две: открытие окна оплаты и сама оплата.
Событие openPaymentReponse
со строкой success
отправляется при удачном открытии окна, иначе - openPaymentError
со строкой, содержащей информацию об ошибке. Например, транзакция по коду уже оплачена или срок оплаты истёк.
spm.on('openPaymentReponse', () => console.log('Окно успешно открыто, ждём оплату'));
spm.on('openPaymentError', err => console.error(`Не удалось открыть окно оплаты: ${err}`));
Событие paymentResponse
со строкой success
отправляется при удачной оплате пользователем товара. Это не значит, что нужно верить, что товар оплачен, стоит проверить это и на бэкенде, но это позволит фронтенду знать, что проверить уже пора. (Хотя, наверное, перед проверкой стоит подождать секунду-другую, пока хук до твоего бекенда дойдёт и обработается)
spm.on('paymentReponse', async () => {
console.log('Оплата успешно произведена');
const premiumStatus = await fetch('/api/premium').then(r => r.json());
if (premiumStatus.active)
user.showShinyBadge = true;
});
В случае же ошибки оплаты, например, если средств недостаточно, это будет отправлено событием paymentError
в виде строки, как и в случае любого другого события ошибки.
spm.on('openPaymentError', err => console.error(`Оплатить не удалось! Ошибка: ${err}`));
В случае ошибки новую транзакцию создавать не нужно. Стоит предложить пользователю попробовать оплатить ещё раз и запросить открытие окна оплаты с тем же кодом.
Из spwmini/middleware
могут быть импортированы функции, помогающие с валидацией пользователя.
Функция spwmValidate
принимает как обязательный аргумент токен приложения и как опциональный - настройки. Возвращает она другую функцию, уже используемую как посредник.
Токен выдаётся сразу после создания приложения и показывается только один раз. Чтобы получить новый, потребуется удалить приложение, создать его заново и придётся заново проходить модерацию.
Возвращаемая функция-посредник принимает http2 запрос и http2 ответ аргументами, а потому может быть встроена посредником как в http2 сервер, так в express.js сервер и иные.
Для корректной работы в случае с express.js, она должна быть встроена как можно раньше, до применения трансформации тела запроса в json, так как она обрабатывает его самостоятельно.
Наверное это нужно переделать, сделать лучше, но неизвестно как. Контрибуторство приветствуется.
import express from 'express';
import { validate } from 'spwmini/middleware';
const app = express();
app.use('/validate', validate('SECRET_TOKEN'));
app.use(express.json());
После подобного применения посредника, его URL можно указать в клиентской части для проверки пользователя.
Единственной настройкой этой функции является отключение проверки метода. По умолчанию функция принимает тол
8000
ко POST
запросы, отклоняя любые другие методы. Указав для checkPostMethod
значение false
, можно позволить посреднику принимать вообще любые методы.
// Принимает совершенно любые методы
app.use('/validate', validate('SECRET_TOKEN', { checkPostMethod: false }));
// Принимает только метод PUT
app.put('/validate', validate('SECRET_TOKEN', { checkPostMethod: false }));
// Отклоняет PUT запросы с ошибкой, другие методы не принимает
app.put('/validate', validate('SECRET_TOKEN'));
Функция checkUser
принимает пользователя, имеющегося на клиенте после инициализации, и секретный токен приложения. Возвращается логическое значение, означающее валидность информации о пользователе, что ни одна часть структуры не подменена.
import express from 'express';
import { checkUser } from 'spwmini/middleware';
const app = express();
app.use(express.json());
app.use('/validate-user', (req, res) => {
if (!req.body.user || typeof req.body.user !== 'object')
return res.status(400).send({ message: "Invalid user provided" });
res.send({ valid: checkUser(req.body.user) })
});
Все типы импортируются из spwmini/types
.
«Это немного, но это честная работа».
Чтобы ваш сайт невозможно было использовать внутри других сайтов, кроме spworlds, нужно написать в <head>
элементе:
<meta http-equiv="Content-Security-Policy" content="frame-ancestors https://spworlds.ru;">
Благодаря одной лишь этой строчке вы легко сделаете свой сайт безопаснее.
После https://spworlds.ru
через пробел можно указать и свой сайт, если это где-то требуется для тестирования:
<meta http-equiv="Content-Security-Policy" content="frame-ancestors https://spworlds.ru https://example.com;">
А если есть возможность модифицировать заголовки запросов, можно для фронтенда сайта указать
X-Frame-Options: ALLOW-FROM https://spworlds.ru https://example.com