@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 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 without uploading the original error message, stack, token, or business payload.
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
- The plugin registers SkyWalking during client setup. 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.
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.2.0`.
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://apm.ainvest.com/skywalking-web";
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
- registrar.observe(liveavatarJsSdk.SDK_AVSYNC_ALERTS_V1, {
64
- id: AVSYNC_ALERTS_CONTRIBUTION_ID,
65
- handler: (alert, context) => this._handleAlertNotification(installation, alert, context.signal),
66
- });
67
- registrar.observe(liveavatarJsSdk.SDK_OPERATIONAL_LOGS_V1, {
68
- id: OPERATIONAL_LOGS_CONTRIBUTION_ID,
69
- handler: (record, context) => this._handleOperationalLog(installation, record, context.signal),
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
- return this._runObserverTask(installation, signal, "SDK error log", () => this._reportSdkError(installation, record, signal));
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(installation.diagnostics, alert, retrievedAtWallClockMs, query, isActive, (message, error) => this._warnInvocationFailure(installation, signal, message, error, {
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 = getClientMonitor();
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 fixed by the plugin.
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://apm.ainvest.com/skywalking-web";
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
- registrar.observe(SDK_AVSYNC_ALERTS_V1, {
62
- id: AVSYNC_ALERTS_CONTRIBUTION_ID,
63
- handler: (alert, context) => this._handleAlertNotification(installation, alert, context.signal),
64
- });
65
- registrar.observe(SDK_OPERATIONAL_LOGS_V1, {
66
- id: OPERATIONAL_LOGS_CONTRIBUTION_ID,
67
- handler: (record, context) => this._handleOperationalLog(installation, record, context.signal),
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
- return this._runObserverTask(installation, signal, "SDK error log", () => this._reportSdkError(installation, record, signal));
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(installation.diagnostics, alert, retrievedAtWallClockMs, query, isActive, (message, error) => this._warnInvocationFailure(installation, signal, message, error, {
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 = getClientMonitor();
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://apm.ainvest.com/skywalking-web";
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
- registrar.observe(liveavatarJsSdk.SDK_AVSYNC_ALERTS_V1, {
66
- id: AVSYNC_ALERTS_CONTRIBUTION_ID,
67
- handler: (alert, context) => this._handleAlertNotification(installation, alert, context.signal),
68
- });
69
- registrar.observe(liveavatarJsSdk.SDK_OPERATIONAL_LOGS_V1, {
70
- id: OPERATIONAL_LOGS_CONTRIBUTION_ID,
71
- handler: (record, context) => this._handleOperationalLog(installation, record, context.signal),
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
- return this._runObserverTask(installation, signal, "SDK error log", () => this._reportSdkError(installation, record, signal));
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(installation.diagnostics, alert, retrievedAtWallClockMs, query, isActive, (message, error) => this._warnInvocationFailure(installation, signal, message, error, {
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 = getClientMonitor();
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.0",
4
- "description": "Bundled SkyWalking plugin for @sanseng/liveavatar-js-sdk A/V Sync diagnostics and Operational Log SDK errors",
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.2.0"
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
+ }