npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@slnka/node

v1.0.0-beta.1

Published

SDK TypeScript/JavaScript officiel pour l'API SLNKA

Downloads

125

Readme

@slnka/node

SDK TypeScript officiel pour l'API SLNKA - Plateforme souveraine marocaine de raccourcissement d'URLs.

npm version License: MIT TypeScript

Table des Matières


Installation

npm

npm install @slnka/node

yarn

yarn add @slnka/node

pnpm

pnpm add @slnka/node

Prérequis: Node.js 18+ ou navigateur moderne avec support fetch API.


Quick Start

Authentification JWT

Pour les applications frontend avec authentification utilisateur:

import { SlnkaClient } from '@slnka/node';

// 1. Créer le client (style `configure()` partagé par tous les SDK SLNKA)
const client = SlnkaClient.configure({
  serverUrl: 'https://your-instance.slnka.ma',
});

// Alternative: builder fluide (idiomatique Java/Kotlin)
// const client = SlnkaClient.builder()
//   .serverUrl('https://your-instance.slnka.ma')
//   .build();

// 2. Login avec email/password
const authResponse = await client.auth.login({
  email: '[email protected]',
  password: 'password123'
});

// 3. Le client gère automatiquement le token JWT
// Toutes les requêtes suivantes utilisent le token d'accès
const links = await client.links.list();

console.log('Access Token:', authResponse.accessToken);
console.log('Links:', links.content);

Avec Token Refresh Automatique

const client = SlnkaClient.builder()
  .serverUrl('https://your-instance.slnka.ma')  // Required
  .bearerToken(
    storedAccessToken,    // Token d'accès existant
    storedRefreshToken    // Refresh token
  )
  .onTokenRefresh((tokens) => {
    // Callback appelé lors du refresh automatique
    localStorage.setItem('accessToken', tokens.accessToken);
    localStorage.setItem('refreshToken', tokens.refreshToken);
    console.log('Tokens refreshed automatically');
  })
  .build();

// Le SDK refresh automatiquement le token si expiré
const link = await client.links.create({
  originalUrl: 'https://example.com'
});

Authentification API Key

Pour les applications serveur ou scripts backend:

import { SlnkaClient } from '@slnka/node';

const client = SlnkaClient.configure({
  serverUrl: 'https://your-instance.slnka.ma',
  apiKey: 'lsk_live_your_api_key_here', // Format: lsk_live_* (production)
});

// Alternative: builder fluide
// const client = SlnkaClient.builder()
//   .serverUrl('https://your-instance.slnka.ma')
//   .apiKey('lsk_live_your_api_key_here')
//   .build();

// Créer un lien raccourci
const link = await client.links.create({
  originalUrl: 'https://example.com/very-long-url',
  customAlias: 'mon-lien',
  tags: ['marketing', 'campaign-2025']
});

console.log('Short URL:', link.shortUrl);
// Output: https://slnka.ma/mon-lien

Format API Keys:

  • Production: lsk_live_*
  • Test: lsk_test_*

Exemples d'Utilisation

Gestion des Liens

Créer un lien simple

const link = await client.links.create({
  originalUrl: 'https://example.com/long-url'
});

console.log(link.shortUrl);      // https://slnka.ma/abc1234
console.log(link.shortCode);     // abc1234
console.log(link.originalUrl);   // https://example.com/long-url

Créer un lien avec alias personnalisé

const link = await client.links.create({
  originalUrl: 'https://example.com/product',
  customAlias: 'produit-2025',
  tags: ['marketing', 'lancement']
});

console.log(link.shortUrl); // https://slnka.ma/produit-2025

Créer un lien avec options avancées

const link = await client.links.create({
  originalUrl: 'https://example.com/secure-content',
  customAlias: 'secure-doc',
  password: 'secret123',                    // Protection par mot de passe
  expiresAt: '2025-12-31T23:59:59Z',       // Date d'expiration
  tags: ['confidential', 'internal'],
  metadata: {
    department: 'Finance',
    documentId: 'DOC-2025-001'
  }
});

