@angular-helpers/browser-web-apis 21.5.0 → 21.6.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.
- package/fesm2022/angular-helpers-browser-web-apis-experimental.mjs +478 -0
- package/fesm2022/angular-helpers-browser-web-apis.mjs +398 -628
- package/package.json +5 -1
- package/types/angular-helpers-browser-web-apis-experimental.d.ts +325 -0
- package/types/angular-helpers-browser-web-apis.d.ts +146 -322
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, DestroyRef, PLATFORM_ID, Injectable, signal, computed, isSignal, effect, ElementRef, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
|
|
4
|
-
import { Observable, fromEvent,
|
|
4
|
+
import { Observable, fromEvent, Subject, of, map as map$1 } from 'rxjs';
|
|
5
5
|
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
6
6
|
import { filter, distinctUntilChanged, map } from 'rxjs/operators';
|
|
7
7
|
import { Router } from '@angular/router';
|
|
@@ -1036,196 +1036,424 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
1036
1036
|
type: Injectable
|
|
1037
1037
|
}], ctorParameters: () => [] });
|
|
1038
1038
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1039
|
+
const DEFAULT_MAX_RECONNECT_DELAY = 30_000;
|
|
1040
|
+
const DEFAULT_REQUEST_TIMEOUT = 30_000;
|
|
1041
|
+
/**
|
|
1042
|
+
* Stateful WebSocket client wrapping a single connection. One instance per logical
|
|
1043
|
+
* connection (do NOT share between `connect()` calls).
|
|
1044
|
+
*
|
|
1045
|
+
* Surfaces:
|
|
1046
|
+
* - `status`: signal of the current connection state.
|
|
1047
|
+
* - `messages$`: stream of every received message (parsed JSON).
|
|
1048
|
+
* - `send` / `sendRaw`: outbound traffic.
|
|
1049
|
+
* - `request<T>(type, data)`: round-trip with id correlation and timeout.
|
|
1050
|
+
* - `close`: idempotent disposal.
|
|
1051
|
+
*
|
|
1052
|
+
* Reconnect uses exponential backoff with jitter, capped by `maxReconnectDelay`.
|
|
1053
|
+
*/
|
|
1054
|
+
class WebSocketClient {
|
|
1055
|
+
config;
|
|
1056
|
+
logger;
|
|
1057
|
+
socket = null;
|
|
1049
1058
|
reconnectTimer = null;
|
|
1050
1059
|
heartbeatTimer = null;
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1060
|
+
_status;
|
|
1061
|
+
_messages$ = new Subject();
|
|
1062
|
+
pendingRequests = new Map();
|
|
1063
|
+
disposed = false;
|
|
1064
|
+
reconnectAttempts = 0;
|
|
1065
|
+
constructor(config, logger, destroyRef) {
|
|
1066
|
+
this.config = config;
|
|
1067
|
+
this.logger = logger;
|
|
1068
|
+
this._status = signal({
|
|
1069
|
+
state: 'idle',
|
|
1070
|
+
reconnectAttempts: 0,
|
|
1071
|
+
error: null,
|
|
1072
|
+
}, ...(ngDevMode ? [{ debugName: "_status" }] : /* istanbul ignore next */ []));
|
|
1073
|
+
if (destroyRef) {
|
|
1074
|
+
destroyRef.onDestroy(() => this.close());
|
|
1059
1075
|
}
|
|
1076
|
+
this.openSocket();
|
|
1060
1077
|
}
|
|
1061
|
-
|
|
1062
|
-
this.
|
|
1063
|
-
return new Observable((observer) => {
|
|
1064
|
-
this.disconnect(); // Disconnect existing connection if any
|
|
1065
|
-
this.updateStatus({
|
|
1066
|
-
connected: false,
|
|
1067
|
-
connecting: true,
|
|
1068
|
-
reconnecting: false,
|
|
1069
|
-
reconnectAttempts: 0,
|
|
1070
|
-
});
|
|
1071
|
-
try {
|
|
1072
|
-
this.webSocket = new WebSocket(config.url, config.protocols);
|
|
1073
|
-
this.setupWebSocketHandlers(config);
|
|
1074
|
-
observer.next(this.statusSubject.getValue());
|
|
1075
|
-
}
|
|
1076
|
-
catch (error) {
|
|
1077
|
-
this.logError('Error creating WebSocket:', error);
|
|
1078
|
-
this.updateStatus({
|
|
1079
|
-
connected: false,
|
|
1080
|
-
connecting: false,
|
|
1081
|
-
reconnecting: false,
|
|
1082
|
-
error: error instanceof Error ? error.message : 'Connection failed',
|
|
1083
|
-
reconnectAttempts: 0,
|
|
1084
|
-
});
|
|
1085
|
-
observer.next(this.statusSubject.getValue());
|
|
1086
|
-
}
|
|
1087
|
-
return () => {
|
|
1088
|
-
this.disconnect();
|
|
1089
|
-
};
|
|
1090
|
-
});
|
|
1078
|
+
get status() {
|
|
1079
|
+
return this._status.asReadonly();
|
|
1091
1080
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
this.heartbeatTimer = null;
|
|
1100
|
-
}
|
|
1101
|
-
if (this.webSocket) {
|
|
1102
|
-
this.webSocket.close();
|
|
1103
|
-
this.webSocket = null;
|
|
1104
|
-
}
|
|
1105
|
-
this.updateStatus({
|
|
1106
|
-
connected: false,
|
|
1107
|
-
connecting: false,
|
|
1108
|
-
reconnecting: false,
|
|
1109
|
-
reconnectAttempts: 0,
|
|
1110
|
-
});
|
|
1081
|
+
get messages$() {
|
|
1082
|
+
return this._messages$.asObservable();
|
|
1083
|
+
}
|
|
1084
|
+
messagesByType(type) {
|
|
1085
|
+
return this._messages$
|
|
1086
|
+
.asObservable()
|
|
1087
|
+
.pipe(filter((msg) => msg.type === type));
|
|
1111
1088
|
}
|
|
1112
1089
|
send(message) {
|
|
1113
|
-
if (!this.
|
|
1090
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
1114
1091
|
throw new Error('WebSocket is not connected');
|
|
1115
1092
|
}
|
|
1116
|
-
const
|
|
1093
|
+
const enriched = {
|
|
1117
1094
|
...message,
|
|
1118
|
-
|
|
1095
|
+
id: message.id ?? this.generateId(),
|
|
1096
|
+
timestamp: message.timestamp ?? Date.now(),
|
|
1119
1097
|
};
|
|
1120
|
-
this.
|
|
1098
|
+
this.socket.send(JSON.stringify(enriched));
|
|
1121
1099
|
}
|
|
1122
1100
|
sendRaw(data) {
|
|
1123
|
-
if (!this.
|
|
1101
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
1124
1102
|
throw new Error('WebSocket is not connected');
|
|
1125
1103
|
}
|
|
1126
|
-
this.
|
|
1104
|
+
this.socket.send(data);
|
|
1127
1105
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1106
|
+
/**
|
|
1107
|
+
* Send a message and await a correlated response. The server MUST echo back the
|
|
1108
|
+
* `correlationId` from the request as `correlationId` on the response message.
|
|
1109
|
+
*/
|
|
1110
|
+
request(type, data, opts) {
|
|
1111
|
+
const id = this.generateId();
|
|
1112
|
+
const timeoutMs = opts?.timeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
1113
|
+
return new Promise((resolve, reject) => {
|
|
1114
|
+
const timer = setTimeout(() => {
|
|
1115
|
+
this.pendingRequests.delete(id);
|
|
1116
|
+
reject(new Error(`WebSocket request timeout after ${timeoutMs}ms`));
|
|
1117
|
+
}, timeoutMs);
|
|
1118
|
+
this.pendingRequests.set(id, {
|
|
1119
|
+
resolve: resolve,
|
|
1120
|
+
reject,
|
|
1121
|
+
timer,
|
|
1122
|
+
});
|
|
1123
|
+
try {
|
|
1124
|
+
this.send({ id, type, data, correlationId: id });
|
|
1125
|
+
}
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
clearTimeout(timer);
|
|
1128
|
+
this.pendingRequests.delete(id);
|
|
1129
|
+
reject(error instanceof Error ? error : new Error('WebSocket send failed'));
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1130
1132
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
+
close() {
|
|
1134
|
+
if (this.disposed)
|
|
1135
|
+
return;
|
|
1136
|
+
this.disposed = true;
|
|
1137
|
+
this.clearTimers();
|
|
1138
|
+
if (this.socket) {
|
|
1139
|
+
try {
|
|
1140
|
+
this.socket.close();
|
|
1141
|
+
}
|
|
1142
|
+
catch {
|
|
1143
|
+
// Ignore — already closing.
|
|
1144
|
+
}
|
|
1145
|
+
this.socket = null;
|
|
1146
|
+
}
|
|
1147
|
+
this.rejectAllPending(new Error('WebSocket closed'));
|
|
1148
|
+
this.updateStatus({ state: 'closed', error: null });
|
|
1133
1149
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1150
|
+
/** Internal handle for tests and advanced usage. */
|
|
1151
|
+
getNativeSocket() {
|
|
1152
|
+
return this.socket;
|
|
1153
|
+
}
|
|
1154
|
+
// ---------- internals ----------
|
|
1155
|
+
openSocket() {
|
|
1156
|
+
if (this.disposed)
|
|
1157
|
+
return;
|
|
1158
|
+
this.updateStatus({ state: 'connecting', error: null });
|
|
1159
|
+
try {
|
|
1160
|
+
this.socket = new WebSocket(this.config.url, this.config.protocols);
|
|
1161
|
+
this.attachHandlers();
|
|
1162
|
+
}
|
|
1163
|
+
catch (error) {
|
|
1164
|
+
const message = error instanceof Error ? error.message : 'WebSocket open failed';
|
|
1165
|
+
this.logger.error('[websocket] Failed to construct socket', error);
|
|
1166
|
+
this.updateStatus({ state: 'closed', error: message });
|
|
1167
|
+
this.scheduleReconnect();
|
|
1168
|
+
}
|
|
1138
1169
|
}
|
|
1139
|
-
|
|
1140
|
-
if (!this.
|
|
1170
|
+
attachHandlers() {
|
|
1171
|
+
if (!this.socket)
|
|
1141
1172
|
return;
|
|
1142
|
-
this.
|
|
1143
|
-
this.logInfo(`Connected to: ${config.url}`);
|
|
1173
|
+
this.socket.onopen = () => {
|
|
1144
1174
|
this.reconnectAttempts = 0;
|
|
1145
|
-
this.updateStatus({
|
|
1146
|
-
|
|
1147
|
-
connecting: false,
|
|
1148
|
-
reconnecting: false,
|
|
1149
|
-
reconnectAttempts: 0,
|
|
1150
|
-
});
|
|
1151
|
-
// Start heartbeat if configured
|
|
1152
|
-
if (config.heartbeatInterval && config.heartbeatMessage) {
|
|
1153
|
-
this.startHeartbeat(config);
|
|
1154
|
-
}
|
|
1175
|
+
this.updateStatus({ state: 'open', error: null, reconnectAttempts: 0 });
|
|
1176
|
+
this.startHeartbeat();
|
|
1155
1177
|
};
|
|
1156
|
-
this.
|
|
1157
|
-
this.
|
|
1178
|
+
this.socket.onclose = (event) => {
|
|
1179
|
+
this.stopHeartbeat();
|
|
1180
|
+
if (this.disposed)
|
|
1181
|
+
return;
|
|
1158
1182
|
this.updateStatus({
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
reconnecting: false,
|
|
1162
|
-
reconnectAttempts: this.reconnectAttempts,
|
|
1183
|
+
state: 'closed',
|
|
1184
|
+
error: event.wasClean ? null : `closed: ${event.code} ${event.reason}`,
|
|
1163
1185
|
});
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
this.attemptReconnect(config);
|
|
1186
|
+
if (!event.wasClean) {
|
|
1187
|
+
this.scheduleReconnect();
|
|
1167
1188
|
}
|
|
1168
1189
|
};
|
|
1169
|
-
this.
|
|
1170
|
-
this.
|
|
1171
|
-
this.updateStatus({
|
|
1172
|
-
connected: false,
|
|
1173
|
-
connecting: false,
|
|
1174
|
-
reconnecting: false,
|
|
1175
|
-
error: 'WebSocket connection error',
|
|
1176
|
-
reconnectAttempts: this.reconnectAttempts,
|
|
1177
|
-
});
|
|
1190
|
+
this.socket.onerror = () => {
|
|
1191
|
+
this.updateStatus({ error: 'WebSocket connection error' });
|
|
1178
1192
|
};
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
1181
|
-
const message = JSON.parse(event.data);
|
|
1182
|
-
this.messageSubject.next(message);
|
|
1183
|
-
}
|
|
1184
|
-
catch (error) {
|
|
1185
|
-
this.logError('Error parsing message:', error);
|
|
1186
|
-
}
|
|
1193
|
+
this.socket.onmessage = (event) => {
|
|
1194
|
+
this.handleIncoming(event.data);
|
|
1187
1195
|
};
|
|
1188
1196
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1197
|
+
handleIncoming(raw) {
|
|
1198
|
+
let message;
|
|
1199
|
+
try {
|
|
1200
|
+
const text = typeof raw === 'string' ? raw : String(raw);
|
|
1201
|
+
message = JSON.parse(text);
|
|
1202
|
+
}
|
|
1203
|
+
catch (error) {
|
|
1204
|
+
this.logger.warn('[websocket] Failed to parse incoming message');
|
|
1205
|
+
this.logger.error('[websocket] parse error', error);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const correlationId = message.correlationId ?? message.id;
|
|
1209
|
+
if (correlationId && this.pendingRequests.has(correlationId)) {
|
|
1210
|
+
const pending = this.pendingRequests.get(correlationId);
|
|
1211
|
+
clearTimeout(pending.timer);
|
|
1212
|
+
this.pendingRequests.delete(correlationId);
|
|
1213
|
+
pending.resolve(message.data);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
this._messages$.next(message);
|
|
1198
1217
|
}
|
|
1199
|
-
|
|
1200
|
-
if (this.
|
|
1201
|
-
|
|
1218
|
+
scheduleReconnect() {
|
|
1219
|
+
if (this.disposed)
|
|
1220
|
+
return;
|
|
1221
|
+
const interval = this.config.reconnectInterval ?? 0;
|
|
1222
|
+
const maxAttempts = this.config.maxReconnectAttempts ?? 0;
|
|
1223
|
+
if (interval <= 0 || maxAttempts <= 0)
|
|
1224
|
+
return;
|
|
1225
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
1226
|
+
this.updateStatus({
|
|
1227
|
+
state: 'closed',
|
|
1228
|
+
error: `Max reconnect attempts (${maxAttempts}) reached`,
|
|
1229
|
+
});
|
|
1202
1230
|
return;
|
|
1203
1231
|
}
|
|
1204
|
-
this.reconnectAttempts
|
|
1232
|
+
this.reconnectAttempts += 1;
|
|
1233
|
+
const delay = WebSocketClient.computeBackoffDelay(this.reconnectAttempts, interval, this.config.maxReconnectDelay ?? DEFAULT_MAX_RECONNECT_DELAY);
|
|
1205
1234
|
this.updateStatus({
|
|
1206
|
-
|
|
1207
|
-
connecting: false,
|
|
1208
|
-
reconnecting: true,
|
|
1235
|
+
state: 'reconnecting',
|
|
1209
1236
|
reconnectAttempts: this.reconnectAttempts,
|
|
1210
1237
|
});
|
|
1211
1238
|
this.reconnectTimer = setTimeout(() => {
|
|
1212
|
-
this.
|
|
1213
|
-
this.
|
|
1214
|
-
},
|
|
1239
|
+
this.reconnectTimer = null;
|
|
1240
|
+
this.openSocket();
|
|
1241
|
+
}, delay);
|
|
1215
1242
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1243
|
+
/**
|
|
1244
|
+
* Exponential backoff with full jitter:
|
|
1245
|
+
* baseDelay = min(maxDelay, interval * 2^(attempt - 1))
|
|
1246
|
+
* delay = random(0, baseDelay)
|
|
1247
|
+
*/
|
|
1248
|
+
static computeBackoffDelay(attempt, interval, maxDelay) {
|
|
1249
|
+
const exp = Math.min(maxDelay, interval * Math.pow(2, attempt - 1));
|
|
1250
|
+
return Math.floor(Math.random() * exp);
|
|
1251
|
+
}
|
|
1252
|
+
startHeartbeat() {
|
|
1253
|
+
const { heartbeatInterval, heartbeatMessage } = this.config;
|
|
1254
|
+
if (!heartbeatInterval || heartbeatMessage === undefined)
|
|
1255
|
+
return;
|
|
1256
|
+
this.stopHeartbeat();
|
|
1257
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1258
|
+
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
1259
|
+
try {
|
|
1260
|
+
this.send({ type: 'heartbeat', data: heartbeatMessage });
|
|
1261
|
+
}
|
|
1262
|
+
catch (error) {
|
|
1263
|
+
this.logger.warn('[websocket] heartbeat send failed');
|
|
1264
|
+
this.logger.error('[websocket] heartbeat error', error);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}, heartbeatInterval);
|
|
1268
|
+
}
|
|
1269
|
+
stopHeartbeat() {
|
|
1270
|
+
if (this.heartbeatTimer) {
|
|
1271
|
+
clearInterval(this.heartbeatTimer);
|
|
1272
|
+
this.heartbeatTimer = null;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
clearTimers() {
|
|
1276
|
+
if (this.reconnectTimer) {
|
|
1277
|
+
clearTimeout(this.reconnectTimer);
|
|
1278
|
+
this.reconnectTimer = null;
|
|
1279
|
+
}
|
|
1280
|
+
this.stopHeartbeat();
|
|
1281
|
+
}
|
|
1282
|
+
rejectAllPending(reason) {
|
|
1283
|
+
this.pendingRequests.forEach((entry) => {
|
|
1284
|
+
clearTimeout(entry.timer);
|
|
1285
|
+
entry.reject(reason);
|
|
1286
|
+
});
|
|
1287
|
+
this.pendingRequests.clear();
|
|
1288
|
+
}
|
|
1289
|
+
updateStatus(partial) {
|
|
1290
|
+
this._status.update((current) => ({ ...current, ...partial }));
|
|
1291
|
+
}
|
|
1292
|
+
generateId() {
|
|
1293
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
1294
|
+
return globalThis.crypto.randomUUID();
|
|
1295
|
+
}
|
|
1296
|
+
return `ws-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
let legacyDeprecationLogged = false;
|
|
1301
|
+
/**
|
|
1302
|
+
* Service that creates and tracks `WebSocketClient` instances.
|
|
1303
|
+
*
|
|
1304
|
+
* Preferred usage:
|
|
1305
|
+
* ```ts
|
|
1306
|
+
* const ws = inject(WebSocketService);
|
|
1307
|
+
* const client = ws.createClient({ url: 'wss://...' });
|
|
1308
|
+
* effect(() => console.log(client.status()));
|
|
1309
|
+
* await client.request('ping', {});
|
|
1310
|
+
* ```
|
|
1311
|
+
*
|
|
1312
|
+
* Legacy usage (`connect()` returning Observable) is preserved for one minor cycle
|
|
1313
|
+
* and will be removed in v22.
|
|
1314
|
+
*/
|
|
1315
|
+
class WebSocketService extends BrowserApiBaseService {
|
|
1316
|
+
wsLogger = inject(BROWSER_API_LOGGER);
|
|
1317
|
+
clients = new Set();
|
|
1318
|
+
_cleanup = this.destroyRef.onDestroy(() => this.disposeAll());
|
|
1319
|
+
/** Legacy single-connection holder used by deprecated `connect()`/`send()` API. */
|
|
1320
|
+
legacyClient = null;
|
|
1321
|
+
getApiName() {
|
|
1322
|
+
return 'websocket';
|
|
1323
|
+
}
|
|
1324
|
+
ensureSupported() {
|
|
1325
|
+
super.ensureSupported();
|
|
1326
|
+
if (typeof WebSocket === 'undefined') {
|
|
1327
|
+
throw new Error('WebSocket API not supported in this browser');
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Create a new WebSocket client. The client owns one connection and is the recommended
|
|
1332
|
+
* surface for all interactions (status signal, request/response, reconnect, etc.).
|
|
1333
|
+
*
|
|
1334
|
+
* The returned client is automatically disposed when the host injector is destroyed.
|
|
1335
|
+
*/
|
|
1336
|
+
createClient(config) {
|
|
1337
|
+
this.ensureSupported();
|
|
1338
|
+
const client = new WebSocketClient(config, this.wsLogger, this.destroyRef);
|
|
1339
|
+
this.clients.add(client);
|
|
1340
|
+
return client;
|
|
1341
|
+
}
|
|
1342
|
+
/** Dispose every client created via `createClient()` (also called automatically on destroy). */
|
|
1343
|
+
disposeAll() {
|
|
1344
|
+
for (const client of this.clients) {
|
|
1345
|
+
client.close();
|
|
1346
|
+
}
|
|
1347
|
+
this.clients.clear();
|
|
1348
|
+
if (this.legacyClient) {
|
|
1349
|
+
this.legacyClient.close();
|
|
1350
|
+
this.legacyClient = null;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
// ---------- legacy API (deprecated) ----------
|
|
1354
|
+
/**
|
|
1355
|
+
* @deprecated Use {@link createClient} which returns a `WebSocketClient` exposing a
|
|
1356
|
+
* status signal, request/response, and proper reconnect. This wrapper will be removed
|
|
1357
|
+
* in v22.
|
|
1358
|
+
*/
|
|
1359
|
+
connect(config) {
|
|
1360
|
+
this.ensureSupported();
|
|
1361
|
+
this.warnLegacyOnce();
|
|
1362
|
+
return new Observable((observer) => {
|
|
1363
|
+
if (this.legacyClient) {
|
|
1364
|
+
this.legacyClient.close();
|
|
1365
|
+
}
|
|
1366
|
+
const client = new WebSocketClient(config, this.wsLogger);
|
|
1367
|
+
this.legacyClient = client;
|
|
1368
|
+
const sub = toObservableLike(client).subscribe({
|
|
1369
|
+
next: (status) => observer.next(status),
|
|
1370
|
+
error: (err) => observer.error(err),
|
|
1371
|
+
});
|
|
1372
|
+
return () => {
|
|
1373
|
+
sub.unsubscribe();
|
|
1374
|
+
client.close();
|
|
1375
|
+
if (this.legacyClient === client) {
|
|
1376
|
+
this.legacyClient = null;
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
/** @deprecated Use {@link createClient} and call `client.close()`. */
|
|
1382
|
+
disconnect() {
|
|
1383
|
+
if (this.legacyClient) {
|
|
1384
|
+
this.legacyClient.close();
|
|
1385
|
+
this.legacyClient = null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
/** @deprecated Use the client returned by {@link createClient}. */
|
|
1389
|
+
send(message) {
|
|
1390
|
+
if (!this.legacyClient) {
|
|
1391
|
+
throw new Error('No active legacy WebSocket. Call connect() first or use createClient().');
|
|
1392
|
+
}
|
|
1393
|
+
this.legacyClient.send(message);
|
|
1394
|
+
}
|
|
1395
|
+
/** @deprecated Use the client returned by {@link createClient}. */
|
|
1396
|
+
sendRaw(data) {
|
|
1397
|
+
if (!this.legacyClient) {
|
|
1398
|
+
throw new Error('No active legacy WebSocket. Call connect() first or use createClient().');
|
|
1399
|
+
}
|
|
1400
|
+
this.legacyClient.sendRaw(data);
|
|
1401
|
+
}
|
|
1402
|
+
/** @deprecated Use `client.status` from {@link createClient}. */
|
|
1403
|
+
getStatus() {
|
|
1404
|
+
return new Observable((observer) => {
|
|
1405
|
+
if (!this.legacyClient) {
|
|
1406
|
+
observer.next({
|
|
1407
|
+
connected: false,
|
|
1408
|
+
connecting: false,
|
|
1409
|
+
reconnecting: false,
|
|
1410
|
+
reconnectAttempts: 0,
|
|
1411
|
+
});
|
|
1412
|
+
return () => {
|
|
1413
|
+
// No-op
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
const sub = toObservableLike(this.legacyClient).subscribe((status) => observer.next(status));
|
|
1417
|
+
return () => sub.unsubscribe();
|
|
1418
|
+
});
|
|
1219
1419
|
}
|
|
1220
|
-
|
|
1420
|
+
/** @deprecated Use `client.messages$` from {@link createClient}. */
|
|
1421
|
+
getMessages() {
|
|
1422
|
+
if (!this.legacyClient) {
|
|
1423
|
+
return new Observable(() => {
|
|
1424
|
+
// No-op stream until connected.
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
return this.legacyClient.messages$;
|
|
1428
|
+
}
|
|
1429
|
+
/** @deprecated Use `client.messagesByType()` from {@link createClient}. */
|
|
1430
|
+
getMessagesByType(type) {
|
|
1431
|
+
if (!this.legacyClient) {
|
|
1432
|
+
return new Observable(() => {
|
|
1433
|
+
// No-op stream until connected.
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
return this.legacyClient.messagesByType(type);
|
|
1437
|
+
}
|
|
1438
|
+
/** @deprecated Use the client returned by {@link createClient}. */
|
|
1221
1439
|
getNativeWebSocket() {
|
|
1222
|
-
return this.
|
|
1440
|
+
return this.legacyClient?.getNativeSocket() ?? null;
|
|
1223
1441
|
}
|
|
1442
|
+
/** @deprecated Use `client.status()` from {@link createClient}. */
|
|
1224
1443
|
isConnected() {
|
|
1225
|
-
return this.
|
|
1444
|
+
return this.legacyClient?.status().state === 'open';
|
|
1226
1445
|
}
|
|
1446
|
+
/** @deprecated Use the native socket via `client.getNativeSocket()`. */
|
|
1227
1447
|
getReadyState() {
|
|
1228
|
-
return this.
|
|
1448
|
+
return this.legacyClient?.getNativeSocket()?.readyState ?? WebSocket.CLOSED;
|
|
1449
|
+
}
|
|
1450
|
+
warnLegacyOnce() {
|
|
1451
|
+
if (legacyDeprecationLogged)
|
|
1452
|
+
return;
|
|
1453
|
+
legacyDeprecationLogged = true;
|
|
1454
|
+
this.wsLogger.warn('[websocket] WebSocketService.connect() is deprecated. Use WebSocketService.createClient() ' +
|
|
1455
|
+
'which returns a WebSocketClient with a status signal, request/response, and proper reconnect. ' +
|
|
1456
|
+
'The legacy API will be removed in v22.');
|
|
1229
1457
|
}
|
|
1230
1458
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1231
1459
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService });
|
|
@@ -1233,6 +1461,27 @@ class WebSocketService extends BrowserApiBaseService {
|
|
|
1233
1461
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, decorators: [{
|
|
1234
1462
|
type: Injectable
|
|
1235
1463
|
}] });
|
|
1464
|
+
/**
|
|
1465
|
+
* Build a stream of legacy `WebSocketStatus` snapshots from a v2 client. Used to keep the
|
|
1466
|
+
* deprecated `connect()` API behaving like before (Observable of legacy status).
|
|
1467
|
+
*/
|
|
1468
|
+
function toObservableLike(client) {
|
|
1469
|
+
return new Observable((observer) => {
|
|
1470
|
+
const emit = () => {
|
|
1471
|
+
const v2 = client.status();
|
|
1472
|
+
observer.next({
|
|
1473
|
+
connected: v2.state === 'open',
|
|
1474
|
+
connecting: v2.state === 'connecting',
|
|
1475
|
+
reconnecting: v2.state === 'reconnecting',
|
|
1476
|
+
error: v2.error ?? undefined,
|
|
1477
|
+
reconnectAttempts: v2.reconnectAttempts,
|
|
1478
|
+
});
|
|
1479
|
+
};
|
|
1480
|
+
emit();
|
|
1481
|
+
const id = setInterval(emit, 100);
|
|
1482
|
+
return () => clearInterval(id);
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1236
1485
|
|
|
1237
1486
|
class WebWorkerService extends BrowserApiBaseService {
|
|
1238
1487
|
workers = new Map();
|
|
@@ -2441,106 +2690,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
2441
2690
|
type: Injectable
|
|
2442
2691
|
}] });
|
|
2443
2692
|
|
|
2444
|
-
function getIdleDetectorClass$1() {
|
|
2445
|
-
return window.IdleDetector;
|
|
2446
|
-
}
|
|
2447
|
-
class IdleDetectorService extends BrowserApiBaseService {
|
|
2448
|
-
getApiName() {
|
|
2449
|
-
return 'idle-detector';
|
|
2450
|
-
}
|
|
2451
|
-
isSupported() {
|
|
2452
|
-
return this.isBrowserEnvironment() && 'IdleDetector' in window;
|
|
2453
|
-
}
|
|
2454
|
-
async requestPermission() {
|
|
2455
|
-
if (!this.isSupported()) {
|
|
2456
|
-
throw new Error('IdleDetector API not supported');
|
|
2457
|
-
}
|
|
2458
|
-
return getIdleDetectorClass$1().requestPermission();
|
|
2459
|
-
}
|
|
2460
|
-
watch(options = {}) {
|
|
2461
|
-
if (!this.isSupported()) {
|
|
2462
|
-
return new Observable((o) => o.error(new Error('IdleDetector API not supported')));
|
|
2463
|
-
}
|
|
2464
|
-
return new Observable((subscriber) => {
|
|
2465
|
-
const abortController = new AbortController();
|
|
2466
|
-
const detector = new (getIdleDetectorClass$1())();
|
|
2467
|
-
detector.addEventListener('change', () => {
|
|
2468
|
-
subscriber.next({
|
|
2469
|
-
user: detector.userState,
|
|
2470
|
-
screen: detector.screenState,
|
|
2471
|
-
});
|
|
2472
|
-
});
|
|
2473
|
-
detector
|
|
2474
|
-
.start({
|
|
2475
|
-
threshold: options.threshold ?? 60_000,
|
|
2476
|
-
signal: abortController.signal,
|
|
2477
|
-
})
|
|
2478
|
-
.catch((err) => subscriber.error(err));
|
|
2479
|
-
return () => abortController.abort();
|
|
2480
|
-
});
|
|
2481
|
-
}
|
|
2482
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2483
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService });
|
|
2484
|
-
}
|
|
2485
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService, decorators: [{
|
|
2486
|
-
type: Injectable
|
|
2487
|
-
}] });
|
|
2488
|
-
|
|
2489
|
-
function getEyeDropperClass() {
|
|
2490
|
-
return window.EyeDropper;
|
|
2491
|
-
}
|
|
2492
|
-
class EyeDropperService extends BrowserApiBaseService {
|
|
2493
|
-
getApiName() {
|
|
2494
|
-
return 'eye-dropper';
|
|
2495
|
-
}
|
|
2496
|
-
isSupported() {
|
|
2497
|
-
return this.isBrowserEnvironment() && 'EyeDropper' in window;
|
|
2498
|
-
}
|
|
2499
|
-
async open(signal) {
|
|
2500
|
-
if (!this.isSupported()) {
|
|
2501
|
-
throw new Error('EyeDropper API not supported');
|
|
2502
|
-
}
|
|
2503
|
-
const dropper = new (getEyeDropperClass())();
|
|
2504
|
-
return dropper.open(signal ? { signal } : undefined);
|
|
2505
|
-
}
|
|
2506
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2507
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService });
|
|
2508
|
-
}
|
|
2509
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService, decorators: [{
|
|
2510
|
-
type: Injectable
|
|
2511
|
-
}] });
|
|
2512
|
-
|
|
2513
|
-
function getBarcodeDetectorClass() {
|
|
2514
|
-
return window.BarcodeDetector;
|
|
2515
|
-
}
|
|
2516
|
-
class BarcodeDetectorService extends BrowserApiBaseService {
|
|
2517
|
-
getApiName() {
|
|
2518
|
-
return 'barcode-detector';
|
|
2519
|
-
}
|
|
2520
|
-
isSupported() {
|
|
2521
|
-
return this.isBrowserEnvironment() && 'BarcodeDetector' in window;
|
|
2522
|
-
}
|
|
2523
|
-
async getSupportedFormats() {
|
|
2524
|
-
if (!this.isSupported())
|
|
2525
|
-
return [];
|
|
2526
|
-
return getBarcodeDetectorClass().getSupportedFormats();
|
|
2527
|
-
}
|
|
2528
|
-
async detect(image, formats) {
|
|
2529
|
-
if (!this.isSupported()) {
|
|
2530
|
-
throw new Error('BarcodeDetector API not supported');
|
|
2531
|
-
}
|
|
2532
|
-
const detector = formats
|
|
2533
|
-
? new (getBarcodeDetectorClass())({ formats })
|
|
2534
|
-
: new (getBarcodeDetectorClass())();
|
|
2535
|
-
return detector.detect(image);
|
|
2536
|
-
}
|
|
2537
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BarcodeDetectorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2538
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BarcodeDetectorService });
|
|
2539
|
-
}
|
|
2540
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BarcodeDetectorService, decorators: [{
|
|
2541
|
-
type: Injectable
|
|
2542
|
-
}] });
|
|
2543
|
-
|
|
2544
2693
|
class WebAudioService extends BrowserApiBaseService {
|
|
2545
2694
|
getApiName() {
|
|
2546
2695
|
return 'web-audio';
|
|
@@ -2750,301 +2899,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
2750
2899
|
type: Injectable
|
|
2751
2900
|
}] });
|
|
2752
2901
|
|
|
2753
|
-
function getBluetooth() {
|
|
2754
|
-
return navigator.bluetooth;
|
|
2755
|
-
}
|
|
2756
|
-
class WebBluetoothService extends BrowserApiBaseService {
|
|
2757
|
-
getApiName() {
|
|
2758
|
-
return 'web-bluetooth';
|
|
2759
|
-
}
|
|
2760
|
-
isSupported() {
|
|
2761
|
-
return this.isBrowserEnvironment() && !!getBluetooth();
|
|
2762
|
-
}
|
|
2763
|
-
async requestDevice(options = { acceptAllDevices: true }) {
|
|
2764
|
-
if (!this.isSupported()) {
|
|
2765
|
-
throw new Error('Web Bluetooth API not supported');
|
|
2766
|
-
}
|
|
2767
|
-
return getBluetooth().requestDevice(options);
|
|
2768
|
-
}
|
|
2769
|
-
async connect(device) {
|
|
2770
|
-
if (!device.gatt) {
|
|
2771
|
-
throw new Error('GATT server not available on this device');
|
|
2772
|
-
}
|
|
2773
|
-
return device.gatt.connect();
|
|
2774
|
-
}
|
|
2775
|
-
disconnect(device) {
|
|
2776
|
-
device.gatt?.disconnect();
|
|
2777
|
-
}
|
|
2778
|
-
watchDisconnection(device) {
|
|
2779
|
-
return new Observable((subscriber) => {
|
|
2780
|
-
const handler = () => subscriber.next();
|
|
2781
|
-
device.addEventListener('gattserverdisconnected', handler);
|
|
2782
|
-
return () => device.removeEventListener('gattserverdisconnected', handler);
|
|
2783
|
-
});
|
|
2784
|
-
}
|
|
2785
|
-
async readCharacteristic(server, serviceUuid, characteristicUuid) {
|
|
2786
|
-
const service = await server.getPrimaryService(serviceUuid);
|
|
2787
|
-
const characteristic = await service.getCharacteristic(characteristicUuid);
|
|
2788
|
-
return characteristic.readValue();
|
|
2789
|
-
}
|
|
2790
|
-
async writeCharacteristic(server, serviceUuid, characteristicUuid, value) {
|
|
2791
|
-
const service = await server.getPrimaryService(serviceUuid);
|
|
2792
|
-
const characteristic = await service.getCharacteristic(characteristicUuid);
|
|
2793
|
-
await characteristic.writeValue(value);
|
|
2794
|
-
}
|
|
2795
|
-
watchCharacteristic(server, serviceUuid, characteristicUuid) {
|
|
2796
|
-
return new Observable((subscriber) => {
|
|
2797
|
-
let characteristic;
|
|
2798
|
-
server
|
|
2799
|
-
.getPrimaryService(serviceUuid)
|
|
2800
|
-
.then((service) => service.getCharacteristic(characteristicUuid))
|
|
2801
|
-
.then((char) => {
|
|
2802
|
-
characteristic = char;
|
|
2803
|
-
const handler = (event) => {
|
|
2804
|
-
const target = event.target;
|
|
2805
|
-
if (target.value)
|
|
2806
|
-
subscriber.next(target.value);
|
|
2807
|
-
};
|
|
2808
|
-
characteristic.addEventListener('characteristicvaluechanged', handler);
|
|
2809
|
-
return characteristic.startNotifications();
|
|
2810
|
-
})
|
|
2811
|
-
.catch((err) => subscriber.error(err));
|
|
2812
|
-
return () => {
|
|
2813
|
-
characteristic?.stopNotifications().catch(() => { });
|
|
2814
|
-
};
|
|
2815
|
-
});
|
|
2816
|
-
}
|
|
2817
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebBluetoothService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2818
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebBluetoothService });
|
|
2819
|
-
}
|
|
2820
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebBluetoothService, decorators: [{
|
|
2821
|
-
type: Injectable
|
|
2822
|
-
}] });
|
|
2823
|
-
|
|
2824
|
-
function getUsb() {
|
|
2825
|
-
return navigator.usb;
|
|
2826
|
-
}
|
|
2827
|
-
class WebUsbService extends BrowserApiBaseService {
|
|
2828
|
-
getApiName() {
|
|
2829
|
-
return 'web-usb';
|
|
2830
|
-
}
|
|
2831
|
-
isSupported() {
|
|
2832
|
-
return this.isBrowserEnvironment() && !!getUsb();
|
|
2833
|
-
}
|
|
2834
|
-
async requestDevice(filters = []) {
|
|
2835
|
-
if (!this.isSupported()) {
|
|
2836
|
-
throw new Error('WebUSB API not supported');
|
|
2837
|
-
}
|
|
2838
|
-
return getUsb().requestDevice({ filters });
|
|
2839
|
-
}
|
|
2840
|
-
async getDevices() {
|
|
2841
|
-
if (!this.isSupported())
|
|
2842
|
-
return [];
|
|
2843
|
-
return getUsb().getDevices();
|
|
2844
|
-
}
|
|
2845
|
-
async open(device) {
|
|
2846
|
-
await device.open();
|
|
2847
|
-
}
|
|
2848
|
-
async close(device) {
|
|
2849
|
-
await device.close();
|
|
2850
|
-
}
|
|
2851
|
-
async selectConfiguration(device, configurationValue) {
|
|
2852
|
-
await device.selectConfiguration(configurationValue);
|
|
2853
|
-
}
|
|
2854
|
-
async claimInterface(device, interfaceNumber) {
|
|
2855
|
-
await device.claimInterface(interfaceNumber);
|
|
2856
|
-
}
|
|
2857
|
-
async releaseInterface(device, interfaceNumber) {
|
|
2858
|
-
await device.releaseInterface(interfaceNumber);
|
|
2859
|
-
}
|
|
2860
|
-
async transferIn(device, endpointNumber, length) {
|
|
2861
|
-
return device.transferIn(endpointNumber, length);
|
|
2862
|
-
}
|
|
2863
|
-
async transferOut(device, endpointNumber, data) {
|
|
2864
|
-
return device.transferOut(endpointNumber, data);
|
|
2865
|
-
}
|
|
2866
|
-
watchConnection() {
|
|
2867
|
-
if (!this.isSupported()) {
|
|
2868
|
-
return new Observable((o) => o.error(new Error('WebUSB API not supported')));
|
|
2869
|
-
}
|
|
2870
|
-
return new Observable((subscriber) => {
|
|
2871
|
-
const usb = getUsb();
|
|
2872
|
-
const onConnect = (e) => subscriber.next({ device: e.device, type: 'connect' });
|
|
2873
|
-
const onDisconnect = (e) => subscriber.next({ device: e.device, type: 'disconnect' });
|
|
2874
|
-
usb.addEventListener('connect', onConnect);
|
|
2875
|
-
usb.addEventListener('disconnect', onDisconnect);
|
|
2876
|
-
return () => {
|
|
2877
|
-
usb.removeEventListener('connect', onConnect);
|
|
2878
|
-
usb.removeEventListener('disconnect', onDisconnect);
|
|
2879
|
-
};
|
|
2880
|
-
});
|
|
2881
|
-
}
|
|
2882
|
-
getDeviceInfo(device) {
|
|
2883
|
-
return {
|
|
2884
|
-
vendorId: device.vendorId,
|
|
2885
|
-
productId: device.productId,
|
|
2886
|
-
productName: device.productName,
|
|
2887
|
-
manufacturerName: device.manufacturerName,
|
|
2888
|
-
serialNumber: device.serialNumber,
|
|
2889
|
-
opened: device.opened,
|
|
2890
|
-
};
|
|
2891
|
-
}
|
|
2892
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebUsbService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2893
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebUsbService });
|
|
2894
|
-
}
|
|
2895
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebUsbService, decorators: [{
|
|
2896
|
-
type: Injectable
|
|
2897
|
-
}] });
|
|
2898
|
-
|
|
2899
|
-
function getNdefReaderClass() {
|
|
2900
|
-
return window.NDEFReader;
|
|
2901
|
-
}
|
|
2902
|
-
class WebNfcService extends BrowserApiBaseService {
|
|
2903
|
-
getApiName() {
|
|
2904
|
-
return 'web-nfc';
|
|
2905
|
-
}
|
|
2906
|
-
isSupported() {
|
|
2907
|
-
return this.isBrowserEnvironment() && 'NDEFReader' in window;
|
|
2908
|
-
}
|
|
2909
|
-
scan() {
|
|
2910
|
-
if (!this.isSupported()) {
|
|
2911
|
-
return new Observable((o) => o.error(new Error('Web NFC API not supported')));
|
|
2912
|
-
}
|
|
2913
|
-
return new Observable((subscriber) => {
|
|
2914
|
-
const abortController = new AbortController();
|
|
2915
|
-
const reader = new (getNdefReaderClass())();
|
|
2916
|
-
const onReading = (event) => {
|
|
2917
|
-
const e = event;
|
|
2918
|
-
subscriber.next({
|
|
2919
|
-
serialNumber: e.serialNumber,
|
|
2920
|
-
message: e.message,
|
|
2921
|
-
});
|
|
2922
|
-
};
|
|
2923
|
-
const onError = (event) => {
|
|
2924
|
-
subscriber.error(event.error ?? new Error('NFC read error'));
|
|
2925
|
-
};
|
|
2926
|
-
reader.addEventListener('reading', onReading);
|
|
2927
|
-
reader.addEventListener('readingerror', onError);
|
|
2928
|
-
reader
|
|
2929
|
-
.scan({ signal: abortController.signal })
|
|
2930
|
-
.catch((err) => subscriber.error(err));
|
|
2931
|
-
return () => abortController.abort();
|
|
2932
|
-
});
|
|
2933
|
-
}
|
|
2934
|
-
async write(message, options) {
|
|
2935
|
-
if (!this.isSupported()) {
|
|
2936
|
-
throw new Error('Web NFC API not supported');
|
|
2937
|
-
}
|
|
2938
|
-
const reader = new (getNdefReaderClass())();
|
|
2939
|
-
await reader.write(message, options);
|
|
2940
|
-
}
|
|
2941
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebNfcService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2942
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebNfcService });
|
|
2943
|
-
}
|
|
2944
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebNfcService, decorators: [{
|
|
2945
|
-
type: Injectable
|
|
2946
|
-
}] });
|
|
2947
|
-
|
|
2948
|
-
class PaymentRequestService extends BrowserApiBaseService {
|
|
2949
|
-
getApiName() {
|
|
2950
|
-
return 'payment-request';
|
|
2951
|
-
}
|
|
2952
|
-
isSupported() {
|
|
2953
|
-
return this.isBrowserEnvironment() && 'PaymentRequest' in window;
|
|
2954
|
-
}
|
|
2955
|
-
async canMakePayment(methods, details) {
|
|
2956
|
-
if (!this.isSupported())
|
|
2957
|
-
return false;
|
|
2958
|
-
const request = new PaymentRequest(methods, details);
|
|
2959
|
-
return request.canMakePayment();
|
|
2960
|
-
}
|
|
2961
|
-
async show(methods, details, options) {
|
|
2962
|
-
if (!this.isSupported()) {
|
|
2963
|
-
throw new Error('Payment Request API not supported');
|
|
2964
|
-
}
|
|
2965
|
-
const request = new PaymentRequest(methods, details, options);
|
|
2966
|
-
const response = await request.show();
|
|
2967
|
-
const result = {
|
|
2968
|
-
methodName: response.methodName,
|
|
2969
|
-
details: response.details,
|
|
2970
|
-
payerName: response.payerName ?? null,
|
|
2971
|
-
payerEmail: response.payerEmail ?? null,
|
|
2972
|
-
payerPhone: response.payerPhone ?? null,
|
|
2973
|
-
};
|
|
2974
|
-
await response.complete('success');
|
|
2975
|
-
return result;
|
|
2976
|
-
}
|
|
2977
|
-
async abort(methods, details) {
|
|
2978
|
-
if (!this.isSupported())
|
|
2979
|
-
return;
|
|
2980
|
-
const request = new PaymentRequest(methods, details);
|
|
2981
|
-
await request.abort();
|
|
2982
|
-
}
|
|
2983
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PaymentRequestService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2984
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PaymentRequestService });
|
|
2985
|
-
}
|
|
2986
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PaymentRequestService, decorators: [{
|
|
2987
|
-
type: Injectable
|
|
2988
|
-
}] });
|
|
2989
|
-
|
|
2990
|
-
class CredentialManagementService extends BrowserApiBaseService {
|
|
2991
|
-
getApiName() {
|
|
2992
|
-
return 'credential-management';
|
|
2993
|
-
}
|
|
2994
|
-
isSupported() {
|
|
2995
|
-
return this.isBrowserEnvironment() && 'credentials' in navigator;
|
|
2996
|
-
}
|
|
2997
|
-
isPublicKeySupported() {
|
|
2998
|
-
return this.isSupported() && 'PublicKeyCredential' in window;
|
|
2999
|
-
}
|
|
3000
|
-
async get(options) {
|
|
3001
|
-
if (!this.isSupported()) {
|
|
3002
|
-
throw new Error('Credential Management API not supported');
|
|
3003
|
-
}
|
|
3004
|
-
return navigator.credentials.get(options);
|
|
3005
|
-
}
|
|
3006
|
-
async store(credential) {
|
|
3007
|
-
if (!this.isSupported()) {
|
|
3008
|
-
throw new Error('Credential Management API not supported');
|
|
3009
|
-
}
|
|
3010
|
-
await navigator.credentials.store(credential);
|
|
3011
|
-
}
|
|
3012
|
-
async createPasswordCredential(data) {
|
|
3013
|
-
if (!this.isSupported()) {
|
|
3014
|
-
throw new Error('Credential Management API not supported');
|
|
3015
|
-
}
|
|
3016
|
-
return navigator.credentials.create({
|
|
3017
|
-
password: data,
|
|
3018
|
-
});
|
|
3019
|
-
}
|
|
3020
|
-
async createPublicKeyCredential(options) {
|
|
3021
|
-
if (!this.isPublicKeySupported()) {
|
|
3022
|
-
throw new Error('PublicKeyCredential API not supported');
|
|
3023
|
-
}
|
|
3024
|
-
return navigator.credentials.create({
|
|
3025
|
-
publicKey: options,
|
|
3026
|
-
});
|
|
3027
|
-
}
|
|
3028
|
-
async preventSilentAccess() {
|
|
3029
|
-
if (!this.isSupported())
|
|
3030
|
-
return;
|
|
3031
|
-
await navigator.credentials.preventSilentAccess();
|
|
3032
|
-
}
|
|
3033
|
-
async isConditionalMediationAvailable() {
|
|
3034
|
-
if (!this.isPublicKeySupported())
|
|
3035
|
-
return false;
|
|
3036
|
-
if ('isConditionalMediationAvailable' in PublicKeyCredential) {
|
|
3037
|
-
return PublicKeyCredential.isConditionalMediationAvailable();
|
|
3038
|
-
}
|
|
3039
|
-
return false;
|
|
3040
|
-
}
|
|
3041
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CredentialManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
3042
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CredentialManagementService });
|
|
3043
|
-
}
|
|
3044
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CredentialManagementService, decorators: [{
|
|
3045
|
-
type: Injectable
|
|
3046
|
-
}] });
|
|
3047
|
-
|
|
3048
2902
|
// Common types for browser APIs
|
|
3049
2903
|
|
|
3050
2904
|
function injectPageVisibility() {
|
|
@@ -3208,42 +3062,6 @@ function injectPerformanceObserver(config) {
|
|
|
3208
3062
|
};
|
|
3209
3063
|
}
|
|
3210
3064
|
|
|
3211
|
-
function getIdleDetectorClass() {
|
|
3212
|
-
return window.IdleDetector;
|
|
3213
|
-
}
|
|
3214
|
-
function injectIdleDetector(options = {}) {
|
|
3215
|
-
const destroyRef = inject(DestroyRef);
|
|
3216
|
-
const platformId = inject(PLATFORM_ID);
|
|
3217
|
-
const defaultState = { user: 'active', screen: 'unlocked' };
|
|
3218
|
-
const state = signal(defaultState, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
3219
|
-
if (isPlatformBrowser(platformId) && 'IdleDetector' in window) {
|
|
3220
|
-
const abortController = new AbortController();
|
|
3221
|
-
const detector = new (getIdleDetectorClass())();
|
|
3222
|
-
detector.addEventListener('change', () => {
|
|
3223
|
-
state.set({
|
|
3224
|
-
user: detector.userState,
|
|
3225
|
-
screen: detector.screenState,
|
|
3226
|
-
});
|
|
3227
|
-
});
|
|
3228
|
-
detector
|
|
3229
|
-
.start({
|
|
3230
|
-
threshold: options.threshold ?? 60_000,
|
|
3231
|
-
signal: abortController.signal,
|
|
3232
|
-
})
|
|
3233
|
-
.catch(() => {
|
|
3234
|
-
/* permission denied or unsupported — keep defaults */
|
|
3235
|
-
});
|
|
3236
|
-
destroyRef.onDestroy(() => abortController.abort());
|
|
3237
|
-
}
|
|
3238
|
-
return {
|
|
3239
|
-
state: state.asReadonly(),
|
|
3240
|
-
userState: computed(() => state().user),
|
|
3241
|
-
screenState: computed(() => state().screen),
|
|
3242
|
-
isUserIdle: computed(() => state().user === 'idle'),
|
|
3243
|
-
isScreenLocked: computed(() => state().screen === 'locked'),
|
|
3244
|
-
};
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
3065
|
function injectGamepad(index, intervalMs = 16) {
|
|
3248
3066
|
const destroyRef = inject(DestroyRef);
|
|
3249
3067
|
const platformId = inject(PLATFORM_ID);
|
|
@@ -3410,10 +3228,6 @@ function provideMediaRecorder() {
|
|
|
3410
3228
|
return makeEnvironmentProviders([PermissionsService, MediaRecorderService]);
|
|
3411
3229
|
}
|
|
3412
3230
|
|
|
3413
|
-
function provideIdleDetector() {
|
|
3414
|
-
return makeEnvironmentProviders([PermissionsService, IdleDetectorService]);
|
|
3415
|
-
}
|
|
3416
|
-
|
|
3417
3231
|
function provideBattery() {
|
|
3418
3232
|
return makeEnvironmentProviders([BatteryService]);
|
|
3419
3233
|
}
|
|
@@ -3482,14 +3296,6 @@ function providePerformanceObserver() {
|
|
|
3482
3296
|
return makeEnvironmentProviders([PerformanceObserverService]);
|
|
3483
3297
|
}
|
|
3484
3298
|
|
|
3485
|
-
function provideEyeDropper() {
|
|
3486
|
-
return makeEnvironmentProviders([EyeDropperService]);
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
|
-
function provideBarcodeDetector() {
|
|
3490
|
-
return makeEnvironmentProviders([BarcodeDetectorService]);
|
|
3491
|
-
}
|
|
3492
|
-
|
|
3493
3299
|
function provideWebAudio() {
|
|
3494
3300
|
return makeEnvironmentProviders([WebAudioService]);
|
|
3495
3301
|
}
|
|
@@ -3498,26 +3304,6 @@ function provideGamepad() {
|
|
|
3498
3304
|
return makeEnvironmentProviders([GamepadService]);
|
|
3499
3305
|
}
|
|
3500
3306
|
|
|
3501
|
-
function provideWebBluetooth() {
|
|
3502
|
-
return makeEnvironmentProviders([WebBluetoothService]);
|
|
3503
|
-
}
|
|
3504
|
-
|
|
3505
|
-
function provideWebUsb() {
|
|
3506
|
-
return makeEnvironmentProviders([WebUsbService]);
|
|
3507
|
-
}
|
|
3508
|
-
|
|
3509
|
-
function provideWebNfc() {
|
|
3510
|
-
return makeEnvironmentProviders([WebNfcService]);
|
|
3511
|
-
}
|
|
3512
|
-
|
|
3513
|
-
function providePaymentRequest() {
|
|
3514
|
-
return makeEnvironmentProviders([PaymentRequestService]);
|
|
3515
|
-
}
|
|
3516
|
-
|
|
3517
|
-
function provideCredentialManagement() {
|
|
3518
|
-
return makeEnvironmentProviders([CredentialManagementService]);
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
3307
|
function provideMediaApis() {
|
|
3522
3308
|
return makeEnvironmentProviders([PermissionsService, CameraService, MediaDevicesService]);
|
|
3523
3309
|
}
|
|
@@ -3562,16 +3348,8 @@ const defaultBrowserWebApisConfig = {
|
|
|
3562
3348
|
enableSpeechSynthesis: false,
|
|
3563
3349
|
enableMutationObserver: false,
|
|
3564
3350
|
enablePerformanceObserver: false,
|
|
3565
|
-
enableIdleDetector: false,
|
|
3566
|
-
enableEyeDropper: false,
|
|
3567
|
-
enableBarcodeDetector: false,
|
|
3568
3351
|
enableWebAudio: false,
|
|
3569
3352
|
enableGamepad: false,
|
|
3570
|
-
enableWebBluetooth: false,
|
|
3571
|
-
enableWebUsb: false,
|
|
3572
|
-
enableWebNfc: false,
|
|
3573
|
-
enablePaymentRequest: false,
|
|
3574
|
-
enableCredentialManagement: false,
|
|
3575
3353
|
};
|
|
3576
3354
|
function provideBrowserWebApis(config = {}) {
|
|
3577
3355
|
const mergedConfig = { ...defaultBrowserWebApisConfig, ...config };
|
|
@@ -3602,16 +3380,8 @@ function provideBrowserWebApis(config = {}) {
|
|
|
3602
3380
|
[mergedConfig.enableSpeechSynthesis, SpeechSynthesisService],
|
|
3603
3381
|
[mergedConfig.enableMutationObserver, MutationObserverService],
|
|
3604
3382
|
[mergedConfig.enablePerformanceObserver, PerformanceObserverService],
|
|
3605
|
-
[mergedConfig.enableIdleDetector, IdleDetectorService],
|
|
3606
|
-
[mergedConfig.enableEyeDropper, EyeDropperService],
|
|
3607
|
-
[mergedConfig.enableBarcodeDetector, BarcodeDetectorService],
|
|
3608
3383
|
[mergedConfig.enableWebAudio, WebAudioService],
|
|
3609
3384
|
[mergedConfig.enableGamepad, GamepadService],
|
|
3610
|
-
[mergedConfig.enableWebBluetooth, WebBluetoothService],
|
|
3611
|
-
[mergedConfig.enableWebUsb, WebUsbService],
|
|
3612
|
-
[mergedConfig.enableWebNfc, WebNfcService],
|
|
3613
|
-
[mergedConfig.enablePaymentRequest, PaymentRequestService],
|
|
3614
|
-
[mergedConfig.enableCredentialManagement, CredentialManagementService],
|
|
3615
3385
|
];
|
|
3616
3386
|
for (const [enabled, provider] of conditionalProviders) {
|
|
3617
3387
|
if (enabled) {
|
|
@@ -3629,4 +3399,4 @@ const version = '0.1.0';
|
|
|
3629
3399
|
* Generated bundle index. Do not edit.
|
|
3630
3400
|
*/
|
|
3631
3401
|
|
|
3632
|
-
export { BROWSER_API_LOGGER,
|
|
3402
|
+
export { BROWSER_API_LOGGER, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, ConnectionRegistryBaseService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, VibrationService, WebAudioService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectGamepad, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideVibration, provideWebAudio, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
|