Publikowanie z JavaScript¶
Wysyłaj powiadomienia do tematów Notifer używając JavaScript (przeglądarka) i Node.js z wbudowanym fetch.
Szybki start¶
Przeglądarka (fetch)¶
fetch('https://app.notifer.io/my-topic', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: 'Twoja wiadomość tutaj'
})
.then(res => res.json())
.then(data => console.log(data));
Node.js (fetch)¶
// Node.js 18+ ma wbudowany fetch
const response = await fetch('https://app.notifer.io/my-topic', {
method: 'POST',
body: 'Twoja wiadomość tutaj'
});
const data = await response.json();
console.log(data);
Wynik:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"topic": "my-topic",
"message": "Twoja wiadomość tutaj",
"priority": 3,
"created_at": "2025-11-22T10:30:00Z"
}
Instalacja¶
Przeglądarka¶
Nie jest wymagana instalacja - fetch jest wbudowany w nowoczesne przeglądarki.
Node.js¶
Node.js 18+: Wbudowane API fetch
Node.js < 18: Zainstaluj node-fetch
Podstawowe publikowanie¶
Prosta wiadomość¶
async function sendNotification(topic, message) {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Użycie
const result = await sendNotification('server-alerts', 'Serwer nie działa!');
console.log(`Message ID: ${result.id}`);
Z tytułem i priorytetem¶
async function sendAlert(topic, title, message, priority = 3) {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers: {
'X-Title': title,
'X-Priority': priority.toString()
},
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Użycie
await sendAlert(
'production-alerts',
'Błąd bazy danych',
'Przekroczono limit czasu połączenia na prod-db-01',
5
);
Z tagami¶
async function sendTaggedMessage(topic, message, tags, priority = 3) {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers: {
'X-Priority': priority.toString(),
'X-Tags': tags.join(',')
},
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Użycie
await sendTaggedMessage(
'monitoring',
'Użycie CPU na poziomie 95%',
['warning', 'cpu', 'prod-web-01'],
4
);
Funkcje zaawansowane¶
Formatowanie Markdown¶
async function sendMarkdownMessage(topic, message, title = null, priority = 3) {
const headers = {
'X-Markdown': 'true',
'X-Priority': priority.toString()
};
if (title) {
headers['X-Title'] = title;
}
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers,
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Użycie
const markdownMessage = `
## Podsumowanie wdrożenia
**Status:** ✅ Sukces
**Wersja:** v2.1.0
**Czas trwania:** 3m 45s
### Zmiany
- Dodano uwierzytelnianie użytkownika
- Naprawiono błąd płatności
- Zaktualizowano zależności
[Zobacz informacje o wydaniu](https://github.com/example/repo/releases/v2.1.0)
`;
await sendMarkdownMessage(
'deployments',
markdownMessage,
'Deploy v2.1.0',
3
);
Prywatne tematy z uwierzytelnianiem¶
class NotiferClient {
constructor(apiKey = null, jwtToken = null) {
this.baseUrl = 'https://app.notifer.io';
this.headers = {};
if (apiKey) {
this.headers['Authorization'] = `Bearer ${apiKey}`;
} else if (jwtToken) {
this.headers['Authorization'] = `Bearer ${jwtToken}`;
}
}
async publish(topic, message, options = {}) {
const {
title = null,
priority = 3,
tags = null,
markdown = false
} = options;
const headers = { ...this.headers };
headers['X-Priority'] = priority.toString();
if (title) {
headers['X-Title'] = title;
}
if (tags) {
headers['X-Tags'] = tags.join(',');
}
if (markdown) {
headers['X-Markdown'] = 'true';
}
const response = await fetch(`${this.baseUrl}/${topic}`, {
method: 'POST',
headers,
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
}
// Użycie
const client = new NotiferClient('your-api-key-here');
await client.publish(
'private-alerts',
'Utracono połączenie z bazą danych',
{
title: 'Krytyczny problem',
priority: 5,
tags: ['critical', 'database']
}
);
Przykłady z rzeczywistości¶
Śledzenie błędów frontend¶
class ErrorTracker {
constructor(topic = 'frontend-errors', apiKey = null) {
this.topic = topic;
this.baseUrl = 'https://app.notifer.io';
this.apiKey = apiKey;
}
async logError(error, context = {}) {
const errorType = error.name;
const errorMessage = error.message;
const stackTrace = error.stack || 'No stack trace available';
const message = `
## ${errorType}
**Błąd:** ${errorMessage}
**Strona:** ${window.location.href}
**User Agent:** ${navigator.userAgent}
**Kontekst:**
${JSON.stringify(context, null, 2)}
**Stack Trace:**
\`\`\`
${stackTrace}
\`\`\`
`;
try {
const headers = {
'X-Title': `Error: ${errorType}`,
'X-Priority': '5',
'X-Tags': `error,${errorType.toLowerCase()},frontend`,
'X-Markdown': 'true'
};
if (this.apiKey) {
headers['Authorization'] = `Bearer ${this.apiKey}`;
}
await fetch(`${this.baseUrl}/${this.topic}`, {
method: 'POST',
headers,
body: message
});
} catch (e) {
// Nie pozwól, aby błąd powiadomienia zepsuł aplikację
console.error('Failed to log error to Notifer:', e);
}
}
}
// Konfiguracja globalnej obsługi błędów
const tracker = new ErrorTracker('frontend-errors');
window.addEventListener('error', (event) => {
tracker.logError(event.error, {
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});
window.addEventListener('unhandledrejection', (event) => {
tracker.logError(new Error(event.reason), {
type: 'unhandled-promise-rejection'
});
});
Monitorowanie serwera Node.js¶
import os from 'os';
class ServerMonitor {
constructor(topic = 'server-monitoring') {
this.topic = topic;
this.cpuThreshold = 80;
this.memoryThreshold = 85;
}
getCPUUsage() {
const cpus = os.cpus();
let totalIdle = 0;
let totalTick = 0;
cpus.forEach(cpu => {
for (const type in cpu.times) {
totalTick += cpu.times[type];
}
totalIdle += cpu.times.idle;
});
return 100 - ~~(100 * totalIdle / totalTick);
}
getMemoryUsage() {
const totalMem = os.totalmem();
const freeMem = os.freemem();
return ((totalMem - freeMem) / totalMem) * 100;
}
async sendAlert(title, message, priority = 4) {
try {
await fetch(`https://app.notifer.io/${this.topic}`, {
method: 'POST',
headers: {
'X-Title': title,
'X-Priority': priority.toString(),
'X-Tags': 'monitoring,server'
},
body: message
});
} catch (error) {
console.error('Failed to send alert:', error);
}
}
async checkSystem() {
const cpuPercent = this.getCPUUsage();
const memoryPercent = this.getMemoryUsage().toFixed(2);
if (cpuPercent > this.cpuThreshold) {
await this.sendAlert(
`Wysokie użycie CPU: ${cpuPercent}%`,
`Użycie CPU wynosi ${cpuPercent}% (próg: ${this.cpuThreshold}%)`,
4
);
}
if (memoryPercent > this.memoryThreshold) {
await this.sendAlert(
`Wysokie użycie pamięci: ${memoryPercent}%`,
`Użycie pamięci wynosi ${memoryPercent}% (próg: ${this.memoryThreshold}%)`,
4
);
}
}
start(intervalMs = 60000) {
console.log('Starting server monitoring...');
this.checkSystem(); // Początkowe sprawdzenie
setInterval(() => this.checkSystem(), intervalMs);
}
}
// Użycie
const monitor = new ServerMonitor();
monitor.start(); // Sprawdzaj co minutę
Integracja Express.js API¶
import express from 'express';
const app = express();
class NotiferLogger {
constructor(topic, apiKey) {
this.topic = topic;
this.apiKey = apiKey;
}
async log(level, message, metadata = {}) {
const priority = {
'debug': 1,
'info': 2,
'warn': 4,
'error': 5
}[level] || 3;
const formattedMessage = `
**Poziom:** ${level.toUpperCase()}
**Znacznik czasu:** ${new Date().toISOString()}
${message}
**Metadane:**
\`\`\`json
${JSON.stringify(metadata, null, 2)}
\`\`\`
`;
try {
await fetch(`https://app.notifer.io/${this.topic}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'X-Title': `${level.toUpperCase()}: ${message.substring(0, 50)}`,
'X-Priority': priority.toString(),
'X-Tags': `log,${level}`,
'X-Markdown': 'true'
},
body: formattedMessage
});
} catch (error) {
console.error('Failed to send log to Notifer:', error);
}
}
}
const logger = new NotiferLogger('api-logs', process.env.NOTIFER_API_KEY);
// Middleware obsługi błędów
app.use((err, req, res, next) => {
logger.log('error', err.message, {
path: req.path,
method: req.method,
body: req.body,
stack: err.stack
});
res.status(500).json({ error: 'Internal server error' });
});
// Przykład endpointu API
app.post('/api/payment', async (req, res) => {
try {
// Przetwarzaj płatność...
await logger.log('info', 'Płatność przetworzona pomyślnie', {
amount: req.body.amount,
userId: req.user.id
});
res.json({ success: true });
} catch (error) {
await logger.log('error', 'Przetwarzanie płatności nie powiodło się', {
error: error.message,
userId: req.user.id
});
throw error;
}
});
app.listen(3000);
Integracja CI/CD GitHub Actions¶
// .github/workflows/notify.yml
// Użyj tego skryptu w GitHub Actions
import fetch from 'node-fetch';
async function notifyDeployment(status, metadata) {
const {
branch = process.env.GITHUB_REF_NAME,
commit = process.env.GITHUB_SHA,
workflow = process.env.GITHUB_WORKFLOW,
actor = process.env.GITHUB_ACTOR
} = metadata;
const emoji = status === 'success' ? '✅' : '❌';
const priority = status === 'success' ? 3 : 5;
const message = `
## ${emoji} ${workflow} ${status === 'success' ? 'Sukces' : 'Błąd'}
**Gałąź:** \`${branch}\`
**Commit:** \`${commit.substring(0, 7)}\`
**Wywołany przez:** ${actor}
**Linki:**
- [Zobacz Workflow](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
- [Zobacz Commit](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${commit})
`;
const response = await fetch('https://app.notifer.io/ci-pipeline', {
method: 'POST',
headers: {
'X-Title': `${workflow}: ${status}`,
'X-Priority': priority.toString(),
'X-Tags': `ci,${status},${branch}`,
'X-Markdown': 'true'
},
body: message
});
if (!response.ok) {
console.error('Failed to send notification');
}
}
// Użycie w workflow
const status = process.argv[2]; // 'success' lub 'failure'
await notifyDeployment(status, {});
Klient TypeScript (silnie typowany)¶
interface PublishOptions {
title?: string;
priority?: 1 | 2 | 3 | 4 | 5;
tags?: string[];
markdown?: boolean;
}
interface Message {
id: string;
topic: string;
message: string;
title?: string;
priority: number;
tags: string[];
created_at: string;
}
class NotiferClient {
private baseUrl: string;
private apiKey?: string;
constructor(apiKey?: string, baseUrl = 'https://app.notifer.io') {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
}
async publish(
topic: string,
message: string,
options: PublishOptions = {}
): Promise<Message> {
const {
title,
priority = 3,
tags,
markdown = false
} = options;
const headers: Record<string, string> = {
'X-Priority': priority.toString()
};
if (this.apiKey) {
headers['Authorization'] = `Bearer ${this.apiKey}`;
}
if (title) {
headers['X-Title'] = title;
}
if (tags && tags.length > 0) {
headers['X-Tags'] = tags.join(',');
}
if (markdown) {
headers['X-Markdown'] = 'true';
}
const response = await fetch(`${this.baseUrl}/${topic}`, {
method: 'POST',
headers,
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as Message;
}
}
// Użycie z bezpieczeństwem typów
const client = new NotiferClient(process.env.NOTIFER_API_KEY);
const result: Message = await client.publish(
'alerts',
'Wiadomość testowa',
{
title: 'Alert testowy',
priority: 4,
tags: ['test', 'demo'],
markdown: true
}
);
console.log(`Opublikowano wiadomość: ${result.id}`);
Hook React dla powiadomień¶
import { useState, useCallback } from 'react';
function useNotifer(apiKey) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const publish = useCallback(async (topic, message, options = {}) => {
setLoading(true);
setError(null);
try {
const headers = {
'X-Priority': (options.priority || 3).toString()
};
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
if (options.title) {
headers['X-Title'] = options.title;
}
if (options.tags) {
headers['X-Tags'] = options.tags.join(',');
}
if (options.markdown) {
headers['X-Markdown'] = 'true';
}
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers,
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [apiKey]);
return { publish, loading, error };
}
// Użycie w komponencie
function NotificationButton() {
const { publish, loading, error } = useNotifer(process.env.REACT_APP_NOTIFER_KEY);
const handleClick = async () => {
try {
await publish('user-actions', 'Kliknięto przycisk', {
title: 'Interakcja użytkownika',
priority: 2,
tags: ['ui', 'interaction']
});
console.log('Powiadomienie wysłane!');
} catch (error) {
console.error('Nie udało się wysłać powiadomienia:', error);
}
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Wysyłanie...' : 'Wyślij powiadomienie'}
</button>
);
}
Obsługa błędów¶
Podstawowa obsługa błędów¶
async function safePublish(topic, message, options = {}) {
try {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers: options.headers || {},
body: message,
signal: AbortSignal.timeout(5000) // 5 sekundowy timeout
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Przekroczono limit czasu żądania');
} else if (error.name === 'TypeError') {
console.error('Błąd sieci');
} else {
console.error('Błąd:', error.message);
}
return null;
}
}
Ponawianie z wykładniczym backoff¶
async function publishWithRetry(topic, message, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers: options.headers || {},
body: message,
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) {
// Ostatnia próba się nie powiodła
console.error(`Niepowodzenie po ${maxRetries} próbach:`, error);
throw error;
}
// Oblicz opóźnienie backoff: 2^attempt sekund
const delay = Math.pow(2, attempt) * 1000;
console.log(`Próba ${attempt + 1} nie powiodła się, ponowienie za ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Kolejka oparta na Promise¶
class NotificationQueue {
constructor(concurrency = 3) {
this.queue = [];
this.running = 0;
this.concurrency = concurrency;
}
async add(topic, message, options = {}) {
return new Promise((resolve, reject) => {
this.queue.push({ topic, message, options, resolve, reject });
this.process();
});
}
async process() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
this.running++;
const { topic, message, options, resolve, reject } = this.queue.shift();
try {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers: options.headers || {},
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
resolve(data);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}
// Użycie
const queue = new NotificationQueue(5); // Maks. 5 równoczesnych żądań
// Dodaj wiele powiadomień
const promises = [
queue.add('topic1', 'Wiadomość 1'),
queue.add('topic2', 'Wiadomość 2'),
queue.add('topic3', 'Wiadomość 3')
];
const results = await Promise.all(promises);
Dobre praktyki¶
1. Używaj zmiennych środowiskowych¶
const topic = process.env.NOTIFER_TOPIC || 'default-topic';
const apiKey = process.env.NOTIFER_API_KEY;
async function publish(message, options = {}) {
const headers = { ...options.headers };
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers,
body: message
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
2. Utwórz instancję Singleton¶
// notifer.js
class Notifer {
static instance = null;
static getInstance(apiKey) {
if (!Notifer.instance) {
Notifer.instance = new Notifer(apiKey);
}
return Notifer.instance;
}
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://app.notifer.io';
}
async publish(topic, message, options = {}) {
// ... implementacja
}
}
export default Notifer.getInstance(process.env.NOTIFER_API_KEY);
// Użycie w innych plikach
import notifer from './notifer.js';
await notifer.publish('alerts', 'Wiadomość testowa');
3. Dodaj timeout żądania¶
async function publishWithTimeout(topic, message, timeoutMs = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
body: message,
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
} finally {
clearTimeout(timeout);
}
}
4. Powiadomienia wsadowe¶
async function publishBatch(notifications) {
const promises = notifications.map(({ topic, message, options }) =>
fetch(`https://app.notifer.io/${topic}`, {
method: 'POST',
headers: options?.headers || {},
body: message
})
);
const results = await Promise.allSettled(promises);
return results.map((result, index) => ({
...notifications[index],
success: result.status === 'fulfilled',
error: result.status === 'rejected' ? result.reason : null
}));
}
// Użycie
const results = await publishBatch([
{ topic: 'alerts', message: 'Alert 1' },
{ topic: 'logs', message: 'Wpis logu 1' },
{ topic: 'metrics', message: 'Dane metryki 1' }
]);
console.log(`Wysłano ${results.filter(r => r.success).length}/${results.length} powiadomień`);
Testowanie¶
Testy jednostkowe (Jest)¶
import { jest } from '@jest/globals';
// Mock fetch
global.fetch = jest.fn();
describe('NotiferClient', () => {
beforeEach(() => {
fetch.mockClear();
});
test('publishes message successfully', async () => {
fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
id: 'test-id',
topic: 'test-topic',
message: 'Test message'
})
});
const client = new NotiferClient('test-key');
const result = await client.publish('test-topic', 'Test message');
expect(result.id).toBe('test-id');
expect(fetch).toHaveBeenCalledTimes(1);
});
test('handles error correctly', async () => {
fetch.mockResolvedValueOnce({
ok: false,
status: 500
});
const client = new NotiferClient('test-key');
await expect(
client.publish('test-topic', 'Test message')
).rejects.toThrow('HTTP error! status: 500');
});
});
Następne kroki¶
- Publikowanie Python - Publikuj z Python
- Publikowanie HTTP - Publikuj przez cURL i HTTP
- Dokumentacja API - Kompletna dokumentacja API
- Przewodnik SSE - Subskrybuj tematy w JavaScript
Wskazówka: Używaj TypeScript dla bezpieczeństwa typów i twórz instancję singleton klienta dla czystszego kodu!