Lister les liens avec pagination

// Page 1, 20 liens par page
const page1 = await client.links.list(0, 20);

console.log('Total links:', page1.totalElements);
console.log('Total pages:', page1.totalPages);
console.log('Links:', page1.content);

// Parcourir toutes les pages
for (let page = 0; page < page1.totalPages; page++) {
  const links = await client.links.list(page, 20);
  links.content.forEach(link => {
    console.log(`${link.shortCode} -> ${link.originalUrl}`);
  });
}

Obtenir un lien spécifique

const link = await client.links.get('abc1234');

console.log('Original URL:', link.originalUrl);
console.log('Created:', link.createdAt);
console.log('Clicks:', link.clickCount);
console.log('Tags:', link.tags);

Mettre à jour un lien

const updated = await client.links.update('abc1234', {
  originalUrl: 'https://new-destination.com',
  tagIds: ['tag-id-1', 'tag-id-2']
});

Supprimer un lien

await client.links.delete('abc1234');
console.log('Link deleted successfully');

Gestion mot de passe

// Définir un mot de passe
await client.links.setPassword('abc1234', 'newpassword123');

// Vérifier un mot de passe
const isValid = await client.links.verifyPassword('abc1234', 'testpassword');
console.log('Password valid:', isValid);

// Supprimer le mot de passe
await client.links.removePassword('abc1234');

Statistiques Analytics

Résumé analytics d'un lien

const summary = await client.analytics.getSummary(
  'abc1234',              // Link ID ou shortCode
  '2025-01-01',          // Date début
  '2025-01-31'           // Date fin
);

console.log('Total Clicks:', summary.totalClicks);
console.log('Unique Visitors:', summary.uniqueVisitors);
console.log('Top Countries:', summary.topCountries);
console.log('Top Browsers:', summary.topBrowsers);
console.log('Top Devices:', summary.topDevices);

// Détails par pays
summary.topCountries.forEach(c => {
  console.log(`${c.country}: ${c.clicks} clicks`);
});

Série temporelle

// Analytics par jour
const dailyStats = await client.analytics.getTimeSeries('abc1234', 'day');

dailyStats.forEach(point => {
  console.log(`${point.timestamp}: ${point.clicks} clicks`);
});

// Analytics par heure
const hourlyStats = await client.analytics.getTimeSeries('abc1234', 'hour');

Visualisation avec Chart.js

import Chart from 'chart.js/auto';

async function renderClicksChart(shortCode: string) {
  // Récupérer les données
  const dailyStats = await client.analytics.getTimeSeries(shortCode, 'day');

  // Préparer les données pour Chart.js
  const labels = dailyStats.map(point => point.timestamp);
  const data = dailyStats.map(point => point.clicks);

  // Créer le graphique
  new Chart(document.getElementById('chart'), {
    type: 'line',
    data: {
      labels,
      datasets: [{
        label: 'Clicks par jour',
        data,
        borderColor: '#2563eb',
        backgroundColor: 'rgba(37, 99, 235, 0.1)',
        tension: 0.4
      }]
    },
    options: {
      responsive: true,
      plugins: {
        title: {
          display: true,
          text: 'Performance du lien'
        }
      }
    }
  });
}

Statistiques globales du workspace

const globalStats = await client.analytics.getGlobalStats();

console.log('Total Clicks:', globalStats.totalClicks);
console.log('Unique Visitors:', globalStats.uniqueVisitors);
console.log('Top Countries:', globalStats.topCountries);
console.log('Top Browsers:', globalStats.topBrowsers);

Génération QR Codes

QR Code basique

const qrCode = await client.qrCode.generate('abc1234', {
  size: 300,                        // Taille en pixels (100-1000)
  format: 'png',                    // 'png' ou 'svg'
  fgColor: '#000000',               // Couleur QR
  bgColor: '#FFFFFF'                // Couleur fond
});

// Le QR code est retourné comme données brutes
// Pour PNG: ArrayBuffer, pour SVG: string
console.log('Content type:', qrCode.contentType);

