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

@aseansc-admin/sea-http

v0.3.1

Published

ASC HTTP client — envelope wrapper, interceptors, error handling

Downloads

1,003

Readme

@aseansc-admin/sea-http

Angular HTTP client for the ASEAN SC API — envelope wrapper, interceptors, auth storage, and error handling.

Installation

npm install @aseansc-admin/sea-http

Peer dependencies: @angular/core, @angular/common, @angular/router ≥ 20.


Quick Setup

Add to app.config.ts:

import { provideHttpClient, withInterceptors } from '@angular/common/http';
import {
  provideAscHttp,
  ascAuthInterceptor,
  ascErrorInterceptor,
  ascLoadingInterceptor,
} from '@aseansc-admin/sea-http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        ascAuthInterceptor,
        ascErrorInterceptor,
        ascLoadingInterceptor,
      ]),
    ),
    provideAscHttp({
      baseUrl: 'https://api.aseansc.com.vn/api',
      apiKey:  'your-api-key',
      api:     'sea-meetings',
    }),
  ],
};

provideAscHttp options

| Option | Type | Required | Default | Description | |--------------|--------------------------|----------|-------------|------------------------------------------| | baseUrl | string | ✅ | — | Default API endpoint | | endpoints | Record<string, string> | — | — | Named endpoints for multi-URL apps | | apiKey | string | ✅ | — | API key injected into every header | | api | string | ✅ | — | Service name, e.g. sea-meetings | | channel | string | — | 'ASEANSC' | Request channel | | subChannel | string | — | 'ASEANSC' | Request sub-channel | | context | string | — | 'WEB' | Request context | | priority | string | — | '1' | Request priority |

Multiple base URLs

If your app talks to more than one API endpoint, declare them in endpoints and pass the key as the third argument to post() / login(). Falls back to baseUrl when the key is omitted or not found.

provideAscHttp({
  baseUrl: 'https://api.aseansc.com.vn/main',
  endpoints: {
    auth: 'https://api.aseansc.com.vn/auth',
  },
  apiKey: 'your-api-key',
  api:    'sea-meetings',
})
// → hits baseUrl
this.api.post('getAllMeetings', data)

// → hits endpoints.auth
this.api.post('doSomething', data, 'auth')
this.api.login(credentials, 'auth')

Making API Calls

General API — AscApiService.post()

Wraps calls in the standard { header, body: { authenType, data } } envelope and unwraps body.data from the response automatically.

import { inject, Injectable } from '@angular/core';
import { AscApiService } from '@aseansc-admin/sea-http';

interface MeetingListData { /* ... */ }

@Injectable({ providedIn: 'root' })
export class MeetingService {
  private api = inject(AscApiService);

  getAll(pageNumber = 0, pageSize = 25) {
    return this.api.post<MeetingListData>('getAllMeetingAsean', {
      pagination: { pageNumber, pageSize },
      search: '',
    });
  }
}

Request envelope sent:

{
  "header": { "reqType": "REQUEST", "api": "sea-meetings", "..." },
  "body": {
    "authenType": "getAllMeetingAsean",
    "data": { "pagination": { "pageNumber": 0, "pageSize": 25 }, "search": "" }
  }
}

Login API — AscApiService.login()

The login endpoint uses a different envelope (command instead of authenType). Use the dedicated login() method.

import { inject, Injectable } from '@angular/core';
import { AscApiService, AscLocalStorageAuthService } from '@aseansc-admin/sea-http';
import { tap } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private api     = inject(AscApiService);
  private storage = inject(AscLocalStorageAuthService);

  login(username: string, password: string) {
    return this.api.login({
      username,
      password,
      authenType: 'getLogin',
      type: 'INHOUSE',
    }).pipe(
      tap(user => this.storage.save(user)),
    );
  }

  logout() {
    this.storage.clear();
  }
}

Request envelope sent:

{
  "header": { "reqType": "REQUEST", "..." },
  "body": {
    "command": "GET_ENQUIRY",
    "data": { "username": "...", "password": "...", "authenType": "getLogin", "type": "INHOUSE" }
  }
}

Auth Storage

Two ready-made services for persisting the token and user session. Both implement the abstract AscAuthStorageService.

AscLocalStorageAuthService — localStorage

No size limits. Recommended when permissionList is large.

import { inject } from '@angular/core';
import { AscLocalStorageAuthService } from '@aseansc-admin/sea-http';

const storage = inject(AscLocalStorageAuthService);

storage.save(loginResponseData);   // saves token + full user data
storage.getToken();                // string | null
storage.getUser();                 // LoginResponseData | null
storage.clear();                   // removes both keys

AscCookieAuthService — Cookie

Useful when the token needs to be sent automatically by the browser. Note: each cookie is limited to ~4 KB — if permissionList is large, use AscLocalStorageAuthService instead.

import { inject } from '@angular/core';
import { AscCookieAuthService } from '@aseansc-admin/sea-http';

const storage = inject(AscCookieAuthService);

