Čo je to?
Poznámka pre administrátora: Toto je príručka pre vývojárov na vytváranie „doplnkov“ – vlastných rozšírení, ktoré pridávajú nové funkcie alebo integrácie na trhovisko vášho e-shopu. Predstavte si to ako manuál na vytváranie vlastných „aplikácií“ pre platformu.
Pre koho je to určené? Administrátori by mali tento dokument preposlať svojmu vývojovému tímu alebo externej agentúre. Je vysoko technický a obsahuje pokyny potrebné na zostavenie, pripojenie a správu vlastného doplnku, ktorý bezproblémovo funguje vo vašom e-shope.
Kľúčové výhody pre e-shop:
- Funkcionalita na mieru: Vytvorte presne tie funkcie, ktoré vaše podnikanie potrebuje, ak ešte nie sú dostupné na štandardnom trhovisku.
- Bezproblémová integrácia: Vlastné doplnky vyzerajú a pôsobia ako súčasť platformy, čím poskytujú konzistentný zážitok pre váš personál.
- Bezpečné pripojenia: Systém automaticky spracováva všetky komplexné bezpečnostné prvky (ako sú „tokeny“), takže vaše vlastné nástroje môžu bezpečne pristupovať k vašim dátam.
- Správa životného cyklu: Jednoducho inštalujte, pozastavujte alebo odstraňujte svoje vlastné nástroje rovnako ako akúkoľvek inú aplikáciu na trhovisku.
API pre vývojárov doplnkov
Táto príručka vysvetľuje, ako vytvoriť doplnok pre trhovisko {productName}. Váš doplnok je v systéme zaregistrovaný so sadou URL adries, ktoré platforma volá v špecifických momentoch životného cyklu – aktivácia, deaktivácia, pozastavenie/obnovenie – a URL adries, ktoré môže používateľ otvoriť pre konfiguráciu a ďalšie zobrazenia.
Prehľad
Záznam verzie doplnku obsahuje nasledujúce polia URL. Každé pole je voliteľné; uveďte len tie, ktoré váš doplnok vyžaduje.
| Pole | Kedy sa volá | Metóda |
|---|---|---|
activationUrl |
Používateľ nainštaluje doplnok | POST |
deactivationUrl |
Používateľ odinštaluje doplnok | POST |
pauseUnpauseUrl |
Používateľ pozastaví alebo obnoví doplnok | POST |
configUrl |
Používateľ klikne na „Konfigurovať“ | Otvorí sa v prehliadači (iframe alebo odkaz) |
statusUrl |
Platforma zisťuje aktuálny stav doplnku | GET |
Okrem toho pole urls obsahuje ďalšie odkazy, ktoré sa zobrazujú v bočnom paneli podrobností doplnku (napr. protokoly, správy). Pozrite si sekciu Zoznam URL adries nižšie.
Verzie doplnkov
Doplnok marketplace má dve úrovne. Samotný doplnok je len jeho identita: kód, názov, kategória, autor, stav a ukazovateľ na jeho aktuálnu verziu. Všetko, čo popisuje, čo doplnok je a ako s ním platforma zaobchádza, sa nachádza vo verzii doplnku: texty, obrázky, cena, všetky vyššie uvedené URL adresy a nastavenia nižšie.
Ak chcete publikovať zmenu, vytvoríte novú verziu a urobíte z nej aktuálnu. Platforma vždy číta aktuálnu verziu — URL adresy životného cyklu, URL adresy stavu a konfigurácie a nastavenia v tabuľke — takže nová verzia ich môže zmeniť (napríklad začať vyžadovať príchuť) bez toho, aby sa dotkla predchádzajúcich verzií.
| Pole (vo verzii) | Význam |
|---|---|
tag |
Odznak zobrazený na karte doplnku v marketplace: new, recommended alebo priceOff. |
singleInstance |
Ak je true, doplnok môže byť nainštalovaný iba raz pre každú organizáciu. Pozrite si Príchute a jedna inštalácia. |
requireFlavours |
Zoznam príchutí, ktoré organizácia musí už mať, aby videla a nainštalovala doplnok. |
providesFlavours |
Zoznam príchutí pridaných do organizácie po aktivácii doplnku. |
`tag`, `singleInstance`, `requireFlavours` a `providesFlavours` boli predtým poliami samotného doplnku. Teraz patria k verzii; existujúce hodnoty boli skopírované do každej verzie doplnku. Keď ich čítate alebo upravujete, vždy používajte verziu.
Príchute a jedna inštalácia
Príchute sú funkčné štítky organizácie (napríklad replica alebo flexibee), ktoré zapínajú časti produktu. Doplnok na nich môže závisieť a rozširovať ich. Obe polia sú JSON polia kľúčov príchutí vo verzii doplnku:
{
"singleInstance": true,
"requireFlavours": ["replica"],
"providesFlavours": ["flexibee"]
}
| Nastavenie | Čo sa stane |
|---|---|
requireFlavours |
Doplnok je skrytý zo zoznamu marketplace pre organizáciu, ktorej chýba niektorá z týchto príchutí. Ak používateľ otvorí jeho detail priamo, upozornenie vysvetlí, čo chýba, a Aktivovať je zakázané. Požiadavka sa kontroluje znova pri aktivácii: ak nie je splnená, inštalácia skončí v stave failed a používateľ dostane chybu. |
providesFlavours |
Po úspešnej aktivácii (s activationUrl alebo bez nej) sa chýbajúce príchute pridajú do organizácie. |
singleInstance |
Jedna inštalácia na organizáciu. Inštalácie v stave not-installed, installed alebo paused blokujú novú; failed a uninstalled neblokujú. Stránka s detailmi zakáže Aktivovať a aktivácia je odmietnutá aj na serveri. |
Keď je doplnok odinštalovaný, odstránia sa iba príchute, ktoré táto inštalácia pridala — nikdy príchute, ktoré organizácia mala predtým. Príchuť zostane aj vtedy, ak ju poskytuje iný nainštalovaný alebo pozastavený doplnok tej istej organizácie.
Použite `requireFlavours` pre doplnok, ktorý má zmysel iba vedľa inej funkcie, a `providesFlavours` pre doplnok, ktorý zapína funkciu. Doplnok, ktorý potrebuje jednu inštaláciu na externý systém (napríklad jednu na databázu), by mal nechať `singleInstance` vypnuté.
API Token
Keď platforma aktivuje váš doplnok, vygeneruje JWT bearer token viazaný na túto konkrétnu inštaláciu. Tento token sa odovzdá vašej activationUrl ako apiToken v aktivačnom payloade a mal by byť bezpečne uložený vašou službou.
Použite ho v následných volaniach späť do {productName} (napr. čítanie alebo zápis dát o produktoch prostredníctvom Query API) ako štandardnú hlavičku Authorization: Bearer <token>.
Token je obmedzený na organizáciu, ktorá nainštalovala doplnok. Má povolenia na čítanie/zápis a je platný neobmedzene. Keď je doplnok odinštalovaný, token automaticky vyprší do 15 minút.
tab: TypeScript
async function fetchProducts(apiToken: string, hostname: string): Promise<any[]> {
const response = await fetch(`{HOSTNAME}/api/query`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: 'products { id, name, price }' }),
});
if (!response.ok) throw new Error(`Query failed: ${response.status}`);
const data = await response.json();
return data.products ?? [];
}
---
tab: PHP
function fetchProducts(string $apiToken): array {
$ch = curl_init("{hostname}/api/query");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiToken",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['query' => 'products { id, name, price }']),
]);
$body = curl_exec($ch);
curl_close($ch);
$data = json_decode($body, true);
return $data['products'] ?? [];
}
URL adresy životného cyklu
activationUrl — Doplnok nainštalovaný
Volá sa raz, keď používateľ nainštaluje váš doplnok. Použite ho na poskytnutie zdrojov (napr. vytvorenie nájomcu/pracovného priestoru vo vašej službe), uloženie apiToken a prípravu akejkoľvek počiatočnej konfigurácie.
Metóda: POST
Očakávaná odpoveď: { "success": true } — akákoľvek neúspešná odpoveď alebo chyba HTTP spôsobí zlyhanie inštalácie.
Do odpovede môžete zahrnúť voliteľné pole `data`. Jeho hodnota (akýkoľvek objekt serializovateľný do JSON) bude preposlaná tak, ako je, do odpovede frontendu, čo je užitočné na ladenie problémov s aktiváciou z vývojárskych nástrojov prehliadača.
```json
{ "success": true, "data": { "workspaceId": "ws-123", "plan": "trial" } }
**Request payload:**
```typescript
interface ActivationPayload {
myAddon_id: string; // Unique ID of this installation
addon_id: string; // Addon definition ID
tenant_id: string; // {productName} tenant ID
organisation_id: string; // Organisation that installed the addon
// API token — store this to authenticate future calls back to {productName}
apiToken: string; // JWT bearer token
access_token: string; // Same as apiToken (alias for compatibility)
api_token_id: string; // Database ID of the token record
// Signing secret — store this to verify ps_token JWTs on incoming configUrl / urls[] / statusUrl requests
psSigningSecret: string;
organisation: {
id: string;
code: string;
name: string;
email: string | null;
vatId: string | null;
registrationNumber: string | null;
taxId: string | null;
defaultLanguage: string; // e.g. "en", "cs"
defaultTimezone: string; // e.g. CET
flavours: Record<string, any> | null;
country: string | null; // ISO country code
};
tenant: {
id: string;
code: string;
name: string;
};
user: {
id: string;
username: string;
firstname: string | null;
surname: string | null;
email: string | null;
telephone: string | null;
jobTitle: string | null;
};
}
tab: TypeScript
import express, { Request, Response } from 'express';
const app = express();
app.use(express.json());
// In-memory store (use a real database in production)
const installations = new Map<string, {
apiToken: string;
psSigningSecret: string;
organisationId: string;
}>();
app.post('/addon/activate', (req: Request, res: Response) => {
const { myAddon_id, apiToken, psSigningSecret, organisation } = req.body;
if (!myAddon_id || !apiToken || !psSigningSecret) {
return res.status(400).json({ success: false, error: 'Missing required fields' });
}
// Store both the API token and the signing secret
installations.set(myAddon_id, {
apiToken,
psSigningSecret,
organisationId: organisation.id,
});
console.log(`Addon installed for org: ${organisation.name} (${organisation.id})`);
res.json({ success: true });
});
---
tab: PHP
// activate.php
$payload = json_decode(file_get_contents('php://input'), true);
$myAddonId = $payload['myAddon_id'] ?? null;
$apiToken = $payload['apiToken'] ?? null;
$psSigningSecret = $payload['psSigningSecret'] ?? null;
$organisation = $payload['organisation'] ?? null;
if (!$myAddonId || !$apiToken || !$psSigningSecret) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Missing required fields']);
exit;
}
// Persist installation (use a real DB in production)
file_put_contents(
__DIR__ . "/installations/$myAddonId.json",
json_encode([
'apiToken' => $apiToken,
'psSigningSecret' => $psSigningSecret,
'organisationId' => $organisation['id'],
])
);
echo json_encode(['success' => true]);
deactivationUrl — Doplnok odinštalovaný
Volá sa, keď používateľ odinštaluje doplnok. Vyčistite poskytnuté zdroje a zneplatnite všetky uložené tokeny.
Metóda: POST
Očakávaná odpoveď: { "success": true } — môžete zahrnúť voliteľné pole data (akýkoľvek objekt JSON), ktoré bude preposlané do odpovede frontendu a je užitočné na ladenie (viditeľné vo vývojárskych nástrojoch prehliadača).
Odpoveď 404 z vášho koncového bodu sa považuje za úspešnú (idempotentná odinštalácia). Ak vrátite neúspešnú odpoveď, platforma zaznamená chybu, ale doplnok stále označí ako odinštalovaný.
Payload požiadavky:
interface DeactivationPayload {
myAddon_id: string;
organisation_id: string;
}
tab: TypeScript
app.post('/addon/deactivate', (req: Request, res: Response) => {
const { myAddon_id } = req.body;
installations.delete(myAddon_id);
console.log(`Addon uninstalled: ${myAddon_id}`);
res.json({ success: true });
});
---
tab: PHP
// deactivate.php
$payload = json_decode(file_get_contents('php://input'), true);
$myAddonId = $payload['myAddon_id'] ?? null;
if ($myAddonId) {
@unlink(__DIR__ . "/installations/$myAddonId.json");
}
echo json_encode(['success' => true]);
pauseUnpauseUrl — Doplnok pozastavený / obnovený
Volá sa, keď používateľ pozastaví alebo obnoví doplnok. Pozastavenie/obnovenie používa rovnakú URL adresu — akciu rozlíšite pomocou poľa action v payloade.
Metóda: POST
Očakávaná odpoveď: { "success": true } — môžete zahrnúť voliteľné pole data (akýkoľvek objekt JSON), ktoré bude preposlané do odpovede frontendu a je užitočné na ladenie (viditeľné vo vývojárskych nástrojoch prehliadača).
Payload požiadavky:
interface PauseUnpausePayload {
myAddon_id: string;
addon_id: string;
tenant_id: string;
organisation_id: string;
action: 'pause' | 'unpause';
organisation: { /* same shape as in activation */ };
tenant: { /* same shape as in activation */ };
// Note: user is NOT included in pause/unpause payloads
}
tab: TypeScript
app.post('/addon/pause-unpause', (req: Request, res: Response) => {
const { myAddon_id, action } = req.body;
if (action !== 'pause' && action !== 'unpause') {
return res.status(400).json({ success: false, error: 'Invalid action' });
}
const installation = installations.get(myAddon_id);
if (!installation) {
return res.status(404).json({ success: false, error: 'Installation not found' });
}
// Pause or resume your background jobs / sync processes here
if (action === 'pause') {
console.log(`Pausing addon ${myAddon_id}`);
} else {
console.log(`Resuming addon ${myAddon_id}`);
}
res.json({ success: true });
});
---
tab: PHP
// pause-unpause.php
$payload = json_decode(file_get_contents('php://input'), true);
$myAddonId = $payload['myAddon_id'] ?? null;
$action = $payload['action'] ?? null;
if (!in_array($action, ['pause', 'unpause'], true)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid action']);
exit;
}
// Store desired state, adjust background jobs, etc.
$state = $action === 'pause' ? 'paused' : 'active';
// ... your business logic here ...
echo json_encode(['success' => true]);
configUrl — Konfiguračné používateľské rozhranie
Konfiguračná URL sa otvorí, keď používateľ klikne na tlačidlo Konfigurovať na stránke detailov doplnku. Nie je to webhook – vaša URL sa otvorí priamo v prehliadači. Môžete si vybrať, ako ju prezentovať pomocou configUrlType.
configUrlType: "link" vs configUrlType: "iframe"
| Typ | Správanie |
|---|---|
link |
URL sa otvorí v novej karte prehliadača. Používateľ opustí používateľské rozhranie {productName}. Vhodné pre komplexné viacstránkové konfiguračné sprievodcov. |
iframe |
URL je vložená do dialógového okna v rámci používateľského rozhrania {productName}. Používateľ zostáva v kontexte. Vhodné pre jednoduché formuláre nastavení. Vaša stránka môže dialógové okno zavrieť sama, pozrite si Zatvorenie konfiguračného dialógového okna. |
Pri použití `iframe` sa uistite, že vaša stránka nastavuje príslušné hlavičky `Content-Security-Policy` a `X-Frame-Options` (alebo ich vynecháva), aby umožnila vloženie. Reštriktívna hlavička `X-Frame-Options: DENY` zabráni načítaniu iframe.
Pred otvorením vašej configUrl v prehliadači platforma pripojí nasledujúce parametre dotazu:
| Parameter | Popis |
|---|---|
ps_token |
Krátkodobý JWT (10 min) podpísaný pomocou psSigningSecret z aktivácie. Použite ho na overenie, či požiadavka pochádza z platformy. |
myAddon_id |
ID inštalácie doplnku. |
organisation_id |
ID organizácie. |
tenant_id |
ID nájomníka. |
user_id |
ID používateľa, ktorý spustil akciu. |
language |
Aktuálny jazyk používateľského rozhrania používateľa (napr. en, cs). |
JWT `ps_token` obsahuje dve tvrdenia: `sub` (`myAddon_id`) a `org` (`organisation_id`). Jednoduché parametre dotazu sú poskytnuté pre pohodlie – vždy overte podpis JWT predtým, ako im budete dôverovať.
Príklad polí verzie doplnku:
{
"configUrl": "https://your-addon.example.com/config",
"configUrlType": "iframe"
}
tab: TypeScript
import jwt from 'jsonwebtoken';
app.get('/config', (req: Request, res: Response) => {
const psConfig = req.query.ps_token as string | undefined;
const myAddonId = req.query.myAddon_id as string | undefined;
if (!psConfig || !myAddonId) {
return res.status(401).send('<p>Missing ps_token</p>');
}
const installation = installations.get(myAddonId);
if (!installation) {
return res.status(403).send('<p>Unknown installation</p>');
}
// Verify signature and expiry using the stored psSigningSecret
try {
jwt.verify(psConfig, installation.psSigningSecret);
} catch {
return res.status(401).send('<p>Invalid or expired ps_token</p>');
}
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Addon Configuration</title>
<style>body { font-family: sans-serif; padding: 1rem; }</style>
</head>
<body>
<h2>Configure Addon</h2>
<form method="POST" action="/config/save">
<input type="hidden" name="myAddon_id" value="${myAddonId}">
<label>Sync interval (minutes):
<input type="number" name="interval" value="30" min="5">
</label>
<button type="submit">Save</button>
</form>
</body>
</html>
`);
});
---
tab: PHP
// config.php
// Requires firebase/php-jwt: composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$psConfig = $_GET['ps_token'] ?? null;
$myAddonId = $_GET['myAddon_id'] ?? null;
if (!$psConfig || !$myAddonId) {
http_response_code(401);
echo '<p>Missing ps_token</p>';
exit;
}
$installationFile = __DIR__ . "/installations/$myAddonId.json";
if (!file_exists($installationFile)) {
http_response_code(403);
echo '<p>Unknown installation</p>';
exit;
}
$installation = json_decode(file_get_contents($installationFile), true);
$psSigningSecret = $installation['psSigningSecret'] ?? null;
// Verify signature and expiry
try {
JWT::decode($psConfig, new Key($psSigningSecret, 'HS256'));
} catch (\Exception $e) {
http_response_code(401);
echo '<p>Invalid or expired ps_token: ' . htmlspecialchars($e->getMessage()) . '</p>';
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Addon Configuration</title>
<style>body { font-family: sans-serif; padding: 1rem; }</style>
</head>
<body>
<h2>Configure Addon</h2>
<form method="POST" action="/config/save.php">
<input type="hidden" name="myAddon_id" value="<?= htmlspecialchars($myAddonId) ?>">
<label>Sync interval (minutes):
<input type="number" name="interval" value="30" min="5">
</label>
<button type="submit">Save</button>
</form>
</body>
</html>
Zatvorenie konfiguračného dialógového okna
S configUrlType: "iframe" môže vaša stránka zavrieť dialógové okno sama, typicky hneď po tom, ako uložila konfiguráciu. Odošlite správu nadradenému oknu:
// after your "save" request succeeded
window.parent.postMessage({ type: 'productsync:addon:close-config', saved: true }, '*');
saved: trueje voliteľné. Keď je nastavené, {productName} po zatvorení zobrazí „Konfigurácia uložená“ — správa zobrazená vo vašej stránke by zmizla spolu s dialógovým oknom.- Odošlite ju iba po úspešnom uložení. Ak uloženie zlyhá, nič neposielajte: dialógové okno zostane otvorené, aby si používateľ mohol prečítať chybu a opraviť formulár.
- Správa je rešpektovaná iba vtedy, ak pochádza zo samotného konfiguračného iframe, bez ohľadu na jej pôvod, takže nič iné nemôže týmto spôsobom zavrieť dialógové okno.
- Bez ohľadu na to, ako sa dialógové okno zavrie — vašou správou, krížikom alebo klávesom Esc — stránka s detailmi doplnku sa znova načíta: stav, priebeh a akcie sa znova načítajú z vášho
statusUrl. Používateľ okamžite vidí účinok novej konfigurácie, takže sa uistite, že vášstatusUrluž odráža to, čo ste práve uložili.
statusUrl — Stav doplnku
Ak poskytnete statusUrl, platforma ju zavolá, aby zobrazila aktuálny stav vášho doplnku používateľovi (zobrazené v bočnom paneli detailov doplnku s farebnou ikonou). Rovnaká odpoveď môže tiež niesť priebeh dlhotrvajúcej úlohy a akčné tlačidlá (napríklad „Synchronizovať teraz“), pozrite si nižšie.
Metóda: GET
Autentifikácia: Pred volaním vašej statusUrl platforma pripojí nasledujúce parametre dotazu:
| Parameter | Popis |
|---|---|
ps_token |
Krátkodobý JWT (10 min) podpísaný pomocou psSigningSecret z aktivácie. Použite ho na overenie, či požiadavka pochádza z platformy. |
myAddon_id |
ID inštalácie doplnku. |
organisation_id |
ID organizácie. |
tenant_id |
ID nájomníka. |
user_id |
ID používateľa, ktorý spustil akciu. |
language |
Aktuálny jazyk používateľského rozhrania používateľa (napr. en, cs). |
JWT `ps_token` obsahuje dve tvrdenia: `sub` (`myAddon_id`) a `org` (`organisation_id`). Jednoduché parametre dotazu sú poskytnuté pre pohodlie – vždy overte podpis JWT predtým, ako im budete dôverovať.
Formát odpovede:
interface AddonStatusResponse {
/** Current state of the addon */
status: 'ok' | 'paused' | 'blocked' | 'invalid-auth' | 'error' | 'unknown';
/** ISO 8601 timestamp of the last successful operation, or null */
lastSuccessAt: string | null;
/** ISO 8601 timestamp of the last failure, or null */
lastFailureAt: string | null;
/**
* Optional human-readable message describing the current state.
* Can be a plain string or a TranslatableString object (language → message map).
*/
statusMessage: string | Record<string, string> | null;
/**
* Optional arbitrary data passed through to the frontend response as-is.
* Not shown in the UI — visible only in the browser's developer tools (Network tab).
* Useful for surfacing diagnostic information without a separate debug endpoint.
*/
data?: Record<string, any>;
/**
* Optional. Progress of a job that is running right now; leave it out (or null) when nothing runs.
* Shown as a progress bar under the status.
*/
progress?: {
/** Short text of what is happening, in the language of the `language` parameter (e.g. "Reading data"). */
phase: string | null;
/** 0-100. Values outside the range are clamped. */
percent: number;
/** Estimated seconds left, or null when unknown. */
etaSeconds: number | null;
} | null;
/** Optional. Buttons shown under the status, see "Actions". */
actions?: AddonAction[];
}
Hodnoty stavu a ich význam:
| Stav | Farba | Význam |
|---|---|---|
ok |
Zelená | Doplnok funguje normálne |
paused |
Žltá | Doplnok je pozastavený používateľom |
blocked |
Oranžová | Spracovanie je zablokované (napr. limit rýchlosti, problém s upstreamom) |
invalid-auth |
Červená | Autentifikácia k externej službe zlyhala |
error |
Červená | Vyskytla sa neočakávaná chyba |
unknown |
Sivá | Stav nemožno určiť, zvyčajne preto, že doplnok ešte nie je nakonfigurovaný. Pridajte statusMessage, ktorý hovorí, čo robiť (napr. “Najprv nakonfigurujte pripojenie”). |
tab: TypeScript
app.get('/addon/status', (req: Request, res: Response) => {
const psConfig = req.query.ps_token as string | undefined;
const myAddonId = req.query.myAddon_id as string | undefined;
if (!psConfig || !myAddonId) {
return res.status(401).json({ error: 'Missing ps_token' });
}
const installation = installations.get(myAddonId);
if (!installation) {
return res.status(403).json({ error: 'Unknown installation' });
}
// Verify signature and expiry using the stored psSigningSecret
try {
jwt.verify(psConfig, installation.psSigningSecret);
} catch {
return res.status(401).json({ error: 'Invalid or expired ps_token' });
}
// Determine your actual status (query your DB, check job queue, etc.)
const isHealthy = checkHealth(myAddonId);
res.json({
status: isHealthy ? 'ok' : 'error',
lastSuccessAt: isHealthy ? new Date().toISOString() : null,
lastFailureAt: isHealthy ? null : new Date().toISOString(),
statusMessage: isHealthy
? { en: 'Running normally', cs: 'Běží normálně' }
: { en: 'Sync failed — check credentials', cs: 'Synchronizace selhala — zkontrolujte přihlašovací údaje' },
});
});
function checkHealth(_myAddonId: string): boolean {
// Your actual health check logic here
return true;
}
---
tab: PHP
// status.php
// Requires firebase/php-jwt: composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$psConfig = $_GET['ps_token'] ?? null;
$myAddonId = $_GET['myAddon_id'] ?? null;
if (!$psConfig || !$myAddonId) {
http_response_code(401);
echo json_encode(['error' => 'Missing ps_token']);
exit;
}
$installationFile = __DIR__ . "/installations/$myAddonId.json";
if (!file_exists($installationFile)) {
http_response_code(403);
echo json_encode(['error' => 'Unknown installation']);
exit;
}
$installation = json_decode(file_get_contents($installationFile), true);
$psSigningSecret = $installation['psSigningSecret'] ?? null;
// Verify signature and expiry
try {
JWT::decode($psConfig, new Key($psSigningSecret, 'HS256'));
} catch (\Exception $e) {
http_response_code(401);
echo json_encode(['error' => 'Invalid or expired ps_token: ' . $e->getMessage()]);
exit;
}
// Return status
header('Content-Type: application/json');
echo json_encode([
'status' => 'ok',
'lastSuccessAt' => date('c'),
'lastFailureAt' => null,
'statusMessage' => [
'en' => 'Running normally',
'cs' => 'Běží normálně',
],
]);
Priebeh dlhotrvajúcej úlohy
Keď váš doplnok spúšťa niečo, čo chvíľu trvá (synchronizácia, export, migrácia), hlásite to v progress, kým beží:
{
"status": "ok",
"lastSuccessAt": "2026-10-08T12:00:00Z",
"lastFailureAt": null,
"statusMessage": null,
"progress": { "phase": "Reading data", "percent": 42, "etaSeconds": 180 }
}
- Platforma zobrazuje pruh s percentami,
fázoua zostávajúcim časom pod stavom. - Kým je otvorený detail doplnku, platforma sa pýta na váš
statusUrlznova každé 4 sekundy, pokiaľ odpoveď obsahujeprogress. Udržujte volanie stavu lacné a po skončení úlohy pošliteprogress: null(alebo ho vynechajte) – pruh zmizne a dotazovanie sa spomalí. - Zabezpečte, aby
percentrástlo len počas behu úlohy; pruh, ktorý skočí späť, vyzerá ako zlyhanie. - Keď sa úloha skončí, nahláste jej výsledok v
status,lastSuccessAt,lastFailureAtastatusMessage.
Akcie
Doplnok môže ponúkať tlačidlá na svojej stránke s detailmi, ako napríklad Synchronizovať teraz alebo Znova spustiť úplnú synchronizáciu. Sú súčasťou odpovede o stave, takže váš doplnok nepotrebuje vlastnú obrazovku. Pridajte ich do actions:
interface AddonAction {
/** Unique within your addon. */
id: string;
/** Text of the button: a string or a language -> text map (see TranslatableString). */
label: string | Record<string, string>;
/** Optional icon name, e.g. 'play' or 'refresh-cw'. Icons the platform does not ship are not drawn. */
icon?: string;
/** Optional: 'default' (primary), 'outline' or 'destructive'. */
variant?: 'default' | 'outline' | 'destructive';
/** Shown but not clickable, e.g. while a run is already in progress. Also refused by the server. */
disabled?: boolean;
/** When set, the user has to confirm first. All texts may be strings or language maps. */
confirm?: { title: string | Record<string, string>; text?: string | Record<string, string>; confirmLabel?: string | Record<string, string> };
/** HTTPS URL the platform POSTs to when the button is clicked. Never sent to the browser. */
url: string;
}
{
"status": "ok",
"lastSuccessAt": "2026-10-08T12:00:00Z",
"lastFailureAt": null,
"statusMessage": null,
"actions": [
{ "id": "sync", "label": { "en": "Synchronize now", "cs": "Synchronizovat teď" }, "icon": "play", "url": "https://addon.example.com/addon/actions/sync" },
{
"id": "full-resync", "label": { "en": "Re-run full synchronization", "cs": "Spustit úplnou synchronizaci znovu" }, "icon": "refresh-cw", "variant": "outline",
"url": "https://addon.example.com/addon/actions/full-resync",
"confirm": { "title": { "en": "Re-run everything?", "cs": "Spustit vše znovu?" }, "text": { "en": "This can take a long time.", "cs": "Může to trvat dlouho." } }
}
]
}
Čo sa stane, keď používateľ klikne na tlačidlo:
- Platforma sa znova spýta na váš
statusUrla vyhľadá akciu podľaid. Spustiť sa môže iba akcia, ktorú váš stav inzeruje práve teraz a ktorá nie jedisabled– prehliadač nikdy neuvádza URL ani príkaz. Chýbajúca akcia odpovie „Neznáma akcia“, zakázaná akcia „Táto akcia nie je momentálne dostupná“. - Ak má akcia
confirm, používateľ najprv potvrdí. - Platforma odošle
POSTs telom JSON{"myAddon_id": "...", "action": "<id>"}naurlakcie. URL je podpísaná presne ako volanie stavu: pripoja sa rovnaké parametre dotazu (ps_token,myAddon_id,organisation_id,tenant_id,user_id,language) aps_tokenoveríte rovnakým spôsobom.urlodkazuje na vašu službu a môže obsahovať premennú šablóny${myAddon.id}. - Akákoľvek odpoveď
2xxje úspech. Čokoľvek iné sa používateľovi zobrazí ako chyba („Doplnok odpovedal HTTP 500“). Platforma čaká na odpoveď maximálne 30 sekúnd, takže dlhú prácu spustite na pozadí, odpovedzte okamžite a hlásite prebiehajúcu úlohu prostredníctvomprogress. - Stránka znova prečíta váš stav, takže tlačidlo, ktoré spustilo úlohu, sa zmení na
disableda zobrazí sa indikátor priebehu.
Platforma nekontroluje rolu používateľa pre externú akciu: každý používateľ, ktorý môže otvoriť detail doplnku, na ňu môže kliknúť. Volanie obsahuje `user_id`; vo svojom koncovom bode rozhodnite, kto môže čo robiť, a nevystavujte deštruktívne akcie bez `confirm`.
tab: TypeScript
// the status answer advertises the buttons; a running job disables them
app.get('/addon/status', (req: Request, res: Response) => {
// ... verify ps_token as above ...
const running = isJobRunning(req.query.myAddon_id as string);
res.json({
status: 'ok',
lastSuccessAt: lastSuccess(),
lastFailureAt: null,
statusMessage: null,
progress: running ? { phase: 'Reading data', percent: jobPercent(), etaSeconds: null } : null,
actions: [
{ id: 'sync', label: { en: 'Synchronize now', cs: 'Synchronizovat teď' }, icon: 'play', disabled: running, url: `${PUBLIC_URL}/addon/actions/sync` },
],
});
});
// the platform POSTs here when the user clicks the button
app.post('/addon/actions/:id', express.json(), (req: Request, res: Response) => {
// ... verify ps_token as above (same query parameters as the status call) ...
if (req.params.id !== req.body.action) return res.status(400).json({ error: 'Action mismatch' });
startJobInBackground(req.body.myAddon_id); // do not wait for the job
res.status(202).json({ started: true }); // any 2xx is success
});
---
tab: PHP
// status.php: the answer advertises the buttons
$running = isJobRunning($myAddonId);
echo json_encode([
'status' => 'ok',
'lastSuccessAt' => lastSuccess(),
'lastFailureAt' => null,
'statusMessage' => null,
'progress' => $running ? ['phase' => 'Reading data', 'percent' => jobPercent(), 'etaSeconds' => null] : null,
'actions' => [[
'id' => 'sync',
'label' => ['en' => 'Synchronize now', 'cs' => 'Synchronizovat teď'],
'icon' => 'play',
'disabled' => $running,
'url' => PUBLIC_URL . '/addon/actions/sync.php',
]],
]);
// actions/sync.php: the platform POSTs here when the user clicks the button
// ... verify ps_token as above (same query parameters as the status call) ...
$body = json_decode(file_get_contents('php://input'), true);
startJobInBackground($body['myAddon_id']); // do not wait for the job
http_response_code(202); // any 2xx is success
echo json_encode(['started' => true]);
Zoznam URL
Okrem configUrl môžete definovať pole dodatočných odkazov (urls), ktoré sa zobrazia v bočnom paneli stránky detailov doplnku. Sú užitočné pre hlboké odkazy do vašej služby, ako sú prehliadače protokolov, správy alebo dashboardy.
{
"urls": [
{
"sortOrder": 1,
"name": {
"en": "Sync Logs",
"cs": "Logy synchronizace"
},
"icon": "list",
"url": "https://your-addon.example.com/logs?addon=${myAddon.id}",
"urlType": "iframe"
},
{
"sortOrder": 2,
"name": {
"en": "Open Dashboard",
"cs": "Otevřít přehled"
},
"icon": "external-link",
"url": "https://your-addon.example.com/dashboard",
"urlType": "link"
}
]
}
Polia:
| Pole | Typ | Popis |
|---|---|---|
sortOrder |
number |
Poradie zobrazenia v bočnom paneli (vzostupne) |
name |
TranslatableString |
Popis tlačidla – objekt s jazykovými kódmi ako kľúčmi |
icon |
string |
Názov ikony (identifikátor ikony Lucide, napr. list, history, settings) |
url |
string |
Cieľová URL. Podporuje premenné šablóny (pozri nižšie). |
urlType |
"link" | "iframe" |
Ako sa URL otvorí (pozri nižšie) |
Premenné šablóny URL
Pole url podporuje nasledujúce premenné šablóny, ktoré sú nahradené na strane servera predtým, ako sa URL odošle do prehliadača:
| Premenná | Nahradené za |
|---|---|
${HOSTNAME} |
Verejná základná URL platformy (napr. https://app.example.com) |
${myAddon.id} |
myAddon_id aktuálnej inštalácie |
Platforma tiež pripojí nasledujúce parametre dotazu ku každej URL pred jej otvorením:
| Parameter | Popis |
|---|---|
ps_token |
Krátkodobý JWT (10 min) podpísaný pomocou psSigningSecret z aktivácie. Použite ho na overenie, či požiadavka pochádza z platformy. |
myAddon_id |
ID inštalácie doplnku. |
organisation_id |
ID organizácie. |
tenant_id |
ID nájomníka. |
user_id |
ID používateľa, ktorý spustil akciu. |
language |
Aktuálny jazyk používateľského rozhrania (napr. en, cs). |
JWT `ps_token` obsahuje dve deklarácie: `sub` (`myAddon_id`) a `org` (`organisation_id`). Jednoduché parametre dotazu sú poskytnuté pre pohodlie – vždy overte podpis JWT predtým, než im budete dôverovať.
urlType: "link" vs urlType: "iframe"
| Typ | Správanie |
|---|---|
link |
Kliknutím na tlačidlo sa URL otvorí v novej karte prehliadača. Používateľ opustí {productName}. |
iframe |
Kliknutím na tlačidlo sa URL otvorí vložená ako panel na stránke podrobností doplnku. Používateľ zostáva v kontexte. |
Použite iframe pre zobrazenia, ktoré sú určené na použitie bez opustenia platformy (napr. denník vyvolaní alebo panel stavu). Použite link, keď je cieľom plnohodnotná externá aplikácia, ktorú nemá zmysel vkladať (napr. váš admin panel).
Preložiteľný reťazec
Niektoré polia (name, shortDescription atď.) podporujú viacero jazykov. Poskytnite objekt JSON s jazykovými značkami IETF ako kľúčmi:
{
"en": "Sync Logs",
"cs": "Logy synchronizace",
"de": "Synchronisierungsprotokolle",
"sk": "Logy synchronizácie",
"pl": "Dzienniki synchronizacji"
}
Platforma vyberie reťazec zodpovedajúci jazyku používateľa, pričom sa vráti k en, ak požadovaný jazyk nie je k dispozícii.
Podporované jazyky: en, cs, de, sk, pl, hu, fr, it, es, ro, hr, ua, pt, sl
Vstavané doplnky
Doplnky, ktoré poskytuje samotný {productName} (napríklad synchronizácie ABRA Flexi a Pohoda), dodržiavajú rovnakú zmluvu ako doplnky, ktoré vytvárate vy, takže používateľ vidí jednu konzistentnú stránku s detailmi doplnku. Rozdiel je len v tom, kde sa práca vykonáva: namiesto adries HTTPS ich polia životného cyklu (activationUrl, deactivationUrl, pauseUnpauseUrl, statusUrl, configUrl) obsahujú hodnotu builtin:, čo znamená „vykonajte to v rámci platformy“. Ich konfiguračná stránka je stránka platformy zobrazená v rovnakom dialógovom okne a zatvára sa rovnakým spôsobom, ako je popísané v Zatvorenie konfiguračného dialógu.
Vo svojom vlastnom doplnku nikdy nepoužívate builtin:. Je to pre vás dôležité ako referencia: čokoľvek zobrazuje vstavaný doplnok – stav, indikátor priebehu, tlačidlá Spustiť a Znova spustiť, Pozastaviť – váš doplnok môže zobraziť prostredníctvom statusUrl, progress, actions a pauseUnpauseUrl.
Formát odpovede
Všetky koncové body životného cyklu (activationUrl, deactivationUrl, pauseUnpauseUrl) musia vrátiť JSON s minimálne booleovskou hodnotou success:`
{ "success": true }
V prípade zlyhania uveďte chybovú správu error:`
{ "success": false, "error": "Failed to provision workspace: quota exceeded" }
Vráťte príslušný stavový kód HTTP:
200— úspech400— chybná požiadavka (neplatné alebo chýbajúce polia dátovej časti)500— neočakávaná chyba servera
Platforma považuje akýkoľvek stav HTTP, ktorý nie je 2xx, alebo odpoveď { "success": false } za zlyhanie. Pre activationUrl to spôsobí zlyhanie inštalácie a jej vrátenie. Pre deactivationUrl sa chyba zaznamená, ale odinštalovanie sa aj tak dokončí. Pre pauseUnpauseUrl sa chyba zaznamená a aktualizácia stavu sa neprejaví.
Ladenie pomocou data
Akákoľvek odpoveď JSON z koncového bodu životného cyklu (activationUrl, deactivationUrl, pauseUnpauseUrl, statusUrl) môže obsahovať voliteľné pole data. Platforma preposiela túto hodnotu nezmenenú do odpovede frontendu, čím ju sprístupňuje v nástrojoch pre vývojárov prehliadača (karta Sieť). Toto je zámerné a predstavuje pohodlný spôsob, ako zobraziť interný stav alebo diagnostické informácie bez potreby samostatného ladiaceho koncového bodu.
// Example: activation response with debug data
{
"success": false,
"error": "Quota exceeded",
"data": {
"currentUsage": 42,
"limit": 40,
"resetAt": "2026-03-01T00:00:00Z"
}
}
Pole data sa nikdy nezobrazuje v používateľskom rozhraní {productName} – je prítomné iba v surových odpovediach API. Neumiestňujte sem citlivé používateľské údaje; považujte ho za diagnostický kanál určený len pre vývojárov.