Przejdź do treś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",
  "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

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',
  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

// 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!