Przejdź do głównej zawartości

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",
"title": null,
"timestamp": "2025-11-22T10:30:00Z",
"priority": 3,
"tags": []
}

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

npm install node-fetch
import fetch from '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',
1
);

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'],
2
);

Funkcje zaawansowane

Formatowanie Markdown

async function sendMarkdownMessage(topic, message, title = null, priority = 3) {
const headers = {
'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
} = options;

const headers = { ...this.headers };
headers['X-Priority'] = priority.toString();

if (title) {
headers['X-Title'] = title;
}

if (tags) {
headers['X-Tags'] = tags.join(',');
}

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: 1,
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': '1',
'X-Tags': `error,${errorType.toLowerCase()},frontend`
};

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 = 2) {
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}%)`,
2
);
}

if (memoryPercent > this.memoryThreshold) {
await this.sendAlert(
`Wysokie użycie pamięci: ${memoryPercent}%`,
`Użycie pamięci wynosi ${memoryPercent}% (próg: ${this.memoryThreshold}%)`,
2
);
}
}

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': 5,
'info': 4,
'warn': 2,
'error': 1
}[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}`
},
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 : 1;

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}`
},
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[];
}

interface Message {
id: string;
topic: string;
message: string;
title: string | null;
timestamp: string;
priority: number;
tags: 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
} = 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(',');
}

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: 2,
tags: ['test', 'demo']
}
);

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

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

// plik .env
NOTIFER_TOPIC=production-alerts
NOTIFER_API_KEY=your-api-key-here
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


Wskazówka: Używaj TypeScript dla bezpieczeństwa typów i twórz instancję singleton klienta dla czystszego kodu!