QR Code personnalisé avec couleurs de marque

// QR Code avec couleurs personnalisées
const brandedQrCode = await client.qrCode.generate('abc1234', {
  size: 500,
  format: 'png',
  fgColor: '#2563eb',              // Bleu de marque
  bgColor: '#f0f9ff'               // Fond bleu clair
});

// Télécharger le QR code
const downloadQrCode = (base64Data: string, filename: string) => {
  const link = document.createElement('a');
  link.href = `data:image/png;base64,${base64Data}`;
  link.download = filename;
  link.click();
};

downloadQrCode(brandedQrCode.data, 'qrcode-abc1234.png');

QR Code SVG pour impression haute qualité

// Format SVG pour qualité maximale
const svgQrCode = await client.qrCode.generate('abc1234', {
  size: 1000,
  format: 'svg',
  fgColor: '#000000',
  bgColor: '#FFFFFF'
});

// Affichage sécurisé du SVG
const svgBlob = new Blob([atob(svgQrCode.data)], { type: 'image/svg+xml' });
const svgUrl = URL.createObjectURL(svgBlob);

const img = document.createElement('img');
img.src = svgUrl;
img.alt = 'QR Code';
document.getElementById('qr-container').appendChild(img);

// Nettoyer l'URL après utilisation
img.onload = () => URL.revokeObjectURL(svgUrl);

Gestion des Workspaces

Lister les workspaces

const workspaces = await client.workspaces.list();

workspaces.forEach(ws => {
  console.log(`${ws.name} (${ws.slug})`);
});

Créer un workspace

const workspace = await client.workspaces.create({
  name: 'Marketing Team',
  slug: 'marketing-team'
});

console.log('Workspace ID:', workspace.id);

Changer de workspace actif

// Changer le workspace pour toutes les requêtes suivantes
await client.workspaces.switch('workspace-id-123');

// Toutes les opérations utilisent maintenant ce workspace
const links = await client.links.list();

Gestion des membres

// Obtenir les membres
const members = await client.workspaces.getMembers('workspace-id');

members.forEach(member => {
  console.log(`${member.email}: ${member.role}`);
});

// Inviter un membre
await client.workspaces.inviteMember(
  'workspace-id',
  '[email protected]',
  'EDITOR'  // OWNER, ADMIN, EDITOR, VIEWER
);

// Changer le rôle d'un membre
await client.workspaces.updateMemberRole(
  'workspace-id',
  'member-id',
  { role: 'ADMIN' }
);

// Retirer un membre
await client.workspaces.removeMember('workspace-id', 'member-id');

Configuration Avancée

Timeout et Retry

const client = SlnkaClient.builder()
  .serverUrl('https://your-instance.slnka.ma')  // Required
  .apiKey('lsk_live_key')
  .timeout(60000)        // Timeout 60 secondes (défaut: 30s)
  .build();

Callbacks personnalisés

const client = SlnkaClient.builder()
  .serverUrl('https://your-instance.slnka.ma')  // Required
  .apiKey('lsk_live_key')
  .onTokenRefresh((tokens) => {
    // Appelé lors du refresh automatique JWT
    console.log('New access token:', tokens.accessToken);
    saveTokensToStorage(tokens);
  })
  .build();

Headers personnalisés

const client = SlnkaClient.builder()
  .serverUrl('https://your-instance.slnka.ma')  // Required
  .apiKey('lsk_live_key')
  .customHeaders({
    'X-Custom-Header': 'value',
    'X-Request-ID': generateRequestId()
  })
  .build();

Environnements multiples

// Production
const prodClient = SlnkaClient.builder()
  .serverUrl('https://your-instance.slnka.ma')  // Required
  .apiKey(process.env.SLNKA_PROD_API_KEY)
  .build();

// Recette
const recetteClient = SlnkaClient.builder()
  .serverUrl('https://api-recette.slnka.ma')
  .apiKey(process.env.SLNKA_RECETTE_API_KEY)
  .build();