storage.save(loginResponseData, 7); // expires in 7 days (default: 1)
storage.getToken();
storage.getUser();
storage.clear();

Cookies are set with SameSite=Strict; path=/.

Using the abstract token for DI

If you want to swap implementations without changing consuming services, provide one via AscAuthStorageService:

// app.config.ts
import {
  AscAuthStorageService,
  AscLocalStorageAuthService,
  // or AscCookieAuthService
} from '@aseansc-admin/sea-http';

providers: [
  { provide: AscAuthStorageService, useExisting: AscLocalStorageAuthService },
]
// any service
private storage = inject(AscAuthStorageService);

Interceptors

| Interceptor | What it does | |------------------------|------------------------------------------------------------------------------| | ascAuthInterceptor | Injects userID into the request header from ASC_HTTP_USER_ID_FN | | ascErrorInterceptor | Handles 401 (redirect to /login), 403, network errors, 5xx via error handler | | ascLoadingInterceptor| Auto-increments/decrements AscLoadingService counter per active request |

All three are opt-in — add only what you need to withInterceptors([...]).

Configure userID injection

// app.config.ts
import { ASC_HTTP_USER_ID_FN } from '@aseansc-admin/sea-http';

{
  provide:    ASC_HTTP_USER_ID_FN,
  useFactory: (auth: AuthService) => () => auth.currentUser()?.userCode ?? '',
  deps:       [AuthService],
}

Configure global error handler

import { ASC_HTTP_ERROR_HANDLER, AscApiError } from '@aseansc-admin/sea-http';

{
  provide:    ASC_HTTP_ERROR_HANDLER,
  useFactory: (toast: ToastService) =>
    (err: unknown) => toast.error(err instanceof AscApiError ? err.message : 'Connection error'),
  deps:       [ToastService],
}

Global loading indicator

import { inject } from '@angular/core';
import { AscLoadingService } from '@aseansc-admin/sea-http';

// in a component
loading = inject(AscLoadingService);
@if (loading.isLoading()) {
  <div class="loading-bar"></div>
}

Error Handling

API-level errors (when body.status !== 'OK') are thrown as AscApiError:

import { AscApiError } from '@aseansc-admin/sea-http';

this.api.post('someAction', data).subscribe({
  error: (err) => {
    if (err instanceof AscApiError) {
      console.log(err.status);     // e.g. 'UNAUTHORIZED'
      console.log(err.authenType); // the operation name
      console.log(err.data);       // raw error payload from server
    }
  }
});

HTTP-level errors (4xx, 5xx, network) are handled automatically by ascErrorInterceptor and passed to ASC_HTTP_ERROR_HANDLER.


Server-Sent Events (SSE)

AscSseService wraps the browser's EventSource API. Since EventSource does not support custom headers, the JWT token is passed automatically as a URL query param (?token=<jwt>).

No extra setup needed — the service reads token from localStorage / cookie and appends it to every URL.

Unnamed events

The server sends lines in the format:

data: {"meetingId":"123","status":"STARTED"}\n\n
import { inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AscSseService } from '@aseansc-admin/sea-http';

interface NotificationDto {
  type:    string;
  message: string;
}

@Component({ ... })
export class NotificationBell {
  private sse = inject(AscSseService);

  constructor() {
    this.sse.connect<NotificationDto>('/notifications/stream')
      .pipe(takeUntilDestroyed())
      .subscribe({
        next:     event => console.log(event.type, event.data),
        error:    err   => console.error('SSE error', err),
        complete: ()    => console.log('SSE closed'),
      });
  }
}

Named events

The server sends lines in the format:

event: meeting-update
data: {"meetingId":"123","agenda":"Updated agenda"}\n\n

Use sse.on() to filter by event type — the returned Observable emits the parsed data directly:

import { inject, Component, OnInit } from '@angular/core';
import { takeUntilDestroyed }        from '@angular/core/rxjs-interop';
import { AscSseService }             from '@aseansc-admin/sea-http';

interface MeetingUpdate {
  meetingId: string;
  agenda:    string;
}

@Component({ ... })
export class MeetingDetailPage implements OnInit {
  private sse = inject(AscSseService);

  ngOnInit() {
    // Chỉ nhận events có type = 'meeting-update'
    this.sse.on<MeetingUpdate>('/notifications/stream', 'meeting-update')
      .pipe(takeUntilDestroyed())
      .subscribe(data => this.applyUpdate(data));

    // Có thể subscribe nhiều event type cùng lúc từ cùng 1 path
    this.sse.on<ParticipantUpdate>('/notifications/stream', 'participant-joined')
      .pipe(takeUntilDestroyed())
      .subscribe(data => this.addParticipant(data));
  }

  private applyUpdate(update: MeetingUpdate) { /* ... */ }
  private addParticipant(p: ParticipantUpdate) { /* ... */ }
}

SSE options

