Skip to content
Back to skills

Angular Realtime

ASecurity

Reviews the Angular SignalR client — the frontend counterpart to dotnet-realtime's hub. Flags a raw HubConnection scattered across components instead of a typed connection service, no automatic reconnect with backoff, the access token not attached (or not refreshed) on the socket, untyped on()/invoke() calls that drift from the hub's method contract, and subscriptions/connections not torn down on component destroy. Targets Angular 17+ standalone + Signals. Outputs pilot-angular angular-realti...

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 6, 2026
developmenttypescriptgoangularapifrontend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 6, 2026

npx -y skills add AgenticPawan/FullStack-Pilot --skill angular-realtime --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Angular Realtime?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Angular Realtime
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/agenticpawan-angular-realtime/badge)](https://www.skillsdirectory.com/skills/agenticpawan-angular-realtime)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: angular-realtime
description: Reviews the Angular SignalR client — the frontend counterpart to dotnet-realtime's hub. Flags a raw HubConnection scattered across components instead of a typed connection service, no automatic reconnect with backoff, the access token not attached (or not refreshed) on the socket, untyped on()/invoke() calls that drift from the hub's method contract, and subscriptions/connections not torn down on component destroy. Targets Angular 17+ standalone + Signals. Outputs pilot-angular angular-realtime standard IDs.
when_to_use: SignalR, real-time Angular, HubConnection, HubConnectionBuilder, withAutomaticReconnect, accessTokenFactory, hub method, on off invoke, websocket reconnect, live updates, notifications stream, presence, signalr client teardown, takeUntilDestroyed, typed hub proxy
applies_to: angular>=17
---

## Standard IDs

| ID | Severity | What it checks |
|----|----------|-----------------|
| ART-001 | P1 | Raw `HubConnection` built inside components instead of one typed connection service |
| ART-002 | P1 | No `withAutomaticReconnect` + no manual reconnect on `onclose` — a dropped socket stays dead |
| ART-003 | P0 | Access token not attached via `accessTokenFactory`, or never refreshed for a long-lived socket |
| ART-004 | P1 | `on()`/`invoke()` use string literals + `any` payloads that drift from the hub contract |
| ART-005 | P0 | Hub subscriptions / the connection itself not disposed on component destroy (leak) |

`dotnet-realtime` governs the server hub (methods, groups, auth, backplane). This skill
governs the browser half: one resilient, typed, authenticated connection whose lifetime is
tied to Angular's, so the client half doesn't leak sockets, silently stop receiving after a
network blip, or send a shape the hub can't bind. Method names and payload types here MUST
match the hub's — that seam is where a real-time feature breaks.

---

## Check A — One typed connection service, not per-component connections (ART-001)

### Detection

Look for `HubConnectionBuilder` invoked inside component classes. Each component that builds
its own connection opens a separate socket, re-authenticates independently, and duplicates
reconnect logic. There should be exactly one injectable connection service per hub, exposing
typed streams (Signals or Observables) that components consume.

### BAD — every component builds its own `HubConnection`

```typescript
@Component({ /* ... */ })
export class NotificationsComponent implements OnInit {
  ngOnInit() {
    const conn = new HubConnectionBuilder().withUrl('/hubs/notifications').build();
    conn.start();                              // ART-001: a second socket per component
    conn.on('notify', (n: any) => this.items.push(n));   // ART-004 + ART-005 too
  }
}
```

### GOOD — one connection service, components consume a typed Signal

```typescript
@Injectable({ providedIn: 'root' })
export class NotificationHubService {
  private readonly connection = new HubConnectionBuilder()
    .withUrl('/hubs/notifications', { accessTokenFactory: () => this.auth.accessToken() })
    .withAutomaticReconnect()
    .build();

  readonly notifications = signal<Notification[]>([]);
  // start() once, register typed handlers once; components just read notifications().
}
```

---

## Check B — Automatic reconnect + resubscribe (ART-002)

### Detection

Check that the connection is built with `withAutomaticReconnect()` (or has an explicit
`onclose` handler that restarts with backoff). Without it, the first transient network drop
kills the socket permanently and the UI silently stops updating — the worst failure mode
because nothing errors. Also confirm server→client group membership / state is re-established
after a reconnect (`onreconnected`), since reconnect starts a fresh connection id.

### BAD — no reconnect; a 2-second network blip ends live updates for the session

```typescript
new HubConnectionBuilder().withUrl('/hubs/orders').build();   // ART-002: no reconnect at all
```

### GOOD — reconnect with backoff, and re-join groups on reconnect

```typescript
new HubConnectionBuilder()
  .withUrl('/hubs/orders', { accessTokenFactory: () => this.auth.accessToken() })
  .withAutomaticReconnect([0, 2000, 5000, 10000])
  .build();
// connection.onreconnected(() => this.rejoinActiveGroups());
```

---

## Check C — Token attached and refreshed on the socket (ART-003)

### Detection

If the hub requires auth (`dotnet-realtime` + `dotnet-authentication`), the client MUST
supply the token via `accessTokenFactory` — a function, so the *current* token is read on
each (re)connect, not a stale one captured once. For WebSockets the token rides the query
string at negotiate time, so a long-lived socket started with an expired-soon token fails on
the next reconnect unless the factory returns a fresh one (see `auth-token-contract`).

### BAD — token captured once as a value; stale after silent renew

```typescript
.withUrl('/hubs/orders', { accessTokenFactory: () => this.tokenValue })  // ART-003: snapshot
```

### GOOD — factory reads the live token each (re)negotiation

```typescript
.withUrl('/hubs/orders', {
  accessTokenFactory: () => this.auth.accessToken(),   // current token on every reconnect
})
// angular-authentication owns the renew; this just always reads the latest.
```

---

## Check D — Typed hub method contract, not string + `any` (ART-004)

### Detection

Flag `connection.on('SomeMethod', (x: any) => ...)` and `connection.invoke('DoThing', ...)`
with bare string literals and untyped payloads. The method names and DTO shapes must match
the .NET hub (`dotnet-realtime`); a rename on the server silently breaks a string literal
here with no compile error. Centralize the method names and payload interfaces so a drift is
a TypeScript error, not a runtime no-op.

### BAD — magic strings, `any` payload

```typescript
connection.on('orderUpdated', (o: any) => this.apply(o));   // ART-004: no contract, no types
connection.invoke('subscribe', someId);
```

### GOOD — shared method names + typed payloads (ideally generated/shared with the API)

```typescript
export const OrdersHub = { OrderUpdated: 'OrderUpdated', Subscribe: 'Subscribe' } as const;

connection.on(OrdersHub.OrderUpdated, (o: OrderDto) => this.apply(o));   // typed to the DTO
connection.invoke(OrdersHub.Subscribe, orderId);
// OrderDto is the same contract angular-api-client-codegen generates for REST — one source.
```

---

## Check E — Teardown on destroy (ART-005)

### Detection

Confirm handlers registered with `on()` are removed and, where the connection is
component-scoped, `stop()` is called on destroy. Leaked handlers accumulate across
navigations (each visit adds another `on` callback → duplicated UI updates), and a leaked
component-scoped connection keeps a socket open forever (see `angular-memory-leaks`). Prefer
`takeUntilDestroyed()` for stream bridges and an explicit `off()`/`stop()` in the service's
`ngOnDestroy`/`DestroyRef`.

### BAD — handler registered every time the component mounts, never removed

```typescript
ngOnInit() { this.hub.connection.on('notify', this.handler); }   // ART-005: never off()'d
// Navigate away and back 5×  →  5 handlers fire per message.
```

### GOOD — bridge to a Signal/Observable scoped to the component lifetime

```typescript
constructor() {
  this.hub.notifications$
    .pipe(takeUntilDestroyed())     // auto-unsubscribes on destroy
    .subscribe(n => this.items.set(n));
}
// Root-scoped connection stays up; only the component's subscription is torn down.
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.