@bielu/scalar-signalr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ /** Direction of a SignalR hub operation relative to the server. */
2
+ export type SignalRDirection = 'clientToServer' | 'serverToClient';
3
+ /** A reference to an AsyncAPI document — either by URL or as an already-parsed object. */
4
+ export type SignalRDocumentRef = {
5
+ /** Logical document name (matches the AsyncAPI/source title). */
6
+ name: string;
7
+ /** URL the AsyncAPI JSON document is served from. */
8
+ url?: string;
9
+ /** An inline, already-parsed document object (from a Scalar source's `content`). */
10
+ doc?: Record<string, any>;
11
+ };
12
+ /** Configuration consumed by the SignalR console plugin. */
13
+ export type SignalRPluginConfig = {
14
+ /**
15
+ * AsyncAPI documents to scan for SignalR bindings. Normally derived automatically from the
16
+ * Scalar configuration's `sources`; only set explicitly to override that discovery.
17
+ */
18
+ documents: SignalRDocumentRef[];
19
+ };
20
+ /** A message an operation can carry, with a generated example payload. */
21
+ export type SignalRMessageModel = {
22
+ /** Message name/key (e.g. `chatMessage`). */
23
+ name: string;
24
+ /** Human-friendly title, when the document provides one. */
25
+ title?: string;
26
+ /**
27
+ * A ready-to-send example of the hub method arguments, as a pretty-printed JSON array string
28
+ * (e.g. `[{ "user": "string", … }]`), generated from the message payload schema.
29
+ */
30
+ example: string;
31
+ };
32
+ /** A single hub operation (client-to-server method or server-to-client event). */
33
+ export type SignalROperationModel = {
34
+ /** AsyncAPI operation id. */
35
+ id: string;
36
+ /** The hub method / event name actually used on the wire. */
37
+ target: string;
38
+ direction: SignalRDirection;
39
+ /** `invocation`, `streamInvocation` or `send`. */
40
+ callType?: string;
41
+ summary?: string;
42
+ /** Messages this operation carries, each with an example payload to prefill the editor. */
43
+ messages: SignalRMessageModel[];
44
+ };
45
+ /** A minimal representation of an AsyncAPI security scheme, extracted at document-parse time. */
46
+ export type SecuritySchemeModel = {
47
+ /** e.g. `'apiKey'`, `'http'`, `'oauth2'`, `'openIdConnect'`. */
48
+ type: string;
49
+ /** For `apiKey`: `'query'`, `'header'`, or `'cookie'`. */
50
+ in?: string;
51
+ /** For `apiKey`: the query-param or header field name. */
52
+ name?: string;
53
+ /** For `http`: `'bearer'`, `'basic'`, etc. */
54
+ scheme?: string;
55
+ };
56
+ /** A SignalR hub discovered from an AsyncAPI channel with a `signalr` binding. */
57
+ export type SignalRHubModel = {
58
+ documentName: string;
59
+ channelName: string;
60
+ /** Hub path, e.g. `/chatHub`. */
61
+ hubPath: string;
62
+ /** Best-effort absolute base URL (scheme://host) resolved from the AsyncAPI servers. */
63
+ baseUrl: string;
64
+ transports: string[];
65
+ protocols: string[];
66
+ operations: SignalROperationModel[];
67
+ /** Security schemes declared in this hub's AsyncAPI document, keyed by scheme name. */
68
+ securitySchemes?: Record<string, SecuritySchemeModel>;
69
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@bielu/scalar-signalr",
3
+ "version": "0.1.0",
4
+ "description": "Interactive SignalR console plugin for the Scalar API Reference. Reads SignalR AsyncAPI bindings and lets you connect to hubs, invoke methods and subscribe to server events.",
5
+ "license": "MIT",
6
+ "author": "Arkadiusz Biel",
7
+ "type": "module",
8
+ "main": "./dist/plugin.js",
9
+ "module": "./dist/plugin.js",
10
+ "types": "./dist/types/index.d.ts",
11
+ "files": [
12
+ "dist/plugin.js",
13
+ "dist/types",
14
+ "src",
15
+ "README.md"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/types/index.d.ts",
20
+ "default": "./dist/plugin.js"
21
+ }
22
+ },
23
+ "keywords": [
24
+ "scalar",
25
+ "signalr",
26
+ "asyncapi",
27
+ "api-reference",
28
+ "plugin",
29
+ "websocket"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/bielu/Bielu.AspNetCore.AsyncApi"
34
+ },
35
+ "scripts": {
36
+ "build:core": "cd ../../Bielu.AspNetCore.AsyncApi.Scalar/assets && npm install && npm run build",
37
+ "prebuild": "npm run build:core",
38
+ "build": "vite build && npm run build:standalone && vue-tsc -p tsconfig.json",
39
+ "build:standalone": "node ../../Bielu.AspNetCore.AsyncApi.Scalar/assets/scripts/build-standalone.mjs",
40
+ "pretypecheck": "npm run build:core",
41
+ "typecheck": "vue-tsc --noEmit",
42
+ "dev": "vite build --watch"
43
+ },
44
+ "dependencies": {
45
+ "@microsoft/signalr": "^8.0.7",
46
+ "vue": "^3.5.13"
47
+ },
48
+ "devDependencies": {
49
+ "@bielu/scalar-core": "file:../../Bielu.AspNetCore.AsyncApi.Scalar/assets",
50
+ "@scalar/api-reference": "1.62.9",
51
+ "@vitejs/plugin-vue": "^5.2.1",
52
+ "typescript": "^5.6.0",
53
+ "vite": "^6.4.3",
54
+ "vue-tsc": "^2.1.10"
55
+ }
56
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,94 @@
1
+ import {
2
+ getAuthState as coreGetAuthState,
3
+ resolveSelectedSchemes,
4
+ setAuthState as coreSetAuthState,
5
+ } from '@bielu/scalar-core'
6
+ import type { SecuritySchemeModel } from './types'
7
+
8
+ // Thin wrappers around @bielu/scalar-core's auth-state store. Typed as `unknown` so the published
9
+ // declaration files never reference the private core package (see the core README).
10
+
11
+ /** Store Scalar's PluginAuthState (no-op on older Scalar versions that predate the API). */
12
+ export function setAuthState(auth: unknown): void {
13
+ coreSetAuthState(auth)
14
+ }
15
+
16
+ /** The captured PluginAuthState, as an opaque handle for `resolveSignalRAuth`. */
17
+ export function getAuthState(): unknown {
18
+ return coreGetAuthState()
19
+ }
20
+
21
+ export type ResolvedAuth = {
22
+ /** Bearer token to supply via `accessTokenFactory` (http bearer, oauth2, openIdConnect). */
23
+ accessToken?: string
24
+ /** Key-value pairs to append as query parameters to the hub URL. */
25
+ queryParams: Record<string, string>
26
+ /** Non-fatal warnings about scheme constraints (e.g. header-only schemes that WS cannot send). */
27
+ warnings: string[]
28
+ }
29
+
30
+ /**
31
+ * Resolve Scalar's selected auth for the given document and map the secrets to SignalR connection
32
+ * options. Browser WebSocket/SSE cannot send arbitrary headers, so API keys are mapped onto query
33
+ * parameters (with a warning when the scheme declared a header) and only bearer-shaped schemes can
34
+ * supply an access token. Returns an empty result when auth state is absent (stock Scalar or no
35
+ * scheme selected), so callers need no guard.
36
+ */
37
+ export function resolveSignalRAuth(
38
+ documentName: string,
39
+ securitySchemes: Record<string, SecuritySchemeModel> | undefined,
40
+ auth: unknown,
41
+ ): ResolvedAuth {
42
+ const result: ResolvedAuth = { queryParams: {}, warnings: [] }
43
+
44
+ for (const { schemeName, type, scheme: schemeDef, secrets } of resolveSelectedSchemes(
45
+ documentName,
46
+ securitySchemes,
47
+ auth,
48
+ )) {
49
+ const token = secrets['x-scalar-secret-token']
50
+
51
+ switch (type) {
52
+ case 'apiKey':
53
+ case 'httpApiKey': {
54
+ const location = (schemeDef.in ?? 'query').toLowerCase()
55
+ const paramName = schemeDef.name ?? 'api_key'
56
+ if (location === 'cookie') {
57
+ result.warnings.push(
58
+ `API key scheme "${schemeName}" targets cookie "${paramName}", which the browser ` +
59
+ `attaches automatically — not appending it to the URL. Ensure the cookie is set ` +
60
+ `for this origin.`,
61
+ )
62
+ break
63
+ }
64
+ if (location === 'header') {
65
+ result.warnings.push(
66
+ `API key scheme "${schemeName}" targets header "${paramName}", but browser WebSocket/SSE ` +
67
+ `cannot set arbitrary headers — appending as a query param instead. ` +
68
+ `Ensure the server also accepts "${paramName}" as a query parameter.`,
69
+ )
70
+ }
71
+ if (token) result.queryParams[paramName] = token
72
+ break
73
+ }
74
+ case 'http': {
75
+ const scheme = schemeDef?.scheme?.toLowerCase() ?? ''
76
+ if (scheme === 'bearer' || scheme === '') {
77
+ if (token) result.accessToken = token
78
+ } else if (scheme === 'basic') {
79
+ result.warnings.push(
80
+ `HTTP Basic scheme "${schemeName}" cannot be sent over browser WebSocket/SSE — ` +
81
+ `connect will proceed without credentials.`,
82
+ )
83
+ }
84
+ break
85
+ }
86
+ case 'oauth2':
87
+ case 'openIdConnect':
88
+ if (token) result.accessToken = token
89
+ break
90
+ }
91
+ }
92
+
93
+ return result
94
+ }