| Option | Type | Default | Description | |-------------------|-----------|----------|------------------------------------------------| | tokenParam | string | 'token'| Tên query param chứa JWT token | | params | Record | {} | Query params thêm vào URL | | withCredentials | boolean | false | Gửi cookie theo request | | reconnect | boolean | true | Tự động reconnect khi mất kết nối | | reconnectDelay | number | 3000 | Delay giữa các lần reconnect (ms) |

// Ví dụ: thêm query params, đổi tên token param
this.sse.connect('/stream', {
  tokenParam: 'access_token',
  params:     { roomId: '42' },
  reconnect:  true,
  reconnectDelay: 5000,
});

WebSocket

AscWebSocketService.connect() trả về một AscWsConnection handle. Outgoing và incoming messages đều dùng cùng envelope protocol:

{
  "header": { "api": "sea-meetings", "apiKey": "...", "userID": "u01", "channel": "ASEANSC", "subChannel": "ASEANSC" },
  "body":   { "authenType": "meetingUpdate", "data": { ... } }
}

Basic usage

import { inject, Component, OnInit, OnDestroy } from '@angular/core';
import { AscWebSocketService, AscWsConnection } from '@aseansc-admin/sea-http';

interface MeetingEvent  { meetingId: string; status: string; }
interface ChatMessage   { from: string; text: string; }

@Component({ ... })
export class MeetingRoomPage implements OnInit, OnDestroy {
  private ws   = inject(AscWebSocketService);
  private conn!: AscWsConnection;

  ngOnInit() {
    this.conn = this.ws.connect('/ws/meetings');

    // Theo dõi trạng thái kết nối
    this.conn.status$.subscribe(status => {
      // 'connecting' | 'connected' | 'disconnected' | 'error'
      console.log('WS status:', status);
    });

    // Nhận tất cả messages
    this.conn.messages$.subscribe(msg => {
      console.log(msg.body.authenType, msg.body.data);
    });

    // Lọc theo authenType
    this.conn.on<MeetingEvent>('meetingUpdate').subscribe(data => {
      console.log('Meeting updated:', data.status);
    });

    this.conn.on<ChatMessage>('chatMessage').subscribe(msg => {
      this.appendChat(msg);
    });

    // Gửi message
    this.conn.send('subscribeMeeting', { meetingId: '123' });
  }

  sendChat(text: string) {
    this.conn.send('sendChat', { text });
  }

  ngOnDestroy() {
    this.conn.close();
  }

  private appendChat(msg: ChatMessage) { /* ... */ }
}

Với takeUntilDestroyed (Angular 16+)

import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AscWebSocketService } from '@aseansc-admin/sea-http';

@Component({ ... })
export class ChatComponent {
  private ws   = inject(AscWebSocketService);
  private conn = this.ws.connect('/ws/chat');

  constructor() {
    this.conn.on<ChatMessage>('chatMessage')
      .pipe(takeUntilDestroyed())
      .subscribe(msg => this.messages.push(msg));
  }
}

Lưu ý: Dùng takeUntilDestroyed() thì không cần gọi conn.close() để cleanup subscription, nhưng vẫn nên gọi conn.close() trong ngOnDestroy để đóng WebSocket connection.

Multiple connections

Mỗi lần gọi connect() tạo một WebSocket connection riêng biệt:

// Kết nối đến meeting room
const meetingConn = this.ws.connect('/ws/meetings');

// Kết nối riêng đến notification channel
const notifConn = this.ws.connect('/ws/notifications', { reconnect: true });

// Kết nối đến URL khác hoàn toàn
const externalConn = this.ws.connect('wss://other.service.com/ws');

WebSocket options

| Option | Type | Default | Description | |------------------|------------------|----------|----------------------------------------------------| | tokenParam | string \| false| 'token'| Tên query param chứa JWT. false = không gửi token| | reconnect | boolean | true | Tự động reconnect khi mất kết nối | | maxRetries | number | 5 | Số lần retry tối đa | | reconnectDelay | number | 3000 | Delay cơ bản giữa các lần retry (ms), tăng dần |

this.ws.connect('/ws/meetings', {
  tokenParam:     'access_token',
  reconnect:      true,
  maxRetries:     10,
  reconnectDelay: 2000,
});

Retry back-off

Delay giữa các lần reconnect tăng tuyến tính theo số lần thử:

| Lần retry | Delay | |-----------|-----------------| | 1 | 1× reconnectDelay | | 2 | 2× reconnectDelay | | 3 | 3× reconnectDelay | | … | … |

Sau maxRetries lần thất bại, messages$ sẽ complete và không reconnect nữa.


Types Reference

import type {
  // Config
  AscHttpConfig,

  // Request / Response envelope
  AscApiRequest,
  AscApiRequestHeader,
  AscApiResponse,
  AscApiResponseHeader,

  // Login
  LoginRequestData,
  LoginResponseData,
  LoginPermission,

  // SSE
  AscSseOptions,
  AscSseEvent,

  // WebSocket
  AscWsOptions,
  AscWsMessage,
  AscWsHeader,
  AscWsStatus,
} from '@aseansc-admin/sea-http';

import { AscApiError, isAscApiRequest } from '@aseansc-admin/sea-http';