Strony publiczne
Strona publiczna to widok OVE dostępny bez logowania, np. karta zamówienia dla klienta sklepu, do której prowadzi link z maila. Moduł rejestruje ją przez Pages.registerPublicPages (od SDK 4.5.0), a adres składa platforma:
https://orbi.do/p/<instanceId>/<pageCode>/?param=wartość
<instanceId>to identyfikator instancji modułu, która zarejestrowała stronę. Firma nie występuje w adresie — wynika z instancji.<pageCode>to kod strony: jeden segment[a-z0-9-], do 40 znaków ('/track'w deklaracji topageCode = 'track').- Dane wejściowe strona dostaje wyłącznie w query (w widoku
{{ $page.urlParams.param }}). Parametrów w ścieżce nie ma.
Strona publiczna działa na osobnej domenie orbi.do, więc nie ma dostępu do sesji pracowników zalogowanych w panelu (my.orbi.do). Nie ma przełącznika w panelu firmy — strona działa od aktualizacji modułu, a po odinstalowaniu instancji znika.
const { Pages } = require('@orbito/api-lib');
Pages.registerPublicPages
Pages.registerPublicPages(pages)
| Parametr | Typ | Opis |
|---|---|---|
pages | object | Mapa '/pageCode' → definicja strony (wiodący / jest opcjonalny) |
Definicja strony:
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
name | string | object | ✔ | Nazwa strony w nagłówku: string albo mapa językowa { pl, en } |
view | string | ✔ | Ścieżka do pliku widoku .yaml/.yml/.json, jak w registerPages |
placement i moduleDependencies są tu błędem — strona publiczna nie trafia do żadnego menu. Deklaracja strony nie wylicza metod: to, co strona może wywołać, określają flagi public: true na metodach (patrz niżej).
Rejestracja działa jak registerPages: można wołać wielokrotnie, platforma robi replace-set (strony, których moduł przestał deklarować, usuwa), a po reconnect zestaw idzie ponownie. Błędna definicja odrzuca całe wywołanie, zanim cokolwiek zmieni. Funkcja zwraca Promise<{ registered, removed }>.
Pages.registerPublicPages({
'/track': {
name: { pl: 'Twoje zamówienie', en: 'Your order' },
view: 'pages/public/track.yaml'
}
});
Metody publiczne
Strona publiczna woła zwykłe metody modułu, ale tylko te zarejestrowane z public: true w formie opisowej Core.registerMethod. Skrócona forma (sama funkcja) nigdy nie jest publiczna. Każde wywołanie innej metody kończy się odpowiedzią 403 z kodem METHOD_NOT_PUBLIC, zanim platforma dotknie modułu.
Metoda publiczna dostaje kontekst bez użytkownika:
ctx = {
public: true,
userId: null,
permissions: [],
caller: null,
origin: null,
publicPage: { instanceId, pageCode }
}
ctx.publicPage.pageCode pochodzi z nagłówka wysyłanego przez przeglądarkę i nie jest weryfikowany — nie opieraj na nim autoryzacji. Metody używające Core.hasPermission(code, ctx.permissions) odmówią same (puste uprawnienia).
Strona może wołać metody własnej instancji, a także metody publiczne innych instancji tej samej firmy (po kodzie modułu albo identyfikatorze instancji, jak w callMethod / callInstanceMethod). Instancja spoza firmy to 404.
Waliduj wejście i zwracaj minimum
Platforma nie waliduje params (schemat służy tylko dokumentacji), a metodę publiczną może wywołać każdy, kto zna adres — także z innymi parametrami niż te, które wysyła Twoja strona. Sprawdzaj każde wejście w handlerze i zwracaj zamkniętą listę pól przeznaczonych dla odbiorcy strony, nigdy całego dokumentu z bazy.
Klucz w adresie strony ma być losowy i nieodgadywalny — uuid v4 wygenerowany przy tworzeniu rekordu (np. order.uuid) albo długi token, a nie _id z bazy, bo sąsiednie ObjectId da się odgadnąć. Metoda waliduje format klucza przed zapytaniem do bazy i szuka po tym polu, a nie po _id. Zwraca zamkniętą listę pól bez danych osobowych, bo link bywa przekazywany dalej.
const { Core } = require('@orbito/api-lib');
const ORDER_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
Core.registerMethod('public.getOrder', {
description: 'Publiczna karta zamówienia dla klienta sklepu (bez danych osobowych).',
public: true,
params: { t: { type: 'string' } },
handler: async ({ t } = {}) => {
if (typeof t !== 'string' || !ORDER_UUID_RE.test(t)) {
throw new Error('ORDER_NOT_FOUND');
}
const order = await Order.findOne({ uuid: t, isRemoved: { $ne: true } });
if (!order) {
throw new Error('ORDER_NOT_FOUND');
}
// tylko pola dla klienta: bez adresu, e-maila, telefonu, nazwiska i uwag
return {
number: order.name,
status: { name: order.statusName, color: order.statusColor },
total: order.summary?.gross,
currency: order.currency || 'PLN'
};
}
});
Widok strony publicznej
Plik widoku to zwykły widok OVE. Parametr z adresu czytasz przez $page.urlParams, dane pobierasz metodą publiczną (dynamic z module i method). Fragment pages/public/track.yaml z core.orders:
type: dynamic
id: trackOrder
module: core/orders
method: public.getOrder
params:
t: '{{ $page.urlParams.t }}'
loadingMessage: Ładowanie zamówienia...
failSoft: false
fetchErrorMessage: Nie znaleziono zamówienia. Sprawdź, czy link jest kompletny.
template:
type: card
title:
type: text
content: 'Zamówienie {{ trackOrder.number }}'
content:
- type: badge
content: '{{ trackOrder.status.name }}'
color: '{{ trackOrder.status.color }}'
light: true
- type: text
content: 'Razem: {{ money trackOrder.total }} {{ trackOrder.currency }}'
weight: bold
align: end
Ograniczenia trybu publicznego
Strona publiczna jest renderowana w trybie ograniczonym:
- widoki prezentacyjne i formularze działają normalnie; akcja
callMethodwywołuje tylko metodypublic: true; - widoki plików (
image,filePreview,filePicker) i pliki modułów są zablokowane, podobnie jak scope$global; - akcja
navigateprowadzi wyłącznie do stron publicznych tej samej instancji;redirectna adres http(s) jest dozwolony; - zmienne strony nie są zapisywane w
localStorage; - język wybiera parametr
?lang=(np.?lang=en), a gdy go brak — język przeglądarki. Nazwa strony jako mapa{ pl, en }jest tłumaczona; treśćcontentw OVE nie przyjmuje map językowych, więc teksty widoku piszesz w jednym języku; - nagłówek strony to jej nazwa i nazwa firmy, bez logo; strony mają
X-Robots-Tag: noindex.
Pages.publicUrl
Zwraca pełny adres strony zarejestrowanej w tej instancji. Adres składa platforma — moduł nie zna formatu ani domeny i nie powinien go składać ręcznie.
Pages.publicUrl(pageCode, query?)
| Parametr | Typ | Opis |
|---|---|---|
pageCode | string | Kod strony, z wiodącym / albo bez ('/track', 'track') |
query | object | Parametry query, np. { t: order.uuid }; wartości: string, liczba lub boolean |
Zwraca Promise<string>, np. https://orbi.do/p/<instanceId>/track/?t=<uuid>. Promise jest odrzucany, gdy kod jest niepoprawny albo strona nie została zarejestrowana.
Typowe zastosowanie to zmienna szablonu wiadomości z linkiem do strony. Fragment z core.orders — brak adresu nie może wywrócić całej wiadomości, więc zmienna zostaje wtedy pusta:
async function trackingUrl(order) {
try {
return await Pages.publicUrl('/track', { t: order.uuid });
} catch (err) {
Log.warn('Nie udało się zbudować adresu strony zamówienia', { error: err.message });
return '';
}
}
Limity
Strony publiczne i ich metody są chronione limiterem w rdzeniu:
| Limit | Wartość |
|---|---|
| Na adres IP klienta | 120 żądań / min |
| Na instancję | 600 żądań / min |
Po przekroczeniu platforma odpowiada 429 RATE_LIMITED z nagłówkiem Retry-After. Strona jest wtedy chwilowo niedostępna — pamiętaj, że limit na instancję dzielą wszyscy odwiedzający, więc nie projektuj stron, które w tle odpytują metodę w pętli. Odpowiedzi mają Cache-Control: no-store.
Bezpieczeństwo
Identyfikator w adresie jest jedynym „kluczem"
Strona publiczna nie ma logowania — dostęp wyznacza wyłącznie znajomość adresu. Dlatego:
- zwracaj minimum danych i żadnych danych osobowych (imię, adres, e-mail, telefon, uwagi klienta);
- waliduj każdy parametr w metodzie publicznej i na nieistniejący lub niepoprawny identyfikator odpowiadaj tym samym błędem, żeby nie zdradzać, co istnieje;
- nie używaj
_idz bazy jako klucza w adresie —ObjectIdjest odgadywalny (sąsiednie wartości różnią się nieznacznie); wstawiaj losowy klucz, patrz Metody publiczne.core.ordersużywaorder.uuidpod parametremt; - nie zakładaj, że
publicPage.pageCodemówi, skąd przyszło wywołanie — zgłasza go klient.
