@sanseng/liveavatar-js-sdk-skywalking 1.0.0 → 1.0.2
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 +31 -14
- package/dist/index.cjs.js +144 -19
- package/dist/index.d.ts +42 -2
- package/dist/index.esm.js +144 -19
- package/dist/index.umd.js +144 -19
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
## What it solves
|
|
6
6
|
|
|
7
|
-
- Reports LiveAvatar A/V Sync alert lifecycles with compact, causally ordered diagnostic evidence for investigation in SkyWalking.
|
|
8
|
-
- Reports qualifying SDK Operational Log errors
|
|
7
|
+
- Reports SDK-generated LiveAvatar A/V Sync alert lifecycles with compact, causally ordered diagnostic evidence for investigation in SkyWalking.
|
|
8
|
+
- Reports qualifying SDK Operational Log errors with their code and SDK-provided reason for diagnosis.
|
|
9
|
+
- Reports every terminal `performance.first-frame` Operational Log, including success, connection failure, and first-frame timeout.
|
|
9
10
|
- Adds observability through the public LiveAvatar Plugin API v1, without subscribing to legacy SDK events or using SDK internals.
|
|
10
11
|
- Bundles and registers a browser SkyWalking client when one has not already been supplied by the application.
|
|
11
12
|
|
|
@@ -19,24 +20,40 @@ npm install @sanseng/liveavatar-js-sdk @sanseng/liveavatar-js-sdk-skywalking
|
|
|
19
20
|
import { createClient } from "@sanseng/liveavatar-js-sdk";
|
|
20
21
|
import { createSkyWalkingPlugin } from "@sanseng/liveavatar-js-sdk-skywalking";
|
|
21
22
|
|
|
23
|
+
const client = createClient({
|
|
24
|
+
plugins: [createSkyWalkingPlugin()],
|
|
25
|
+
connectConfig: {
|
|
26
|
+
// Your normal LiveAvatar connection configuration.
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The plugin registers SkyWalking during client setup. SDK errors, terminal first-frame logs, and SDK-generated A/V Sync alerts are all reported by default. Its service is always `yy-tx::liveavatar-js-sdk`; it cannot be configured. If `window.ClientMonitor` already exists, the plugin treats it as externally registered and never overwrites or registers it. The bundled client registers only once per page; later differing configurations are ignored with a console warning.
|
|
32
|
+
|
|
33
|
+
Enable SDK A/V Sync diagnostics only when automatic A/V Sync alerts and their evidence are needed. When it is disabled, the SDK produces no automatic A/V Sync alerts, so the plugin does not query A/V telemetry or send A/V logs:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
22
36
|
const client = createClient({
|
|
23
37
|
debug: { avSync: { enabled: true } },
|
|
24
|
-
plugins: [
|
|
25
|
-
createSkyWalkingPlugin({
|
|
26
|
-
isOverseas: false,
|
|
27
|
-
serviceVersion: "web_1.0.0",
|
|
28
|
-
jsErrors: true,
|
|
29
|
-
apiErrors: true,
|
|
30
|
-
resourceErrors: true,
|
|
31
|
-
autoTracePerf: true,
|
|
32
|
-
}),
|
|
33
|
-
],
|
|
38
|
+
plugins: [createSkyWalkingPlugin()],
|
|
34
39
|
connectConfig: {
|
|
35
40
|
// Your normal LiveAvatar connection configuration.
|
|
36
41
|
},
|
|
37
42
|
});
|
|
38
43
|
```
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
## Reporting controls
|
|
46
|
+
|
|
47
|
+
Use `reporting` to disable individual automatic report types; every type defaults to `true`:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
createSkyWalkingPlugin({
|
|
51
|
+
reporting: { sdkError: true, firstFrame: false, avSync: true },
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
When all three report types are `false`, the plugin does not initialize SkyWalking or subscribe to SDK extension points.
|
|
56
|
+
|
|
57
|
+
Each SDK `performance.first-frame` Operational Log produces one SkyWalking structured log through `reportFrameErrors` with a one-millisecond dispatch timeout. “First frame” means the SDK-owned private video element reached `loadeddata`; it does not claim that pixels have already appeared in the application's visible UI. See the [API reference](docs/api.en.md#first-frame-log) for outcomes, payload fields, and omission rules.
|
|
41
58
|
|
|
42
|
-
The package requires `@sanseng/liveavatar-js-sdk` `^2.
|
|
59
|
+
The package requires `@sanseng/liveavatar-js-sdk` `^2.4.0`.
|
package/dist/index.cjs.js
CHANGED
|
@@ -33,7 +33,7 @@ const FRAME_METADATA_MAX_ASSOCIATION_AGE_MS = 150;
|
|
|
33
33
|
const TIME_SYNC_BUCKET_MS = 1000;
|
|
34
34
|
const SKYWALKING_SERVICE = "yy-tx::liveavatar-js-sdk";
|
|
35
35
|
const DOMESTIC_COLLECTOR = "https://apm.hexin.cn/skywalking-web";
|
|
36
|
-
const OVERSEAS_COLLECTOR = "https://
|
|
36
|
+
const OVERSEAS_COLLECTOR = "https://ai.dreamfaceapp.com/skywalking-web";
|
|
37
37
|
const REGISTRATION_STATE_KEY = Symbol.for("liveavatar.skywalking.registration-state");
|
|
38
38
|
/**
|
|
39
39
|
* Reports LiveAvatar A/V-sync incident lifecycle events through the bundled or
|
|
@@ -46,28 +46,34 @@ class SkyWalkingPlugin {
|
|
|
46
46
|
this.apiVersion = 1;
|
|
47
47
|
this._nextInstallationGeneration = 0;
|
|
48
48
|
this._skyWalkingConfig = { ...config };
|
|
49
|
+
this._reporting = normalizeReportingConfig(config.reporting);
|
|
49
50
|
}
|
|
50
51
|
setup(registrar) {
|
|
51
52
|
this._deactivateInstallation();
|
|
53
|
+
if (!hasEnabledReporting(this._reporting))
|
|
54
|
+
return;
|
|
52
55
|
registerSkyWalking(this._skyWalkingConfig);
|
|
53
|
-
const diagnostics = registrar.consume(liveavatarJsSdk.SDK_AVSYNC_DIAGNOSTICS_V1);
|
|
54
56
|
const installation = {
|
|
55
57
|
generation: ++this._nextInstallationGeneration,
|
|
56
|
-
diagnostics,
|
|
57
58
|
abortController: new AbortController(),
|
|
58
59
|
alertReportStates: new Map(),
|
|
59
60
|
active: true,
|
|
60
61
|
};
|
|
61
62
|
this._installation = installation;
|
|
62
63
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
if (this._reporting.avSync) {
|
|
65
|
+
installation.diagnostics = registrar.consume(liveavatarJsSdk.SDK_AVSYNC_DIAGNOSTICS_V1);
|
|
66
|
+
registrar.observe(liveavatarJsSdk.SDK_AVSYNC_ALERTS_V1, {
|
|
67
|
+
id: AVSYNC_ALERTS_CONTRIBUTION_ID,
|
|
68
|
+
handler: (alert, context) => this._handleAlertNotification(installation, alert, context.signal),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (this._reporting.sdkError || this._reporting.firstFrame) {
|
|
72
|
+
registrar.observe(liveavatarJsSdk.SDK_OPERATIONAL_LOGS_V1, {
|
|
73
|
+
id: OPERATIONAL_LOGS_CONTRIBUTION_ID,
|
|
74
|
+
handler: (record, context) => this._handleOperationalLog(installation, record, context.signal),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
78
|
catch (error) {
|
|
73
79
|
this._deactivateInstallation();
|
|
@@ -116,12 +122,19 @@ class SkyWalkingPlugin {
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
_handleOperationalLog(installation, record, signal) {
|
|
119
|
-
if (!this._isInvocationActive(installation, signal)
|
|
120
|
-
record.level !== "error" ||
|
|
121
|
-
record.event !== "sdk.error") {
|
|
125
|
+
if (!this._isInvocationActive(installation, signal))
|
|
122
126
|
return Promise.resolve();
|
|
127
|
+
if (this._reporting.sdkError &&
|
|
128
|
+
record.level === "error" &&
|
|
129
|
+
record.event === "sdk.error") {
|
|
130
|
+
return this._runObserverTask(installation, signal, "SDK error log", () => this._reportSdkError(installation, record, signal));
|
|
123
131
|
}
|
|
124
|
-
|
|
132
|
+
if (this._reporting.firstFrame &&
|
|
133
|
+
record.category === "media" &&
|
|
134
|
+
record.event === "performance.first-frame") {
|
|
135
|
+
return this._runObserverTask(installation, signal, "first-frame log", () => this._reportFirstFrameLog(installation, record, signal));
|
|
136
|
+
}
|
|
137
|
+
return Promise.resolve();
|
|
125
138
|
}
|
|
126
139
|
_enqueue(state, task) {
|
|
127
140
|
const queued = state.tail.then(task, task);
|
|
@@ -132,6 +145,9 @@ class SkyWalkingPlugin {
|
|
|
132
145
|
const isActive = () => this._isInvocationActive(installation, signal);
|
|
133
146
|
if (!isActive())
|
|
134
147
|
return;
|
|
148
|
+
const diagnostics = installation.diagnostics;
|
|
149
|
+
if (diagnostics === undefined)
|
|
150
|
+
return;
|
|
135
151
|
const clientMonitor = getClientMonitor();
|
|
136
152
|
if (clientMonitor === undefined) {
|
|
137
153
|
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
@@ -144,7 +160,7 @@ class SkyWalkingPlugin {
|
|
|
144
160
|
const query = await createEvidenceQuery(alert.phase, state.lastSuccessfulToWallClockMs, [signal, installation.abortController.signal]);
|
|
145
161
|
if (!isActive() || query === undefined)
|
|
146
162
|
return;
|
|
147
|
-
const collection = await collectTelemetry(
|
|
163
|
+
const collection = await collectTelemetry(diagnostics, alert, retrievedAtWallClockMs, query, isActive, (message, error) => this._warnInvocationFailure(installation, signal, message, error, {
|
|
148
164
|
alert: toJsonSafeAlert(alert),
|
|
149
165
|
}));
|
|
150
166
|
if (!isActive() || collection === undefined)
|
|
@@ -227,6 +243,7 @@ class SkyWalkingPlugin {
|
|
|
227
243
|
if (!this._isInvocationActive(installation, signal))
|
|
228
244
|
return;
|
|
229
245
|
const code = stringOr(record.attributes?.code, "SDK_ERROR");
|
|
246
|
+
const reason = record.attributes?.reason;
|
|
230
247
|
const clientMonitor = getClientMonitor();
|
|
231
248
|
if (clientMonitor === undefined) {
|
|
232
249
|
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
@@ -241,6 +258,7 @@ class SkyWalkingPlugin {
|
|
|
241
258
|
sdkVersion: liveavatarJsSdk.VERSION,
|
|
242
259
|
error: stringifyJson({
|
|
243
260
|
code,
|
|
261
|
+
...(typeof reason === "string" ? { reason } : {}),
|
|
244
262
|
eventId: record.eventId,
|
|
245
263
|
sequence: record.sequence,
|
|
246
264
|
occurredAtMs: record.occurredAtMs,
|
|
@@ -272,6 +290,43 @@ class SkyWalkingPlugin {
|
|
|
272
290
|
if (!this._isInvocationActive(installation, signal))
|
|
273
291
|
return;
|
|
274
292
|
}
|
|
293
|
+
async _reportFirstFrameLog(installation, record, signal) {
|
|
294
|
+
if (!this._isInvocationActive(installation, signal))
|
|
295
|
+
return;
|
|
296
|
+
const payload = toSkyWalkingFirstFrameLogPayload(record);
|
|
297
|
+
if (payload === undefined) {
|
|
298
|
+
this._warnInvocation(installation, signal, "Skipped invalid first-frame Operational Log", { eventId: record.eventId });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const clientMonitor = getClientMonitor();
|
|
302
|
+
if (clientMonitor === undefined) {
|
|
303
|
+
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
304
|
+
pluginId: this.id,
|
|
305
|
+
attemptId: payload.attemptId,
|
|
306
|
+
outcome: payload.outcome,
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!this._isInvocationActive(installation, signal))
|
|
311
|
+
return;
|
|
312
|
+
try {
|
|
313
|
+
await Promise.resolve(clientMonitor.reportFrameErrors({
|
|
314
|
+
category: "media",
|
|
315
|
+
grade: payload.outcome === "first-frame" ? "Info" : "Warning",
|
|
316
|
+
content: payload,
|
|
317
|
+
// The bundled client batches error records. Its public timeout
|
|
318
|
+
// option bypasses the default delay for these terminal events.
|
|
319
|
+
timeout: 1,
|
|
320
|
+
}, {
|
|
321
|
+
name: "",
|
|
322
|
+
message: `[LiveAvatar first-frame] ${payload.outcome}`,
|
|
323
|
+
stack: "",
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
this._warnInvocationFailure(installation, signal, "Failed to report first-frame Operational Log to SkyWalking", error, { attemptId: payload.attemptId, outcome: payload.outcome });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
275
330
|
_deactivateInstallation() {
|
|
276
331
|
const installation = this._installation;
|
|
277
332
|
if (installation === undefined)
|
|
@@ -1231,6 +1286,60 @@ function toJsonSafeAlert(alert) {
|
|
|
1231
1286
|
features: { ...alert.features },
|
|
1232
1287
|
};
|
|
1233
1288
|
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Strictly projects the published first-frame Operational Log. The SDK record
|
|
1291
|
+
* has additional identifiers and arbitrary attributes; none of those can
|
|
1292
|
+
* reach SkyWalking through this path.
|
|
1293
|
+
*/
|
|
1294
|
+
function toSkyWalkingFirstFrameLogPayload(record) {
|
|
1295
|
+
const correlation = toRecord(record.correlation);
|
|
1296
|
+
const attributes = toRecord(record.attributes);
|
|
1297
|
+
const attemptId = correlation.attemptId;
|
|
1298
|
+
const outcome = attributes.outcome;
|
|
1299
|
+
if (!isNonNegativeNumber(record.occurredAtMs) ||
|
|
1300
|
+
!isPositiveInteger(attemptId) ||
|
|
1301
|
+
!isFirstFrameOutcome(outcome)) {
|
|
1302
|
+
return undefined;
|
|
1303
|
+
}
|
|
1304
|
+
const sourceNodes = toRecord(attributes.nodes);
|
|
1305
|
+
const nodes = {};
|
|
1306
|
+
copyNonNegativeNumber(sourceNodes, "configReadyMs", nodes);
|
|
1307
|
+
copyNonNegativeNumber(sourceNodes, "rtcConnectedMs", nodes);
|
|
1308
|
+
copyNonNegativeNumber(sourceNodes, "videoTrackAddedMs", nodes);
|
|
1309
|
+
copyNonNegativeNumber(sourceNodes, "videoMetadataMs", nodes);
|
|
1310
|
+
copyNonNegativeNumber(sourceNodes, "firstFrameMs", nodes);
|
|
1311
|
+
return {
|
|
1312
|
+
schemaVersion: 1,
|
|
1313
|
+
kind: "liveavatar.performance.first-frame",
|
|
1314
|
+
sdkVersion: liveavatarJsSdk.VERSION,
|
|
1315
|
+
occurredAtMs: record.occurredAtMs,
|
|
1316
|
+
attemptId,
|
|
1317
|
+
outcome,
|
|
1318
|
+
...(typeof attributes.renderMode === "string"
|
|
1319
|
+
? { renderMode: attributes.renderMode }
|
|
1320
|
+
: {}),
|
|
1321
|
+
...(isNonNegativeNumber(attributes.timeoutAfterRtcMs)
|
|
1322
|
+
? { timeoutAfterRtcMs: attributes.timeoutAfterRtcMs }
|
|
1323
|
+
: {}),
|
|
1324
|
+
nodes,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
function copyNonNegativeNumber(source, key, target) {
|
|
1328
|
+
const value = source[key];
|
|
1329
|
+
if (isNonNegativeNumber(value))
|
|
1330
|
+
target[key] = value;
|
|
1331
|
+
}
|
|
1332
|
+
function isFirstFrameOutcome(value) {
|
|
1333
|
+
return (value === "first-frame" ||
|
|
1334
|
+
value === "connect-failed" ||
|
|
1335
|
+
value === "first-frame-timeout");
|
|
1336
|
+
}
|
|
1337
|
+
function isPositiveInteger(value) {
|
|
1338
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1339
|
+
}
|
|
1340
|
+
function isNonNegativeNumber(value) {
|
|
1341
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1342
|
+
}
|
|
1234
1343
|
function stringifyJson(value) {
|
|
1235
1344
|
const serialized = JSON.stringify(value);
|
|
1236
1345
|
if (serialized === undefined) {
|
|
@@ -1279,9 +1388,8 @@ function registerSkyWalking(config = {}) {
|
|
|
1279
1388
|
}
|
|
1280
1389
|
/** Triggers a SkyWalking performance report through an already registered client. */
|
|
1281
1390
|
function setSkyWalkingPerformance(config = {}, customMetrics) {
|
|
1282
|
-
const clientMonitor =
|
|
1283
|
-
if (clientMonitor === undefined
|
|
1284
|
-
typeof clientMonitor.setPerformance !== "function") {
|
|
1391
|
+
const clientMonitor = getPerformanceClientMonitor();
|
|
1392
|
+
if (clientMonitor === undefined) {
|
|
1285
1393
|
warnOnce("set-performance-unavailable", "[LiveAvatar SkyWalking plugin] ClientMonitor.setPerformance is unavailable; register SkyWalking first");
|
|
1286
1394
|
return;
|
|
1287
1395
|
}
|
|
@@ -1306,6 +1414,16 @@ async function reportSkyWalkingFrameErrors(config, error) {
|
|
|
1306
1414
|
}
|
|
1307
1415
|
await Promise.resolve(clientMonitor.reportFrameErrors(config, error));
|
|
1308
1416
|
}
|
|
1417
|
+
function normalizeReportingConfig(config) {
|
|
1418
|
+
return {
|
|
1419
|
+
sdkError: config?.sdkError ?? true,
|
|
1420
|
+
firstFrame: config?.firstFrame ?? true,
|
|
1421
|
+
avSync: config?.avSync ?? true,
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
function hasEnabledReporting(config) {
|
|
1425
|
+
return config.sdkError || config.firstFrame || config.avSync;
|
|
1426
|
+
}
|
|
1309
1427
|
function normalizeSkyWalkingConfig(config) {
|
|
1310
1428
|
return {
|
|
1311
1429
|
collector: config.collector ??
|
|
@@ -1353,6 +1471,13 @@ function getClientMonitor() {
|
|
|
1353
1471
|
? candidate
|
|
1354
1472
|
: undefined;
|
|
1355
1473
|
}
|
|
1474
|
+
/** Finds the performance API without requiring the unrelated error API. */
|
|
1475
|
+
function getPerformanceClientMonitor() {
|
|
1476
|
+
const candidate = getGlobalClientMonitor();
|
|
1477
|
+
return typeof candidate?.setPerformance === "function"
|
|
1478
|
+
? candidate
|
|
1479
|
+
: undefined;
|
|
1480
|
+
}
|
|
1356
1481
|
function getGlobalClientMonitor() {
|
|
1357
1482
|
if (typeof window === "undefined")
|
|
1358
1483
|
return undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,11 +14,23 @@ interface SkyWalkingSlowInteractionConfig {
|
|
|
14
14
|
threshold?: number;
|
|
15
15
|
reportInterval?: number;
|
|
16
16
|
}
|
|
17
|
+
/** Selects which SDK-generated records the plugin sends to SkyWalking. */
|
|
18
|
+
interface SkyWalkingReportingConfig {
|
|
19
|
+
/** Report SDK Operational Log errors. Defaults to `true`. */
|
|
20
|
+
sdkError?: boolean;
|
|
21
|
+
/** Report terminal SDK first-frame Operational Logs. Defaults to `true`. */
|
|
22
|
+
firstFrame?: boolean;
|
|
23
|
+
/** Report SDK-generated A/V Sync alert lifecycles and evidence. Defaults to `true`. */
|
|
24
|
+
avSync?: boolean;
|
|
25
|
+
}
|
|
17
26
|
/**
|
|
18
27
|
* Registration options accepted by SkyWalking js-sdk 1.1.27, plus the
|
|
19
|
-
* plugin-level overseas switch. `service` is deliberately
|
|
28
|
+
* plugin-level reporting controls and overseas switch. `service` is deliberately
|
|
29
|
+
* fixed by the plugin.
|
|
20
30
|
*/
|
|
21
31
|
interface SkyWalkingPluginConfig {
|
|
32
|
+
/** Select automatic SDK log types. Every type is enabled by default. */
|
|
33
|
+
reporting?: SkyWalkingReportingConfig;
|
|
22
34
|
/** Explicit collector URL. It takes precedence over `isOverseas`. */
|
|
23
35
|
collector?: string;
|
|
24
36
|
/** `false` selects the domestic collector; `true` selects the overseas one. */
|
|
@@ -216,6 +228,7 @@ interface SkyWalkingSdkErrorPayload {
|
|
|
216
228
|
schemaVersion: 2;
|
|
217
229
|
kind: "liveavatar.sdk.error";
|
|
218
230
|
sdkVersion: string;
|
|
231
|
+
/** JSON-encoded error record, including the SDK's optional string `reason`. */
|
|
219
232
|
error: string;
|
|
220
233
|
}
|
|
221
234
|
interface SkyWalkingFrameError {
|
|
@@ -241,6 +254,31 @@ interface SkyWalkingFrameErrorReportConfig {
|
|
|
241
254
|
maxReqCostTime?: number;
|
|
242
255
|
timeout?: number;
|
|
243
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Custom performance fields sent for one terminal connect-to-first-frame
|
|
259
|
+
* attempt. Stage fields are present only when the SDK observed that stage.
|
|
260
|
+
*/
|
|
261
|
+
type SkyWalkingFirstFrameOutcome = "first-frame" | "connect-failed" | "first-frame-timeout";
|
|
262
|
+
/** Allowed first-frame timing nodes copied from the SDK Operational Log. */
|
|
263
|
+
interface SkyWalkingFirstFrameLogNodes {
|
|
264
|
+
configReadyMs?: number;
|
|
265
|
+
rtcConnectedMs?: number;
|
|
266
|
+
videoTrackAddedMs?: number;
|
|
267
|
+
videoMetadataMs?: number;
|
|
268
|
+
firstFrameMs?: number;
|
|
269
|
+
}
|
|
270
|
+
/** Structured content of one terminal SDK `performance.first-frame` log. */
|
|
271
|
+
interface SkyWalkingFirstFrameLogPayload {
|
|
272
|
+
schemaVersion: 1;
|
|
273
|
+
kind: "liveavatar.performance.first-frame";
|
|
274
|
+
sdkVersion: string;
|
|
275
|
+
occurredAtMs: number;
|
|
276
|
+
attemptId: number;
|
|
277
|
+
outcome: SkyWalkingFirstFrameOutcome;
|
|
278
|
+
renderMode?: string;
|
|
279
|
+
timeoutAfterRtcMs?: number;
|
|
280
|
+
nodes: SkyWalkingFirstFrameLogNodes;
|
|
281
|
+
}
|
|
244
282
|
/**
|
|
245
283
|
* Reports LiveAvatar A/V-sync incident lifecycle events through the bundled or
|
|
246
284
|
* externally provided SkyWalking client.
|
|
@@ -250,6 +288,7 @@ declare class SkyWalkingPlugin implements SDKPlugin {
|
|
|
250
288
|
/** SDK Plugin API v1 is the registrar-based public plugin contract. */
|
|
251
289
|
readonly apiVersion = 1;
|
|
252
290
|
private readonly _skyWalkingConfig;
|
|
291
|
+
private readonly _reporting;
|
|
253
292
|
private _installation;
|
|
254
293
|
private _nextInstallationGeneration;
|
|
255
294
|
constructor(config?: SkyWalkingPluginConfig);
|
|
@@ -261,6 +300,7 @@ declare class SkyWalkingPlugin implements SDKPlugin {
|
|
|
261
300
|
private _reportActiveAlert;
|
|
262
301
|
private _reportRecovery;
|
|
263
302
|
private _reportSdkError;
|
|
303
|
+
private _reportFirstFrameLog;
|
|
264
304
|
private _deactivateInstallation;
|
|
265
305
|
private _isInvocationActive;
|
|
266
306
|
private _runObserverTask;
|
|
@@ -283,4 +323,4 @@ declare function setSkyWalkingHRT(config?: Partial<SkyWalkingPluginConfig>, cust
|
|
|
283
323
|
declare function reportSkyWalkingFrameErrors(config: SkyWalkingFrameErrorReportConfig, error: SkyWalkingFrameError): Promise<void>;
|
|
284
324
|
|
|
285
325
|
export { SKYWALKING_PLUGIN_ID, SkyWalkingPlugin, createSkyWalkingPlugin, registerSkyWalking, reportSkyWalkingFrameErrors, setSkyWalkingHRT, setSkyWalkingPerformance };
|
|
286
|
-
export type { AvSyncAlertEvidencePayload, AvSyncEvidence, AvSyncEvidenceLogChunkPayload, AvSyncRecoveryPayload, JsonSafeAlert, SkyWalkingFrameError, SkyWalkingFrameErrorReportConfig, SkyWalkingHooks, SkyWalkingPluginConfig, SkyWalkingRegistrationOptions, SkyWalkingSdkErrorPayload, SkyWalkingSlowInteractionConfig };
|
|
326
|
+
export type { AvSyncAlertEvidencePayload, AvSyncEvidence, AvSyncEvidenceLogChunkPayload, AvSyncRecoveryPayload, JsonSafeAlert, SkyWalkingFirstFrameLogNodes, SkyWalkingFirstFrameLogPayload, SkyWalkingFirstFrameOutcome, SkyWalkingFrameError, SkyWalkingFrameErrorReportConfig, SkyWalkingHooks, SkyWalkingPluginConfig, SkyWalkingRegistrationOptions, SkyWalkingReportingConfig, SkyWalkingSdkErrorPayload, SkyWalkingSlowInteractionConfig };
|
package/dist/index.esm.js
CHANGED
|
@@ -31,7 +31,7 @@ const FRAME_METADATA_MAX_ASSOCIATION_AGE_MS = 150;
|
|
|
31
31
|
const TIME_SYNC_BUCKET_MS = 1000;
|
|
32
32
|
const SKYWALKING_SERVICE = "yy-tx::liveavatar-js-sdk";
|
|
33
33
|
const DOMESTIC_COLLECTOR = "https://apm.hexin.cn/skywalking-web";
|
|
34
|
-
const OVERSEAS_COLLECTOR = "https://
|
|
34
|
+
const OVERSEAS_COLLECTOR = "https://ai.dreamfaceapp.com/skywalking-web";
|
|
35
35
|
const REGISTRATION_STATE_KEY = Symbol.for("liveavatar.skywalking.registration-state");
|
|
36
36
|
/**
|
|
37
37
|
* Reports LiveAvatar A/V-sync incident lifecycle events through the bundled or
|
|
@@ -44,28 +44,34 @@ class SkyWalkingPlugin {
|
|
|
44
44
|
this.apiVersion = 1;
|
|
45
45
|
this._nextInstallationGeneration = 0;
|
|
46
46
|
this._skyWalkingConfig = { ...config };
|
|
47
|
+
this._reporting = normalizeReportingConfig(config.reporting);
|
|
47
48
|
}
|
|
48
49
|
setup(registrar) {
|
|
49
50
|
this._deactivateInstallation();
|
|
51
|
+
if (!hasEnabledReporting(this._reporting))
|
|
52
|
+
return;
|
|
50
53
|
registerSkyWalking(this._skyWalkingConfig);
|
|
51
|
-
const diagnostics = registrar.consume(SDK_AVSYNC_DIAGNOSTICS_V1);
|
|
52
54
|
const installation = {
|
|
53
55
|
generation: ++this._nextInstallationGeneration,
|
|
54
|
-
diagnostics,
|
|
55
56
|
abortController: new AbortController(),
|
|
56
57
|
alertReportStates: new Map(),
|
|
57
58
|
active: true,
|
|
58
59
|
};
|
|
59
60
|
this._installation = installation;
|
|
60
61
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
if (this._reporting.avSync) {
|
|
63
|
+
installation.diagnostics = registrar.consume(SDK_AVSYNC_DIAGNOSTICS_V1);
|
|
64
|
+
registrar.observe(SDK_AVSYNC_ALERTS_V1, {
|
|
65
|
+
id: AVSYNC_ALERTS_CONTRIBUTION_ID,
|
|
66
|
+
handler: (alert, context) => this._handleAlertNotification(installation, alert, context.signal),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (this._reporting.sdkError || this._reporting.firstFrame) {
|
|
70
|
+
registrar.observe(SDK_OPERATIONAL_LOGS_V1, {
|
|
71
|
+
id: OPERATIONAL_LOGS_CONTRIBUTION_ID,
|
|
72
|
+
handler: (record, context) => this._handleOperationalLog(installation, record, context.signal),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
69
75
|
}
|
|
70
76
|
catch (error) {
|
|
71
77
|
this._deactivateInstallation();
|
|
@@ -114,12 +120,19 @@ class SkyWalkingPlugin {
|
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
_handleOperationalLog(installation, record, signal) {
|
|
117
|
-
if (!this._isInvocationActive(installation, signal)
|
|
118
|
-
record.level !== "error" ||
|
|
119
|
-
record.event !== "sdk.error") {
|
|
123
|
+
if (!this._isInvocationActive(installation, signal))
|
|
120
124
|
return Promise.resolve();
|
|
125
|
+
if (this._reporting.sdkError &&
|
|
126
|
+
record.level === "error" &&
|
|
127
|
+
record.event === "sdk.error") {
|
|
128
|
+
return this._runObserverTask(installation, signal, "SDK error log", () => this._reportSdkError(installation, record, signal));
|
|
121
129
|
}
|
|
122
|
-
|
|
130
|
+
if (this._reporting.firstFrame &&
|
|
131
|
+
record.category === "media" &&
|
|
132
|
+
record.event === "performance.first-frame") {
|
|
133
|
+
return this._runObserverTask(installation, signal, "first-frame log", () => this._reportFirstFrameLog(installation, record, signal));
|
|
134
|
+
}
|
|
135
|
+
return Promise.resolve();
|
|
123
136
|
}
|
|
124
137
|
_enqueue(state, task) {
|
|
125
138
|
const queued = state.tail.then(task, task);
|
|
@@ -130,6 +143,9 @@ class SkyWalkingPlugin {
|
|
|
130
143
|
const isActive = () => this._isInvocationActive(installation, signal);
|
|
131
144
|
if (!isActive())
|
|
132
145
|
return;
|
|
146
|
+
const diagnostics = installation.diagnostics;
|
|
147
|
+
if (diagnostics === undefined)
|
|
148
|
+
return;
|
|
133
149
|
const clientMonitor = getClientMonitor();
|
|
134
150
|
if (clientMonitor === undefined) {
|
|
135
151
|
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
@@ -142,7 +158,7 @@ class SkyWalkingPlugin {
|
|
|
142
158
|
const query = await createEvidenceQuery(alert.phase, state.lastSuccessfulToWallClockMs, [signal, installation.abortController.signal]);
|
|
143
159
|
if (!isActive() || query === undefined)
|
|
144
160
|
return;
|
|
145
|
-
const collection = await collectTelemetry(
|
|
161
|
+
const collection = await collectTelemetry(diagnostics, alert, retrievedAtWallClockMs, query, isActive, (message, error) => this._warnInvocationFailure(installation, signal, message, error, {
|
|
146
162
|
alert: toJsonSafeAlert(alert),
|
|
147
163
|
}));
|
|
148
164
|
if (!isActive() || collection === undefined)
|
|
@@ -225,6 +241,7 @@ class SkyWalkingPlugin {
|
|
|
225
241
|
if (!this._isInvocationActive(installation, signal))
|
|
226
242
|
return;
|
|
227
243
|
const code = stringOr(record.attributes?.code, "SDK_ERROR");
|
|
244
|
+
const reason = record.attributes?.reason;
|
|
228
245
|
const clientMonitor = getClientMonitor();
|
|
229
246
|
if (clientMonitor === undefined) {
|
|
230
247
|
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
@@ -239,6 +256,7 @@ class SkyWalkingPlugin {
|
|
|
239
256
|
sdkVersion: VERSION,
|
|
240
257
|
error: stringifyJson({
|
|
241
258
|
code,
|
|
259
|
+
...(typeof reason === "string" ? { reason } : {}),
|
|
242
260
|
eventId: record.eventId,
|
|
243
261
|
sequence: record.sequence,
|
|
244
262
|
occurredAtMs: record.occurredAtMs,
|
|
@@ -270,6 +288,43 @@ class SkyWalkingPlugin {
|
|
|
270
288
|
if (!this._isInvocationActive(installation, signal))
|
|
271
289
|
return;
|
|
272
290
|
}
|
|
291
|
+
async _reportFirstFrameLog(installation, record, signal) {
|
|
292
|
+
if (!this._isInvocationActive(installation, signal))
|
|
293
|
+
return;
|
|
294
|
+
const payload = toSkyWalkingFirstFrameLogPayload(record);
|
|
295
|
+
if (payload === undefined) {
|
|
296
|
+
this._warnInvocation(installation, signal, "Skipped invalid first-frame Operational Log", { eventId: record.eventId });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const clientMonitor = getClientMonitor();
|
|
300
|
+
if (clientMonitor === undefined) {
|
|
301
|
+
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
302
|
+
pluginId: this.id,
|
|
303
|
+
attemptId: payload.attemptId,
|
|
304
|
+
outcome: payload.outcome,
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (!this._isInvocationActive(installation, signal))
|
|
309
|
+
return;
|
|
310
|
+
try {
|
|
311
|
+
await Promise.resolve(clientMonitor.reportFrameErrors({
|
|
312
|
+
category: "media",
|
|
313
|
+
grade: payload.outcome === "first-frame" ? "Info" : "Warning",
|
|
314
|
+
content: payload,
|
|
315
|
+
// The bundled client batches error records. Its public timeout
|
|
316
|
+
// option bypasses the default delay for these terminal events.
|
|
317
|
+
timeout: 1,
|
|
318
|
+
}, {
|
|
319
|
+
name: "",
|
|
320
|
+
message: `[LiveAvatar first-frame] ${payload.outcome}`,
|
|
321
|
+
stack: "",
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
this._warnInvocationFailure(installation, signal, "Failed to report first-frame Operational Log to SkyWalking", error, { attemptId: payload.attemptId, outcome: payload.outcome });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
273
328
|
_deactivateInstallation() {
|
|
274
329
|
const installation = this._installation;
|
|
275
330
|
if (installation === undefined)
|
|
@@ -1229,6 +1284,60 @@ function toJsonSafeAlert(alert) {
|
|
|
1229
1284
|
features: { ...alert.features },
|
|
1230
1285
|
};
|
|
1231
1286
|
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Strictly projects the published first-frame Operational Log. The SDK record
|
|
1289
|
+
* has additional identifiers and arbitrary attributes; none of those can
|
|
1290
|
+
* reach SkyWalking through this path.
|
|
1291
|
+
*/
|
|
1292
|
+
function toSkyWalkingFirstFrameLogPayload(record) {
|
|
1293
|
+
const correlation = toRecord(record.correlation);
|
|
1294
|
+
const attributes = toRecord(record.attributes);
|
|
1295
|
+
const attemptId = correlation.attemptId;
|
|
1296
|
+
const outcome = attributes.outcome;
|
|
1297
|
+
if (!isNonNegativeNumber(record.occurredAtMs) ||
|
|
1298
|
+
!isPositiveInteger(attemptId) ||
|
|
1299
|
+
!isFirstFrameOutcome(outcome)) {
|
|
1300
|
+
return undefined;
|
|
1301
|
+
}
|
|
1302
|
+
const sourceNodes = toRecord(attributes.nodes);
|
|
1303
|
+
const nodes = {};
|
|
1304
|
+
copyNonNegativeNumber(sourceNodes, "configReadyMs", nodes);
|
|
1305
|
+
copyNonNegativeNumber(sourceNodes, "rtcConnectedMs", nodes);
|
|
1306
|
+
copyNonNegativeNumber(sourceNodes, "videoTrackAddedMs", nodes);
|
|
1307
|
+
copyNonNegativeNumber(sourceNodes, "videoMetadataMs", nodes);
|
|
1308
|
+
copyNonNegativeNumber(sourceNodes, "firstFrameMs", nodes);
|
|
1309
|
+
return {
|
|
1310
|
+
schemaVersion: 1,
|
|
1311
|
+
kind: "liveavatar.performance.first-frame",
|
|
1312
|
+
sdkVersion: VERSION,
|
|
1313
|
+
occurredAtMs: record.occurredAtMs,
|
|
1314
|
+
attemptId,
|
|
1315
|
+
outcome,
|
|
1316
|
+
...(typeof attributes.renderMode === "string"
|
|
1317
|
+
? { renderMode: attributes.renderMode }
|
|
1318
|
+
: {}),
|
|
1319
|
+
...(isNonNegativeNumber(attributes.timeoutAfterRtcMs)
|
|
1320
|
+
? { timeoutAfterRtcMs: attributes.timeoutAfterRtcMs }
|
|
1321
|
+
: {}),
|
|
1322
|
+
nodes,
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function copyNonNegativeNumber(source, key, target) {
|
|
1326
|
+
const value = source[key];
|
|
1327
|
+
if (isNonNegativeNumber(value))
|
|
1328
|
+
target[key] = value;
|
|
1329
|
+
}
|
|
1330
|
+
function isFirstFrameOutcome(value) {
|
|
1331
|
+
return (value === "first-frame" ||
|
|
1332
|
+
value === "connect-failed" ||
|
|
1333
|
+
value === "first-frame-timeout");
|
|
1334
|
+
}
|
|
1335
|
+
function isPositiveInteger(value) {
|
|
1336
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1337
|
+
}
|
|
1338
|
+
function isNonNegativeNumber(value) {
|
|
1339
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1340
|
+
}
|
|
1232
1341
|
function stringifyJson(value) {
|
|
1233
1342
|
const serialized = JSON.stringify(value);
|
|
1234
1343
|
if (serialized === undefined) {
|
|
@@ -1277,9 +1386,8 @@ function registerSkyWalking(config = {}) {
|
|
|
1277
1386
|
}
|
|
1278
1387
|
/** Triggers a SkyWalking performance report through an already registered client. */
|
|
1279
1388
|
function setSkyWalkingPerformance(config = {}, customMetrics) {
|
|
1280
|
-
const clientMonitor =
|
|
1281
|
-
if (clientMonitor === undefined
|
|
1282
|
-
typeof clientMonitor.setPerformance !== "function") {
|
|
1389
|
+
const clientMonitor = getPerformanceClientMonitor();
|
|
1390
|
+
if (clientMonitor === undefined) {
|
|
1283
1391
|
warnOnce("set-performance-unavailable", "[LiveAvatar SkyWalking plugin] ClientMonitor.setPerformance is unavailable; register SkyWalking first");
|
|
1284
1392
|
return;
|
|
1285
1393
|
}
|
|
@@ -1304,6 +1412,16 @@ async function reportSkyWalkingFrameErrors(config, error) {
|
|
|
1304
1412
|
}
|
|
1305
1413
|
await Promise.resolve(clientMonitor.reportFrameErrors(config, error));
|
|
1306
1414
|
}
|
|
1415
|
+
function normalizeReportingConfig(config) {
|
|
1416
|
+
return {
|
|
1417
|
+
sdkError: config?.sdkError ?? true,
|
|
1418
|
+
firstFrame: config?.firstFrame ?? true,
|
|
1419
|
+
avSync: config?.avSync ?? true,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function hasEnabledReporting(config) {
|
|
1423
|
+
return config.sdkError || config.firstFrame || config.avSync;
|
|
1424
|
+
}
|
|
1307
1425
|
function normalizeSkyWalkingConfig(config) {
|
|
1308
1426
|
return {
|
|
1309
1427
|
collector: config.collector ??
|
|
@@ -1351,6 +1469,13 @@ function getClientMonitor() {
|
|
|
1351
1469
|
? candidate
|
|
1352
1470
|
: undefined;
|
|
1353
1471
|
}
|
|
1472
|
+
/** Finds the performance API without requiring the unrelated error API. */
|
|
1473
|
+
function getPerformanceClientMonitor() {
|
|
1474
|
+
const candidate = getGlobalClientMonitor();
|
|
1475
|
+
return typeof candidate?.setPerformance === "function"
|
|
1476
|
+
? candidate
|
|
1477
|
+
: undefined;
|
|
1478
|
+
}
|
|
1354
1479
|
function getGlobalClientMonitor() {
|
|
1355
1480
|
if (typeof window === "undefined")
|
|
1356
1481
|
return undefined;
|
package/dist/index.umd.js
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
const TIME_SYNC_BUCKET_MS = 1000;
|
|
36
36
|
const SKYWALKING_SERVICE = "yy-tx::liveavatar-js-sdk";
|
|
37
37
|
const DOMESTIC_COLLECTOR = "https://apm.hexin.cn/skywalking-web";
|
|
38
|
-
const OVERSEAS_COLLECTOR = "https://
|
|
38
|
+
const OVERSEAS_COLLECTOR = "https://ai.dreamfaceapp.com/skywalking-web";
|
|
39
39
|
const REGISTRATION_STATE_KEY = Symbol.for("liveavatar.skywalking.registration-state");
|
|
40
40
|
/**
|
|
41
41
|
* Reports LiveAvatar A/V-sync incident lifecycle events through the bundled or
|
|
@@ -48,28 +48,34 @@
|
|
|
48
48
|
this.apiVersion = 1;
|
|
49
49
|
this._nextInstallationGeneration = 0;
|
|
50
50
|
this._skyWalkingConfig = { ...config };
|
|
51
|
+
this._reporting = normalizeReportingConfig(config.reporting);
|
|
51
52
|
}
|
|
52
53
|
setup(registrar) {
|
|
53
54
|
this._deactivateInstallation();
|
|
55
|
+
if (!hasEnabledReporting(this._reporting))
|
|
56
|
+
return;
|
|
54
57
|
registerSkyWalking(this._skyWalkingConfig);
|
|
55
|
-
const diagnostics = registrar.consume(liveavatarJsSdk.SDK_AVSYNC_DIAGNOSTICS_V1);
|
|
56
58
|
const installation = {
|
|
57
59
|
generation: ++this._nextInstallationGeneration,
|
|
58
|
-
diagnostics,
|
|
59
60
|
abortController: new AbortController(),
|
|
60
61
|
alertReportStates: new Map(),
|
|
61
62
|
active: true,
|
|
62
63
|
};
|
|
63
64
|
this._installation = installation;
|
|
64
65
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
if (this._reporting.avSync) {
|
|
67
|
+
installation.diagnostics = registrar.consume(liveavatarJsSdk.SDK_AVSYNC_DIAGNOSTICS_V1);
|
|
68
|
+
registrar.observe(liveavatarJsSdk.SDK_AVSYNC_ALERTS_V1, {
|
|
69
|
+
id: AVSYNC_ALERTS_CONTRIBUTION_ID,
|
|
70
|
+
handler: (alert, context) => this._handleAlertNotification(installation, alert, context.signal),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (this._reporting.sdkError || this._reporting.firstFrame) {
|
|
74
|
+
registrar.observe(liveavatarJsSdk.SDK_OPERATIONAL_LOGS_V1, {
|
|
75
|
+
id: OPERATIONAL_LOGS_CONTRIBUTION_ID,
|
|
76
|
+
handler: (record, context) => this._handleOperationalLog(installation, record, context.signal),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
catch (error) {
|
|
75
81
|
this._deactivateInstallation();
|
|
@@ -118,12 +124,19 @@
|
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
_handleOperationalLog(installation, record, signal) {
|
|
121
|
-
if (!this._isInvocationActive(installation, signal)
|
|
122
|
-
record.level !== "error" ||
|
|
123
|
-
record.event !== "sdk.error") {
|
|
127
|
+
if (!this._isInvocationActive(installation, signal))
|
|
124
128
|
return Promise.resolve();
|
|
129
|
+
if (this._reporting.sdkError &&
|
|
130
|
+
record.level === "error" &&
|
|
131
|
+
record.event === "sdk.error") {
|
|
132
|
+
return this._runObserverTask(installation, signal, "SDK error log", () => this._reportSdkError(installation, record, signal));
|
|
125
133
|
}
|
|
126
|
-
|
|
134
|
+
if (this._reporting.firstFrame &&
|
|
135
|
+
record.category === "media" &&
|
|
136
|
+
record.event === "performance.first-frame") {
|
|
137
|
+
return this._runObserverTask(installation, signal, "first-frame log", () => this._reportFirstFrameLog(installation, record, signal));
|
|
138
|
+
}
|
|
139
|
+
return Promise.resolve();
|
|
127
140
|
}
|
|
128
141
|
_enqueue(state, task) {
|
|
129
142
|
const queued = state.tail.then(task, task);
|
|
@@ -134,6 +147,9 @@
|
|
|
134
147
|
const isActive = () => this._isInvocationActive(installation, signal);
|
|
135
148
|
if (!isActive())
|
|
136
149
|
return;
|
|
150
|
+
const diagnostics = installation.diagnostics;
|
|
151
|
+
if (diagnostics === undefined)
|
|
152
|
+
return;
|
|
137
153
|
const clientMonitor = getClientMonitor();
|
|
138
154
|
if (clientMonitor === undefined) {
|
|
139
155
|
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
@@ -146,7 +162,7 @@
|
|
|
146
162
|
const query = await createEvidenceQuery(alert.phase, state.lastSuccessfulToWallClockMs, [signal, installation.abortController.signal]);
|
|
147
163
|
if (!isActive() || query === undefined)
|
|
148
164
|
return;
|
|
149
|
-
const collection = await collectTelemetry(
|
|
165
|
+
const collection = await collectTelemetry(diagnostics, alert, retrievedAtWallClockMs, query, isActive, (message, error) => this._warnInvocationFailure(installation, signal, message, error, {
|
|
150
166
|
alert: toJsonSafeAlert(alert),
|
|
151
167
|
}));
|
|
152
168
|
if (!isActive() || collection === undefined)
|
|
@@ -229,6 +245,7 @@
|
|
|
229
245
|
if (!this._isInvocationActive(installation, signal))
|
|
230
246
|
return;
|
|
231
247
|
const code = stringOr(record.attributes?.code, "SDK_ERROR");
|
|
248
|
+
const reason = record.attributes?.reason;
|
|
232
249
|
const clientMonitor = getClientMonitor();
|
|
233
250
|
if (clientMonitor === undefined) {
|
|
234
251
|
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
@@ -243,6 +260,7 @@
|
|
|
243
260
|
sdkVersion: liveavatarJsSdk.VERSION,
|
|
244
261
|
error: stringifyJson({
|
|
245
262
|
code,
|
|
263
|
+
...(typeof reason === "string" ? { reason } : {}),
|
|
246
264
|
eventId: record.eventId,
|
|
247
265
|
sequence: record.sequence,
|
|
248
266
|
occurredAtMs: record.occurredAtMs,
|
|
@@ -274,6 +292,43 @@
|
|
|
274
292
|
if (!this._isInvocationActive(installation, signal))
|
|
275
293
|
return;
|
|
276
294
|
}
|
|
295
|
+
async _reportFirstFrameLog(installation, record, signal) {
|
|
296
|
+
if (!this._isInvocationActive(installation, signal))
|
|
297
|
+
return;
|
|
298
|
+
const payload = toSkyWalkingFirstFrameLogPayload(record);
|
|
299
|
+
if (payload === undefined) {
|
|
300
|
+
this._warnInvocation(installation, signal, "Skipped invalid first-frame Operational Log", { eventId: record.eventId });
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const clientMonitor = getClientMonitor();
|
|
304
|
+
if (clientMonitor === undefined) {
|
|
305
|
+
this._warnInvocation(installation, signal, "ClientMonitor.reportFrameErrors is unavailable", {
|
|
306
|
+
pluginId: this.id,
|
|
307
|
+
attemptId: payload.attemptId,
|
|
308
|
+
outcome: payload.outcome,
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (!this._isInvocationActive(installation, signal))
|
|
313
|
+
return;
|
|
314
|
+
try {
|
|
315
|
+
await Promise.resolve(clientMonitor.reportFrameErrors({
|
|
316
|
+
category: "media",
|
|
317
|
+
grade: payload.outcome === "first-frame" ? "Info" : "Warning",
|
|
318
|
+
content: payload,
|
|
319
|
+
// The bundled client batches error records. Its public timeout
|
|
320
|
+
// option bypasses the default delay for these terminal events.
|
|
321
|
+
timeout: 1,
|
|
322
|
+
}, {
|
|
323
|
+
name: "",
|
|
324
|
+
message: `[LiveAvatar first-frame] ${payload.outcome}`,
|
|
325
|
+
stack: "",
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this._warnInvocationFailure(installation, signal, "Failed to report first-frame Operational Log to SkyWalking", error, { attemptId: payload.attemptId, outcome: payload.outcome });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
277
332
|
_deactivateInstallation() {
|
|
278
333
|
const installation = this._installation;
|
|
279
334
|
if (installation === undefined)
|
|
@@ -1233,6 +1288,60 @@
|
|
|
1233
1288
|
features: { ...alert.features },
|
|
1234
1289
|
};
|
|
1235
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Strictly projects the published first-frame Operational Log. The SDK record
|
|
1293
|
+
* has additional identifiers and arbitrary attributes; none of those can
|
|
1294
|
+
* reach SkyWalking through this path.
|
|
1295
|
+
*/
|
|
1296
|
+
function toSkyWalkingFirstFrameLogPayload(record) {
|
|
1297
|
+
const correlation = toRecord(record.correlation);
|
|
1298
|
+
const attributes = toRecord(record.attributes);
|
|
1299
|
+
const attemptId = correlation.attemptId;
|
|
1300
|
+
const outcome = attributes.outcome;
|
|
1301
|
+
if (!isNonNegativeNumber(record.occurredAtMs) ||
|
|
1302
|
+
!isPositiveInteger(attemptId) ||
|
|
1303
|
+
!isFirstFrameOutcome(outcome)) {
|
|
1304
|
+
return undefined;
|
|
1305
|
+
}
|
|
1306
|
+
const sourceNodes = toRecord(attributes.nodes);
|
|
1307
|
+
const nodes = {};
|
|
1308
|
+
copyNonNegativeNumber(sourceNodes, "configReadyMs", nodes);
|
|
1309
|
+
copyNonNegativeNumber(sourceNodes, "rtcConnectedMs", nodes);
|
|
1310
|
+
copyNonNegativeNumber(sourceNodes, "videoTrackAddedMs", nodes);
|
|
1311
|
+
copyNonNegativeNumber(sourceNodes, "videoMetadataMs", nodes);
|
|
1312
|
+
copyNonNegativeNumber(sourceNodes, "firstFrameMs", nodes);
|
|
1313
|
+
return {
|
|
1314
|
+
schemaVersion: 1,
|
|
1315
|
+
kind: "liveavatar.performance.first-frame",
|
|
1316
|
+
sdkVersion: liveavatarJsSdk.VERSION,
|
|
1317
|
+
occurredAtMs: record.occurredAtMs,
|
|
1318
|
+
attemptId,
|
|
1319
|
+
outcome,
|
|
1320
|
+
...(typeof attributes.renderMode === "string"
|
|
1321
|
+
? { renderMode: attributes.renderMode }
|
|
1322
|
+
: {}),
|
|
1323
|
+
...(isNonNegativeNumber(attributes.timeoutAfterRtcMs)
|
|
1324
|
+
? { timeoutAfterRtcMs: attributes.timeoutAfterRtcMs }
|
|
1325
|
+
: {}),
|
|
1326
|
+
nodes,
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
function copyNonNegativeNumber(source, key, target) {
|
|
1330
|
+
const value = source[key];
|
|
1331
|
+
if (isNonNegativeNumber(value))
|
|
1332
|
+
target[key] = value;
|
|
1333
|
+
}
|
|
1334
|
+
function isFirstFrameOutcome(value) {
|
|
1335
|
+
return (value === "first-frame" ||
|
|
1336
|
+
value === "connect-failed" ||
|
|
1337
|
+
value === "first-frame-timeout");
|
|
1338
|
+
}
|
|
1339
|
+
function isPositiveInteger(value) {
|
|
1340
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1341
|
+
}
|
|
1342
|
+
function isNonNegativeNumber(value) {
|
|
1343
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1344
|
+
}
|
|
1236
1345
|
function stringifyJson(value) {
|
|
1237
1346
|
const serialized = JSON.stringify(value);
|
|
1238
1347
|
if (serialized === undefined) {
|
|
@@ -1281,9 +1390,8 @@
|
|
|
1281
1390
|
}
|
|
1282
1391
|
/** Triggers a SkyWalking performance report through an already registered client. */
|
|
1283
1392
|
function setSkyWalkingPerformance(config = {}, customMetrics) {
|
|
1284
|
-
const clientMonitor =
|
|
1285
|
-
if (clientMonitor === undefined
|
|
1286
|
-
typeof clientMonitor.setPerformance !== "function") {
|
|
1393
|
+
const clientMonitor = getPerformanceClientMonitor();
|
|
1394
|
+
if (clientMonitor === undefined) {
|
|
1287
1395
|
warnOnce("set-performance-unavailable", "[LiveAvatar SkyWalking plugin] ClientMonitor.setPerformance is unavailable; register SkyWalking first");
|
|
1288
1396
|
return;
|
|
1289
1397
|
}
|
|
@@ -1308,6 +1416,16 @@
|
|
|
1308
1416
|
}
|
|
1309
1417
|
await Promise.resolve(clientMonitor.reportFrameErrors(config, error));
|
|
1310
1418
|
}
|
|
1419
|
+
function normalizeReportingConfig(config) {
|
|
1420
|
+
return {
|
|
1421
|
+
sdkError: config?.sdkError ?? true,
|
|
1422
|
+
firstFrame: config?.firstFrame ?? true,
|
|
1423
|
+
avSync: config?.avSync ?? true,
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function hasEnabledReporting(config) {
|
|
1427
|
+
return config.sdkError || config.firstFrame || config.avSync;
|
|
1428
|
+
}
|
|
1311
1429
|
function normalizeSkyWalkingConfig(config) {
|
|
1312
1430
|
return {
|
|
1313
1431
|
collector: config.collector ??
|
|
@@ -1355,6 +1473,13 @@
|
|
|
1355
1473
|
? candidate
|
|
1356
1474
|
: undefined;
|
|
1357
1475
|
}
|
|
1476
|
+
/** Finds the performance API without requiring the unrelated error API. */
|
|
1477
|
+
function getPerformanceClientMonitor() {
|
|
1478
|
+
const candidate = getGlobalClientMonitor();
|
|
1479
|
+
return typeof candidate?.setPerformance === "function"
|
|
1480
|
+
? candidate
|
|
1481
|
+
: undefined;
|
|
1482
|
+
}
|
|
1358
1483
|
function getGlobalClientMonitor() {
|
|
1359
1484
|
if (typeof window === "undefined")
|
|
1360
1485
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanseng/liveavatar-js-sdk-skywalking",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "SkyWalking plugin for LiveAvatar A/V Sync alerts, first-frame performance, and SDK errors with type-selective reporting",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "sanseng",
|
|
7
7
|
"main": "dist/index.cjs.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"observability"
|
|
44
44
|
],
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@sanseng/liveavatar-js-sdk": "^2.
|
|
46
|
+
"@sanseng/liveavatar-js-sdk": "^2.4.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
"rimraf": "^6.1.2",
|
|
54
54
|
"rollup": "^4.55.1",
|
|
55
55
|
"rollup-plugin-dts": "^6.3.0",
|
|
56
|
+
"tslib": "^2.8.1",
|
|
56
57
|
"typescript": "^5.9.3",
|
|
57
58
|
"vite": "^7.3.0",
|
|
58
59
|
"vitest": "^4.1.7"
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}
|