// Development local
const devClient = SlnkaClient.builder()
  .serverUrl('http://localhost:8080')
  .apiKey('lsk_test_local_dev_key')
  .build();

Gestion des Erreurs

Le SDK fournit des classes d'erreur typées pour une gestion précise:

Types d'erreurs

import {
  SlnkaError,
  UnauthorizedError,
  NotFoundError,
  RateLimitError,
  ValidationError,
  ConflictError
} from '@slnka/node';

try {
  const link = await client.links.create({
    originalUrl: 'invalid-url'
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Données invalides:', error.details);
    // Afficher les erreurs de validation
  } else if (error instanceof UnauthorizedError) {
    console.error('Non authentifié - rediriger vers login');
    redirectToLogin();
  } else if (error instanceof NotFoundError) {
    console.error('Ressource non trouvée');
  } else if (error instanceof ConflictError) {
    console.error('Conflit - alias déjà utilisé');
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limit - réessayer dans ${error.details?.retryAfter}s`);
    setTimeout(() => retry(), error.details.retryAfter * 1000);
  } else if (error instanceof SlnkaError) {
    console.error(`Erreur API: ${error.code} - ${error.message}`);
  } else {
    console.error('Erreur inattendue:', error);
  }
}

Codes d'erreur courants

| Code | Type | Description | |------|------|-------------| | 400 | ValidationError | Données de requête invalides | | 401 | UnauthorizedError | Non authentifié | | 403 | ForbiddenError | Permission refusée | | 404 | NotFoundError | Ressource non trouvée | | 409 | ConflictError | Conflit (alias déjà utilisé) | | 429 | RateLimitError | Rate limit dépassé | | 500 | SlnkaError | Erreur serveur |

Détails d'erreur

try {
  await client.links.create({ originalUrl: 'invalid' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log('Message:', error.message);
    console.log('Code:', error.code);
    console.log('Détails:', error.details);
    // Détails contient les erreurs de validation par champ
    // {
    //   originalUrl: ['Must be a valid URL'],
    //   customAlias: ['Already in use']
    // }
  }
}

Types TypeScript

Le SDK est entièrement typé TypeScript. Tous les types sont exportés:

import type {
  // Responses
  LinkResponse,
  AuthResponse,
  WorkspaceResponse,
  AnalyticsSummary,
  QRCodeResponse,
  UserProfile,

  // Requests
  CreateLinkRequest,
  UpdateLinkRequest,
  LoginRequest,
  RegisterRequest,
  CreateWorkspaceRequest,
  UpdateMemberRoleRequest,

  // Pagination
  PaginatedLinks,

  // Analytics
  TimeSeriesPoint,
  AnalyticsBreakdownItem,
  TopLinkItem,

  // Workspace
  WorkspaceMember,
  QuotaUsageResponse
} from '@slnka/node';

Exemple d'utilisation des types

import type { CreateLinkRequest, LinkResponse } from '@slnka/node';

async function createMarketingLink(
  url: string,
  campaign: string
): Promise<LinkResponse> {
  const request: CreateLinkRequest = {
    originalUrl: url,
    tags: ['marketing', campaign],
    metadata: {
      campaign,
      createdBy: 'marketing-automation'
    }
  };

  return await client.links.create(request);
}

// TypeScript infère automatiquement le type de retour
const link = await createMarketingLink(
  'https://example.com/promo',
  'summer-2025'
);

console.log(link.shortUrl);  // Type: string
console.log(link.clickCount); // Type: number

Support

Documentation

Assistance

Communauté


License

MIT © SLNKA


Changelog

v1.0.0 (2025-01-19)

  • ✨ Initial release
  • 🔐 JWT authentication avec refresh automatique
  • 🔑 API Key authentication
  • 🔗 API Links complète (CRUD)
  • 📊 Analytics avec séries temporelles
  • 📱 QR Code generation
  • 🏢 Workspace management
  • 🛡️ TypeScript types complets
  • ⚡ Error handling typé
  • 🎯 Rate limiting avec retry automatique