@obsidiane/auth-client-js 1.0.3 → 1.0.5
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.
- package/README.md +725 -105
- package/fesm2022/obsidiane-auth-client-js.mjs +961 -0
- package/fesm2022/obsidiane-auth-client-js.mjs.map +1 -0
- package/index.d.ts +361 -0
- package/obsidiane-auth-client-js-0.1.0.tgz +0 -0
- package/package.json +15 -36
- package/DEVELOPMENT.md +0 -226
- package/dist/index.js +0 -10
- package/index.ts +0 -10
- package/src/lib/bridge/rest/api-platform.adapter.ts +0 -84
- package/src/lib/bridge/rest/http-request.options.ts +0 -21
- package/src/lib/bridge/rest/query-builder.ts +0 -43
- package/src/lib/bridge/sse/eventsource-wrapper.ts +0 -70
- package/src/lib/bridge/sse/mercure-topic.mapper.ts +0 -48
- package/src/lib/bridge/sse/mercure-url.builder.ts +0 -17
- package/src/lib/bridge/sse/mercure.adapter.ts +0 -261
- package/src/lib/bridge/sse/ref-count-topic.registry.ts +0 -45
- package/src/lib/bridge.types.ts +0 -33
- package/src/lib/facades/bridge.facade.ts +0 -108
- package/src/lib/facades/facade.factory.ts +0 -38
- package/src/lib/facades/facade.interface.ts +0 -30
- package/src/lib/facades/resource.facade.ts +0 -101
- package/src/lib/interceptors/bridge-debug.interceptor.ts +0 -32
- package/src/lib/interceptors/bridge-defaults.interceptor.ts +0 -53
- package/src/lib/interceptors/content-type.interceptor.ts +0 -49
- package/src/lib/interceptors/singleflight.interceptor.ts +0 -55
- package/src/lib/ports/realtime.port.ts +0 -36
- package/src/lib/ports/resource-repository.port.ts +0 -78
- package/src/lib/provide-bridge.ts +0 -148
- package/src/lib/tokens.ts +0 -20
- package/src/lib/utils/url.ts +0 -15
- package/src/models/Auth.ts +0 -5
- package/src/models/AuthInviteCompleteInputInviteComplete.ts +0 -7
- package/src/models/AuthInviteUserInputInviteSend.ts +0 -5
- package/src/models/AuthLdJson.ts +0 -5
- package/src/models/AuthPasswordForgotInputPasswordForgot.ts +0 -5
- package/src/models/AuthPasswordResetInputPasswordReset.ts +0 -6
- package/src/models/AuthRegisterUserInputUserRegister.ts +0 -6
- package/src/models/FrontendConfig.ts +0 -12
- package/src/models/InvitePreview.ts +0 -8
- package/src/models/InviteUserInviteRead.ts +0 -9
- package/src/models/Setup.ts +0 -5
- package/src/models/SetupRegisterUserInputUserRegister.ts +0 -6
- package/src/models/UserUpdateUserRolesInputUserRoles.ts +0 -5
- package/src/models/UserUserRead.ts +0 -9
- package/src/models/index.ts +0 -14
- package/src/public-api.ts +0 -9
- package/tsconfig.json +0 -23
package/DEVELOPMENT.md
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# Development Guide
|
|
2
|
-
|
|
3
|
-
## Architecture Overview
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
@obsidiane/auth-client-js
|
|
7
|
-
├── Bridge (Meridiane-generated) ← Auto-generated from OpenAPI
|
|
8
|
-
│ ├── src/lib/ ← HTTP client, facades, interceptors
|
|
9
|
-
│ ├── src/models/ ← TypeScript types
|
|
10
|
-
│ └── src/public-api.ts ← Main exports
|
|
11
|
-
│
|
|
12
|
-
└── Config
|
|
13
|
-
├── package.json ← NPM metadata
|
|
14
|
-
├── tsconfig.json ← TypeScript config
|
|
15
|
-
└── .gitignore ← Ignore generated /src/
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## File Structure
|
|
19
|
-
|
|
20
|
-
### Generated Files (auto-ignored)
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
src/ ← GENERATED by Meridiane, ignored in git
|
|
24
|
-
├── lib/
|
|
25
|
-
│ ├── bridge/
|
|
26
|
-
│ │ ├── rest/ ← HTTP adapters
|
|
27
|
-
│ │ └── sse/ ← Realtime (optional)
|
|
28
|
-
│ ├── facades/ ← Resource facades
|
|
29
|
-
│ ├── interceptors/ ← HTTP interceptors
|
|
30
|
-
│ ├── ports/ ← Interfaces
|
|
31
|
-
│ ├── utils/ ← Helpers
|
|
32
|
-
│ ├── provide-bridge.ts ← Angular provider
|
|
33
|
-
│ ├── bridge.types.ts ← Core types
|
|
34
|
-
│ └── tokens.ts ← DI tokens
|
|
35
|
-
├── models/ ← Generated from OpenAPI schemas
|
|
36
|
-
└── public-api.ts ← Main exports
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Manual Files (version-controlled)
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
package.json
|
|
43
|
-
tsconfig.json
|
|
44
|
-
README.md
|
|
45
|
-
DEVELOPMENT.md (this file)
|
|
46
|
-
.gitignore
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Regeneration Workflow
|
|
50
|
-
|
|
51
|
-
### When Backend API Changes
|
|
52
|
-
|
|
53
|
-
1. **Backend deploys** new endpoints to `/api/docs.json`
|
|
54
|
-
|
|
55
|
-
2. **Run generation**:
|
|
56
|
-
```bash
|
|
57
|
-
make sdks
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
3. **What happens**:
|
|
61
|
-
- Downloads OpenAPI spec from `http://localhost:9000/api/docs.json`
|
|
62
|
-
- Runs Meridiane to generate the bridge
|
|
63
|
-
- Copies `src/` into `packages/auth-client-js/src/`
|
|
64
|
-
|
|
65
|
-
4. **Commit**:
|
|
66
|
-
```bash
|
|
67
|
-
git add packages/auth-client-js/
|
|
68
|
-
git commit -m "chore: regenerate SDKs from OpenAPI spec"
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
5. **Publish** (if needed):
|
|
72
|
-
```bash
|
|
73
|
-
cd packages/auth-client-js
|
|
74
|
-
npm run build
|
|
75
|
-
npm publish
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Adding New Features
|
|
79
|
-
|
|
80
|
-
### Scenario 1: New HTTP Endpoint
|
|
81
|
-
|
|
82
|
-
**The endpoint is added in the backend API** → automatically generated by Meridiane.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
make sdk-npm
|
|
86
|
-
# Done! New endpoint is in src/ automatically
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Building for Publication
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# Install dependencies (if needed for TypeScript compilation)
|
|
93
|
-
npm install
|
|
94
|
-
|
|
95
|
-
# Build TypeScript → JavaScript
|
|
96
|
-
npm run build
|
|
97
|
-
|
|
98
|
-
# Output in ./dist/
|
|
99
|
-
ls -la dist/
|
|
100
|
-
|
|
101
|
-
# Publish to npm
|
|
102
|
-
npm publish
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Using in Applications
|
|
106
|
-
|
|
107
|
-
### In Angular App
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// app.config.ts
|
|
111
|
-
import { provideBridge } from '@obsidiane/auth-client-js';
|
|
112
|
-
|
|
113
|
-
export const appConfig: ApplicationConfig = {
|
|
114
|
-
providers: [
|
|
115
|
-
provideBridge({
|
|
116
|
-
baseUrl: 'http://localhost:9000',
|
|
117
|
-
}),
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### In a Service
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import { FacadeFactory } from '@obsidiane/auth-client-js';
|
|
126
|
-
import type { UserUserRead } from '@obsidiane/auth-client-js';
|
|
127
|
-
|
|
128
|
-
@Injectable({ providedIn: 'root' })
|
|
129
|
-
export class UsersService {
|
|
130
|
-
private readonly factory = inject(FacadeFactory);
|
|
131
|
-
private readonly facade = this.factory.create<UserUserRead>({
|
|
132
|
-
url: '/api/users',
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
getAll() {
|
|
136
|
-
return this.facade.getCollection$();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
getOne(id: string) {
|
|
140
|
-
return this.facade.get$(id);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Testing
|
|
146
|
-
|
|
147
|
-
The bridge (Meridiane) is tested separately in its own project.
|
|
148
|
-
|
|
149
|
-
## Troubleshooting
|
|
150
|
-
|
|
151
|
-
### Build Fails
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
npm run build
|
|
155
|
-
|
|
156
|
-
# If tsc not found
|
|
157
|
-
npm install
|
|
158
|
-
npm run build
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### "Cannot find module 'src/public-api'"
|
|
162
|
-
|
|
163
|
-
The `src/` folder doesn't exist. You need to generate it:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
make sdk-npm
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## CI/CD Integration
|
|
170
|
-
|
|
171
|
-
Example GitHub Actions workflow:
|
|
172
|
-
|
|
173
|
-
```yaml
|
|
174
|
-
name: Publish SDK
|
|
175
|
-
|
|
176
|
-
on:
|
|
177
|
-
push:
|
|
178
|
-
branches: [main]
|
|
179
|
-
|
|
180
|
-
jobs:
|
|
181
|
-
publish:
|
|
182
|
-
runs-on: ubuntu-latest
|
|
183
|
-
steps:
|
|
184
|
-
- uses: actions/checkout@v3
|
|
185
|
-
|
|
186
|
-
# Regenerate from OpenAPI (ensures freshness)
|
|
187
|
-
- run: make sdk-npm
|
|
188
|
-
|
|
189
|
-
# Build
|
|
190
|
-
- run: npm ci
|
|
191
|
-
working-directory: packages/auth-client-js
|
|
192
|
-
- run: npm run build
|
|
193
|
-
working-directory: packages/auth-client-js
|
|
194
|
-
|
|
195
|
-
# Publish to npm
|
|
196
|
-
- run: npm publish
|
|
197
|
-
working-directory: packages/auth-client-js
|
|
198
|
-
env:
|
|
199
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Synchronization with webfront/bridge
|
|
203
|
-
|
|
204
|
-
Both the SDK and the webfront bridge are generated from the same OpenAPI spec:
|
|
205
|
-
|
|
206
|
-
- **webfront/bridge**: Direct use in the Angular app
|
|
207
|
-
- **packages/auth-client-js**: Packaged for reuse in other projects
|
|
208
|
-
|
|
209
|
-
They should be **identical in functionality** but:
|
|
210
|
-
- Bridge: used directly via imports (`import { provideBridge }`)
|
|
211
|
-
- SDK: packaged as npm module (`npm install @obsidiane/auth-client-js`)
|
|
212
|
-
|
|
213
|
-
Both can coexist in your monorepo.
|
|
214
|
-
|
|
215
|
-
## Maintenance Checklist
|
|
216
|
-
|
|
217
|
-
- [ ] Run `make sdks` after API changes
|
|
218
|
-
- [ ] Version bump before publishing (in `package.json`)
|
|
219
|
-
- [ ] Run `npm run build` before release
|
|
220
|
-
- [ ] Commit `package.json` + `package-lock.json` changes
|
|
221
|
-
|
|
222
|
-
## References
|
|
223
|
-
|
|
224
|
-
- [Meridiane Documentation](https://github.com/obsidiane-lab/meridiane)
|
|
225
|
-
- [Angular HTTP Client](https://angular.io/guide/http)
|
|
226
|
-
- [Angular Dependency Injection](https://angular.io/guide/dependency-injection)
|
package/dist/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Obsidiane Auth Client for Angular/TypeScript
|
|
3
|
-
*
|
|
4
|
-
* Main entry point that exports the Meridiane-generated bridge:
|
|
5
|
-
* - HTTP client (BridgeFacade)
|
|
6
|
-
* - Facades (resource-based API)
|
|
7
|
-
* - Models (auto-generated types from OpenAPI)
|
|
8
|
-
*/
|
|
9
|
-
export * from './src/public-api';
|
|
10
|
-
//# sourceMappingURL=index.js.map
|
package/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Obsidiane Auth Client for Angular/TypeScript
|
|
3
|
-
*
|
|
4
|
-
* Main entry point that exports the Meridiane-generated bridge:
|
|
5
|
-
* - HTTP client (BridgeFacade)
|
|
6
|
-
* - Facades (resource-based API)
|
|
7
|
-
* - Models (auto-generated types from OpenAPI)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export * from './src/public-api';
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import {HttpClient} from '@angular/common/http';
|
|
2
|
-
import {Observable} from 'rxjs';
|
|
3
|
-
import {toHttpParams} from './query-builder';
|
|
4
|
-
import {buildHttpRequestOptions} from './http-request.options';
|
|
5
|
-
import {
|
|
6
|
-
Collection,
|
|
7
|
-
HttpCallOptions,
|
|
8
|
-
HttpRequestConfig,
|
|
9
|
-
Iri,
|
|
10
|
-
IriRequired,
|
|
11
|
-
Item,
|
|
12
|
-
AnyQuery,
|
|
13
|
-
ResourceRepository,
|
|
14
|
-
} from '../../ports/resource-repository.port';
|
|
15
|
-
import {resolveUrl} from '../../utils/url';
|
|
16
|
-
|
|
17
|
-
export class ApiPlatformRestRepository<T extends Item> implements ResourceRepository<T> {
|
|
18
|
-
constructor(
|
|
19
|
-
private readonly http: HttpClient,
|
|
20
|
-
private readonly apiBase: string,
|
|
21
|
-
private readonly resourcePath: Iri,
|
|
22
|
-
private readonly withCredentialsDefault: boolean,
|
|
23
|
-
) {
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getCollection$(query?: AnyQuery, opts?: HttpCallOptions): Observable<Collection<T>> {
|
|
27
|
-
const params = toHttpParams(query);
|
|
28
|
-
return this.http.get<Collection<T>>(this.resolveUrl(this.resourcePath), {
|
|
29
|
-
params,
|
|
30
|
-
headers: opts?.headers,
|
|
31
|
-
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get$(iri: IriRequired, opts?: HttpCallOptions): Observable<T> {
|
|
36
|
-
return this.http.get<T>(this.resolveUrl(iri), {
|
|
37
|
-
headers: opts?.headers,
|
|
38
|
-
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
post$(payload: Partial<T>, opts?: HttpCallOptions): Observable<T> {
|
|
43
|
-
return this.http.post<T>(this.resolveUrl(this.resourcePath), payload, {
|
|
44
|
-
headers: opts?.headers,
|
|
45
|
-
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
patch$(iri: IriRequired, changes: Partial<T>, opts?: HttpCallOptions): Observable<T> {
|
|
50
|
-
return this.http.patch<T>(this.resolveUrl(iri), changes, {
|
|
51
|
-
headers: opts?.headers,
|
|
52
|
-
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
put$(iri: IriRequired, payload: Partial<T>, opts?: HttpCallOptions): Observable<T> {
|
|
57
|
-
return this.http.put<T>(this.resolveUrl(iri), payload, {
|
|
58
|
-
headers: opts?.headers,
|
|
59
|
-
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
delete$(iri: IriRequired, opts?: HttpCallOptions): Observable<void> {
|
|
64
|
-
return this.http.delete<void>(this.resolveUrl(iri), {
|
|
65
|
-
headers: opts?.headers,
|
|
66
|
-
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
request$<R = unknown, B = unknown>(req: HttpRequestConfig<B>): Observable<R> {
|
|
71
|
-
// Low-level escape hatch for non-standard endpoints (custom controllers, uploads, etc.).
|
|
72
|
-
const {method, url} = req;
|
|
73
|
-
|
|
74
|
-
const targetUrl = this.resolveUrl(url ?? this.resourcePath);
|
|
75
|
-
const mergedOptions = buildHttpRequestOptions(req, {withCredentialsDefault: this.withCredentialsDefault});
|
|
76
|
-
return this.http.request<R>(method, targetUrl, mergedOptions as {observe: 'body'});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private resolveUrl(path?: Iri): string {
|
|
80
|
-
const effectivePath = path ?? this.resourcePath;
|
|
81
|
-
if (!effectivePath) throw new Error('ApiPlatformRestRepository: missing url and resourcePath');
|
|
82
|
-
return resolveUrl(this.apiBase, effectivePath);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {HttpRequestConfig} from '../../ports/resource-repository.port';
|
|
2
|
-
import {toHttpParams} from './query-builder';
|
|
3
|
-
|
|
4
|
-
export function buildHttpRequestOptions(
|
|
5
|
-
req: HttpRequestConfig,
|
|
6
|
-
{withCredentialsDefault}: {withCredentialsDefault: boolean}
|
|
7
|
-
): Record<string, unknown> {
|
|
8
|
-
const {query, body, headers, responseType, withCredentials, options = {}} = req;
|
|
9
|
-
const mergedOptions: Record<string, unknown> = {...options};
|
|
10
|
-
|
|
11
|
-
if (headers) mergedOptions['headers'] = headers;
|
|
12
|
-
if (query) mergedOptions['params'] = toHttpParams(query);
|
|
13
|
-
if (body !== undefined) mergedOptions['body'] = body;
|
|
14
|
-
|
|
15
|
-
mergedOptions['responseType'] = (responseType ?? (mergedOptions['responseType'] as any) ?? 'json') as any;
|
|
16
|
-
mergedOptions['withCredentials'] =
|
|
17
|
-
withCredentials ?? (mergedOptions['withCredentials'] as any) ?? withCredentialsDefault;
|
|
18
|
-
mergedOptions['observe'] = 'body';
|
|
19
|
-
|
|
20
|
-
return mergedOptions;
|
|
21
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {HttpParams} from '@angular/common/http';
|
|
2
|
-
import {AnyQuery, Query, QueryParamValue} from '../../ports/resource-repository.port';
|
|
3
|
-
|
|
4
|
-
export function toHttpParams(q: AnyQuery | undefined): HttpParams {
|
|
5
|
-
if (!q) return new HttpParams();
|
|
6
|
-
if (q instanceof HttpParams) return q;
|
|
7
|
-
|
|
8
|
-
const fromObject: Record<string, string | string[]> = {};
|
|
9
|
-
|
|
10
|
-
const consumed = new Set<string>();
|
|
11
|
-
const maybeQuery = q as Query;
|
|
12
|
-
if (maybeQuery.page != null) {
|
|
13
|
-
fromObject['page'] = String(maybeQuery.page);
|
|
14
|
-
consumed.add('page');
|
|
15
|
-
}
|
|
16
|
-
if (maybeQuery.itemsPerPage != null) {
|
|
17
|
-
fromObject['itemsPerPage'] = String(maybeQuery.itemsPerPage);
|
|
18
|
-
consumed.add('itemsPerPage');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (q.filters) {
|
|
22
|
-
consumed.add('filters');
|
|
23
|
-
for (const [k, v] of Object.entries(q.filters)) {
|
|
24
|
-
assign(fromObject, k, v);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
for (const [k, v] of Object.entries(q as Record<string, QueryParamValue>)) {
|
|
29
|
-
if (consumed.has(k)) continue;
|
|
30
|
-
assign(fromObject, k, v);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return new HttpParams({fromObject});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function assign(target: Record<string, string | string[]>, key: string, value: QueryParamValue | undefined) {
|
|
37
|
-
if (value == null) return;
|
|
38
|
-
if (Array.isArray(value)) {
|
|
39
|
-
target[key] = value.map(String);
|
|
40
|
-
} else {
|
|
41
|
-
target[key] = String(value);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {ReplaySubject, Subject} from 'rxjs';
|
|
2
|
-
import {SseEvent, SseOptions, RealtimeStatus} from '../../ports/realtime.port';
|
|
3
|
-
import {BridgeLogger} from '../../bridge.types';
|
|
4
|
-
|
|
5
|
-
export class EventSourceWrapper {
|
|
6
|
-
private es?: EventSource;
|
|
7
|
-
|
|
8
|
-
private readonly statusSub = new ReplaySubject<RealtimeStatus>(1);
|
|
9
|
-
private readonly eventSub = new Subject<SseEvent>();
|
|
10
|
-
|
|
11
|
-
readonly status$ = this.statusSub.asObservable();
|
|
12
|
-
readonly events$ = this.eventSub.asObservable();
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly url: string,
|
|
16
|
-
private readonly opts: SseOptions = {},
|
|
17
|
-
private readonly logger?: BridgeLogger,
|
|
18
|
-
) {
|
|
19
|
-
this.setState('closed');
|
|
20
|
-
this.log('[SSE] init', {url, withCredentials: !!opts.withCredentials});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
open(): void {
|
|
24
|
-
if (this.es) {
|
|
25
|
-
this.log('[SSE] open() ignored: already open');
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
this.setState('connecting');
|
|
30
|
-
this.log('[SSE] open', {url: this.url});
|
|
31
|
-
|
|
32
|
-
const es = new EventSource(this.url, {
|
|
33
|
-
withCredentials: !!this.opts.withCredentials,
|
|
34
|
-
});
|
|
35
|
-
this.es = es;
|
|
36
|
-
|
|
37
|
-
es.onopen = () => {
|
|
38
|
-
this.setState('connected');
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
es.onmessage = (ev) => {
|
|
42
|
-
this.eventSub.next({type: 'message', data: ev.data, lastEventId: ev.lastEventId || undefined});
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
es.onerror = () => {
|
|
46
|
-
// The browser will retry automatically. We stay in "connecting".
|
|
47
|
-
this.log('[SSE] error');
|
|
48
|
-
this.setState('connecting');
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
close(): void {
|
|
53
|
-
if (this.es) {
|
|
54
|
-
this.es.close();
|
|
55
|
-
this.es = undefined;
|
|
56
|
-
this.log('[SSE] closed');
|
|
57
|
-
}
|
|
58
|
-
this.setState('closed');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ──────────────── internals ────────────────
|
|
62
|
-
|
|
63
|
-
private setState(state: RealtimeStatus): void {
|
|
64
|
-
this.statusSub.next(state);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private log(...args: unknown[]): void {
|
|
68
|
-
this.logger?.debug?.(...args);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import {MercureTopicMode} from '../../bridge.types';
|
|
2
|
-
|
|
3
|
-
export class MercureTopicMapper {
|
|
4
|
-
private readonly apiBaseUrl: URL;
|
|
5
|
-
|
|
6
|
-
constructor(
|
|
7
|
-
apiBase: string,
|
|
8
|
-
private readonly mode: MercureTopicMode,
|
|
9
|
-
) {
|
|
10
|
-
this.apiBaseUrl = new URL(apiBase);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Canonical value used for ref-counting and as the "topic" query param value.
|
|
15
|
-
* - mode "url": always absolute, same-origin resolved
|
|
16
|
-
* - mode "iri": same-origin path+query+hash ("/api/..."), otherwise keep as-is
|
|
17
|
-
*/
|
|
18
|
-
toTopic(input: string): string {
|
|
19
|
-
if (this.mode === 'url') return this.toAbsoluteUrl(input);
|
|
20
|
-
return this.toRelativeIriIfSameOrigin(input);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Canonical value used to compare incoming payload IRIs with subscribed IRIs.
|
|
25
|
-
* We keep payload matching stable by using same-origin relative IRIs ("/api/...").
|
|
26
|
-
*/
|
|
27
|
-
toPayloadIri(input: string): string {
|
|
28
|
-
return this.toRelativeIriIfSameOrigin(input);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private toAbsoluteUrl(input: string): string {
|
|
32
|
-
try {
|
|
33
|
-
return new URL(input, this.apiBaseUrl).toString();
|
|
34
|
-
} catch {
|
|
35
|
-
return input;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private toRelativeIriIfSameOrigin(input: string): string {
|
|
40
|
-
try {
|
|
41
|
-
const url = new URL(input, this.apiBaseUrl);
|
|
42
|
-
if (url.origin !== this.apiBaseUrl.origin) return input;
|
|
43
|
-
return `${url.pathname}${url.search}${url.hash}`;
|
|
44
|
-
} catch {
|
|
45
|
-
return input;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export class MercureUrlBuilder {
|
|
2
|
-
/**
|
|
3
|
-
* Builds the Mercure hub URL with one `topic=` parameter per topic.
|
|
4
|
-
* The adapter is responsible for canonicalising topics beforehand.
|
|
5
|
-
*/
|
|
6
|
-
build(hubUrl: string, topics: ReadonlySet<string>, lastEventId?: string): string {
|
|
7
|
-
const url = new URL(hubUrl);
|
|
8
|
-
if (lastEventId) {
|
|
9
|
-
url.searchParams.set('lastEventID', lastEventId);
|
|
10
|
-
}
|
|
11
|
-
url.searchParams.delete('topic');
|
|
12
|
-
for (const topic of topics) {
|
|
13
|
-
url.searchParams.append('topic', topic);
|
|
14
|
-
}
|
|
15
|
-
return url.toString();
|
|
16
|
-
}
|
|
17
|
-
}
|