@spatius/avatarkit 1.3.1-beta.7 → 1.3.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/CHANGELOG.md +27 -0
- package/dist/{AvatarDownloader-B1tJTnIb.js → AvatarDownloader-BDKn8AjO.js} +98 -43
- package/dist/{AvatarSDK-CTprrfJk.js → AvatarSDK-DqP8n0ex.js} +162 -2560
- package/dist/{OpusCodec-PWv7ynPp.js → OpusCodec-B0YtpenN.js} +43 -9
- package/dist/{OpusDecoderProxy-m0fVN_M2.js → OpusDecoderProxy-vGagApTz.js} +19 -6
- package/dist/{OpusEncoderProxy-DiW21sBK.js → OpusEncoderProxy-sMAwGYqf.js} +42 -7
- package/dist/{StreamingAudioPlayer-C6hgkvbK.js → StreamingAudioPlayer-DXsCXseQ.js} +2 -2
- package/dist/assets/{AvatarDownloader-LO9uuYvm.js → AvatarDownloader-u-f7nYVa.js} +96 -41
- package/dist/assets/{AvatarSDK-CPEeTeLT.js → AvatarSDK-bSFhbb92.js} +149 -12
- package/dist/assets/{OpusDecoderWorker.worker-CSSWn75M.js → OpusDecoderWorker.worker-Cp7jmav3.js} +21 -5
- package/dist/assets/{OpusEncoderWorker.worker-q4TdCVDZ.js → OpusEncoderWorker.worker-CQct4FWr.js} +53 -11
- package/dist/assets/{logger-DCzHWd4N.js → logger-D7nCOkvy.js} +319 -126
- package/dist/core/AvatarController.d.ts +10 -0
- package/dist/{error-utils-BCZDCrc6.js → error-utils-D7aLxFrw.js} +1 -1
- package/dist/index.js +304 -44
- package/dist/internal-telemetry.d.ts +45 -20
- package/dist/internal-telemetry.js +27 -2
- package/dist/{logger-DdmfSEn-.js → logger-gpv4NaPj.js} +336 -126
- package/dist/otel-trace-M2fi3yoE.js +2611 -0
- package/dist/{pwa-cache-manager-BMKyJWXI.js → pwa-cache-manager-CcFR2ARt.js} +1 -1
- package/dist/types/index.d.ts +30 -4
- package/package.json +1 -1
|
@@ -1156,6 +1156,28 @@ var DrivingServiceMode = /* @__PURE__ */ function(DrivingServiceMode) {
|
|
|
1156
1156
|
DrivingServiceMode["direct"] = "direct";
|
|
1157
1157
|
/** Driven by host application */
|
|
1158
1158
|
DrivingServiceMode["backend"] = "backend";
|
|
1159
|
+
/**
|
|
1160
|
+
* Use this **only** when driving the avatar through the companion RTC SDK,
|
|
1161
|
+
* `@spatius/avatarkit-rtc`. If you are not using that package, stay on
|
|
1162
|
+
* `direct` / `backend` — do not switch to this because it looks newer.
|
|
1163
|
+
*
|
|
1164
|
+
* **Telemetry dimension only — it gates no behaviour.** Every session reports
|
|
1165
|
+
* `dsm`; without a value of its own, RTC traffic would be indistinguishable
|
|
1166
|
+
* from plain `backend` traffic on the dashboards.
|
|
1167
|
+
*
|
|
1168
|
+
* It deliberately does not unlock any API. RTC drives the avatar frame by
|
|
1169
|
+
* frame through `AvatarView.renderFrame` / `renderFromProtobuf`, which put the
|
|
1170
|
+
* view into pure-rendering mode on their own — it never calls the host-driven
|
|
1171
|
+
* feeding path (`yieldAudioData` / `yieldFramesData`) that `backend` exists
|
|
1172
|
+
* for. So the `=== backend` checks guarding those methods are left untouched:
|
|
1173
|
+
* loosening them for `rtc` would widen the reachable API surface without
|
|
1174
|
+
* enabling anything RTC actually uses.
|
|
1175
|
+
*
|
|
1176
|
+
* Note the two concepts sit at different levels and are not interchangeable:
|
|
1177
|
+
* pure-rendering mode is a per-frame runtime state of the render loop, while
|
|
1178
|
+
* this is a session-wide value declared once at `initialize`.
|
|
1179
|
+
*/
|
|
1180
|
+
DrivingServiceMode["rtc"] = "rtc";
|
|
1159
1181
|
return DrivingServiceMode;
|
|
1160
1182
|
}({});
|
|
1161
1183
|
/**
|
|
@@ -7190,7 +7212,7 @@ var customRandom = (alphabet, defaultSize, getRandom) => {
|
|
|
7190
7212
|
};
|
|
7191
7213
|
};
|
|
7192
7214
|
var customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size | 0, random);
|
|
7193
|
-
var nanoid$
|
|
7215
|
+
var nanoid$2 = (size = 21) => {
|
|
7194
7216
|
let id = "";
|
|
7195
7217
|
let bytes = crypto.getRandomValues(new Uint8Array(size |= 0));
|
|
7196
7218
|
while (size--) id += urlAlphabet[bytes[size] & 63];
|
|
@@ -7215,11 +7237,11 @@ function getOrCreateClientId() {
|
|
|
7215
7237
|
try {
|
|
7216
7238
|
const stored = localStorage.getItem(CLIENT_ID_STORAGE_KEY);
|
|
7217
7239
|
if (stored) return stored;
|
|
7218
|
-
const clientId = nanoid$
|
|
7240
|
+
const clientId = nanoid$2(21);
|
|
7219
7241
|
localStorage.setItem(CLIENT_ID_STORAGE_KEY, clientId);
|
|
7220
7242
|
return clientId;
|
|
7221
7243
|
} catch (error) {
|
|
7222
|
-
return `temp_${nanoid$
|
|
7244
|
+
return `temp_${nanoid$2(21)}_${Date.now()}`;
|
|
7223
7245
|
}
|
|
7224
7246
|
}
|
|
7225
7247
|
//#endregion
|
|
@@ -7237,7 +7259,7 @@ function getOrCreateClientId() {
|
|
|
7237
7259
|
* Custom character set: only uppercase, lowercase letters and numbers (URL safe, no hyphens)
|
|
7238
7260
|
* Character set: A-Z, a-z, 0-9 (62 characters)
|
|
7239
7261
|
*/
|
|
7240
|
-
var nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);
|
|
7262
|
+
var nanoid$1 = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);
|
|
7241
7263
|
/**
|
|
7242
7264
|
* Generate unified conversation ID
|
|
7243
7265
|
*
|
|
@@ -7252,7 +7274,7 @@ var nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy
|
|
|
7252
7274
|
var generateTraceId = () => generateConversationId();
|
|
7253
7275
|
function generateConversationId() {
|
|
7254
7276
|
const now = /* @__PURE__ */ new Date();
|
|
7255
|
-
return `${`${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")}${String(now.getUTCHours()).padStart(2, "0")}${String(now.getUTCMinutes()).padStart(2, "0")}${String(now.getUTCSeconds()).padStart(2, "0")}`}_${nanoid()}`;
|
|
7277
|
+
return `${`${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")}${String(now.getUTCHours()).padStart(2, "0")}${String(now.getUTCMinutes()).padStart(2, "0")}${String(now.getUTCSeconds()).padStart(2, "0")}`}_${nanoid$1()}`;
|
|
7256
7278
|
}
|
|
7257
7279
|
//#endregion
|
|
7258
7280
|
//#region utils/id-manager.ts
|
|
@@ -7261,9 +7283,12 @@ function generateConversationId() {
|
|
|
7261
7283
|
* Unified management of all types of IDs in SDK
|
|
7262
7284
|
* @internal
|
|
7263
7285
|
*/
|
|
7286
|
+
/** 与 conversation_id 同字符集:URL 安全、无连字符。 */
|
|
7287
|
+
var nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);
|
|
7264
7288
|
var IdManager = class {
|
|
7265
7289
|
ids = {
|
|
7266
7290
|
clientId: "",
|
|
7291
|
+
sessionId: "",
|
|
7267
7292
|
userId: null,
|
|
7268
7293
|
appId: null,
|
|
7269
7294
|
sessionToken: null,
|
|
@@ -7346,10 +7371,16 @@ var IdManager = class {
|
|
|
7346
7371
|
getLogContext() {
|
|
7347
7372
|
return {
|
|
7348
7373
|
client_id: this.ids.clientId,
|
|
7374
|
+
session_id: this.ids.sessionId,
|
|
7349
7375
|
user_id: this.ids.userId || "",
|
|
7350
7376
|
app_id: this.ids.appId || ""
|
|
7351
7377
|
};
|
|
7352
7378
|
}
|
|
7379
|
+
/** 本次启动的 session id;首次调用时生成。 @internal */
|
|
7380
|
+
getSessionId() {
|
|
7381
|
+
if (!this.ids.sessionId) this.ids.sessionId = nanoid();
|
|
7382
|
+
return this.ids.sessionId;
|
|
7383
|
+
}
|
|
7353
7384
|
/**
|
|
7354
7385
|
* Clear all IDs (for testing or reset)
|
|
7355
7386
|
* @internal
|
|
@@ -13404,119 +13435,6 @@ var OTLPLogExporter = class extends OTLPExporterBase {
|
|
|
13404
13435
|
}
|
|
13405
13436
|
};
|
|
13406
13437
|
//#endregion
|
|
13407
|
-
//#region utils/event-store.ts
|
|
13408
|
-
/**
|
|
13409
|
-
* Event Store - Persistent buffer for OTel telemetry events.
|
|
13410
|
-
*
|
|
13411
|
-
* Each emitted event is written here before being handed to the OTel
|
|
13412
|
-
* BatchProcessor. When the wrapper exporter picks a batch up for HTTP
|
|
13413
|
-
* upload it deletes those records — so anything still here on next
|
|
13414
|
-
* startup is unsent and gets re-emitted (replay).
|
|
13415
|
-
*
|
|
13416
|
-
* Schema is intentionally minimal: no status / retry_count. A record's
|
|
13417
|
-
* presence in the store means "not yet handed to inner.export".
|
|
13418
|
-
* @internal
|
|
13419
|
-
*/
|
|
13420
|
-
var DB_NAME = "avatarkit_events";
|
|
13421
|
-
var STORE_NAME = "events";
|
|
13422
|
-
var DB_VERSION = 1;
|
|
13423
|
-
var EventStore = class {
|
|
13424
|
-
db = null;
|
|
13425
|
-
initPromise = null;
|
|
13426
|
-
initialize() {
|
|
13427
|
-
if (this.db) return Promise.resolve();
|
|
13428
|
-
if (this.initPromise) return this.initPromise;
|
|
13429
|
-
this.initPromise = new Promise((resolve, reject) => {
|
|
13430
|
-
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
13431
|
-
req.onerror = () => reject(/* @__PURE__ */ new Error("Failed to open avatarkit_events"));
|
|
13432
|
-
req.onsuccess = () => {
|
|
13433
|
-
this.db = req.result;
|
|
13434
|
-
resolve();
|
|
13435
|
-
};
|
|
13436
|
-
req.onupgradeneeded = (e) => {
|
|
13437
|
-
const db = e.target.result;
|
|
13438
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, {
|
|
13439
|
-
keyPath: "id",
|
|
13440
|
-
autoIncrement: true
|
|
13441
|
-
});
|
|
13442
|
-
};
|
|
13443
|
-
});
|
|
13444
|
-
return this.initPromise;
|
|
13445
|
-
}
|
|
13446
|
-
/**
|
|
13447
|
-
* Append a record, returning the auto-generated id.
|
|
13448
|
-
* Caller must use this id as the `_index` attribute when emitting to OTel.
|
|
13449
|
-
*/
|
|
13450
|
-
async add(record) {
|
|
13451
|
-
if (!this.db) await this.initialize();
|
|
13452
|
-
if (!this.db) throw new Error("event-store not available");
|
|
13453
|
-
return new Promise((resolve, reject) => {
|
|
13454
|
-
const req = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).add(record);
|
|
13455
|
-
req.onsuccess = () => resolve(req.result);
|
|
13456
|
-
req.onerror = () => reject(/* @__PURE__ */ new Error("event-store add failed"));
|
|
13457
|
-
});
|
|
13458
|
-
}
|
|
13459
|
-
/**
|
|
13460
|
-
* Atomically: for each id, check whether it still exists in the store;
|
|
13461
|
-
* if so, delete it and include it in the returned set. Used by the
|
|
13462
|
-
* wrapper exporter to claim ownership of a batch — only records the
|
|
13463
|
-
* caller successfully "took" should be forwarded to inner.export.
|
|
13464
|
-
*
|
|
13465
|
-
* This is the multi-tab dedup mechanism: when Tab B starts and replays
|
|
13466
|
-
* records that Tab A also still has in memory, only one tab will win
|
|
13467
|
-
* the take and actually send.
|
|
13468
|
-
*
|
|
13469
|
-
* The get + delete pair runs inside a single readwrite transaction so
|
|
13470
|
-
* concurrent tabs see consistent state.
|
|
13471
|
-
*/
|
|
13472
|
-
async takeIfExists(ids) {
|
|
13473
|
-
const taken = /* @__PURE__ */ new Set();
|
|
13474
|
-
if (ids.length === 0) return taken;
|
|
13475
|
-
if (!this.db) await this.initialize();
|
|
13476
|
-
if (!this.db) return taken;
|
|
13477
|
-
return new Promise((resolve) => {
|
|
13478
|
-
const tx = this.db.transaction([STORE_NAME], "readwrite");
|
|
13479
|
-
const store = tx.objectStore(STORE_NAME);
|
|
13480
|
-
let pending = ids.length;
|
|
13481
|
-
const finish = () => {
|
|
13482
|
-
if (--pending === 0) resolve(taken);
|
|
13483
|
-
};
|
|
13484
|
-
for (const id of ids) {
|
|
13485
|
-
const req = store.get(id);
|
|
13486
|
-
req.onsuccess = () => {
|
|
13487
|
-
if (req.result !== void 0) {
|
|
13488
|
-
taken.add(id);
|
|
13489
|
-
store.delete(id);
|
|
13490
|
-
}
|
|
13491
|
-
finish();
|
|
13492
|
-
};
|
|
13493
|
-
req.onerror = () => finish();
|
|
13494
|
-
}
|
|
13495
|
-
tx.onerror = () => {
|
|
13496
|
-
logger.warn("[event-store] takeIfExists tx failed");
|
|
13497
|
-
resolve(taken);
|
|
13498
|
-
};
|
|
13499
|
-
});
|
|
13500
|
-
}
|
|
13501
|
-
/**
|
|
13502
|
-
* Return all stored records. Used on startup to replay anything left
|
|
13503
|
-
* over from a previous (possibly crashed) session.
|
|
13504
|
-
*/
|
|
13505
|
-
async getAll() {
|
|
13506
|
-
if (!this.db) await this.initialize();
|
|
13507
|
-
if (!this.db) return [];
|
|
13508
|
-
return new Promise((resolve) => {
|
|
13509
|
-
const req = this.db.transaction([STORE_NAME], "readonly").objectStore(STORE_NAME).getAll();
|
|
13510
|
-
req.onsuccess = () => resolve(req.result || []);
|
|
13511
|
-
req.onerror = () => {
|
|
13512
|
-
logger.warn("[event-store] getAll failed");
|
|
13513
|
-
resolve([]);
|
|
13514
|
-
};
|
|
13515
|
-
});
|
|
13516
|
-
}
|
|
13517
|
-
};
|
|
13518
|
-
var eventStore = new EventStore();
|
|
13519
|
-
//#endregion
|
|
13520
13438
|
//#region node_modules/.pnpm/@opentelemetry+exporter-metrics-otlp-http@0.218.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/exporter-metrics-otlp-http/build/esm/OTLPMetricExporterOptions.js
|
|
13521
13439
|
var AggregationTemporalityPreference;
|
|
13522
13440
|
(function(AggregationTemporalityPreference) {
|
|
@@ -13901,13 +13819,49 @@ var BUCKET_PROFILES = [
|
|
|
13901
13819
|
["rtc_playback_start_transition_missing", RTC_LOST_FRAME_BUCKETS],
|
|
13902
13820
|
["rtc_playback_end_transition_missing", RTC_LOST_FRAME_BUCKETS],
|
|
13903
13821
|
["rtc_playback_skip_rate_pct", RTC_PERCENT_BUCKETS],
|
|
13822
|
+
["rtc_playback_stall_rate_pct", RTC_PERCENT_BUCKETS],
|
|
13904
13823
|
["rtc_playback_duration_ms", RTC_PLAYBACK_DURATION_BUCKETS_MS],
|
|
13905
13824
|
["rtc_playback_stall_total_ms", RTC_STALL_DURATION_BUCKETS_MS],
|
|
13906
13825
|
["rtc_playback_stall_max_ms", RTC_STALL_DURATION_BUCKETS_MS],
|
|
13907
13826
|
["rtc_transport_packets_lost", RTC_LOST_FRAME_BUCKETS],
|
|
13908
13827
|
["rtc_transport_packets_recovered", RTC_LOST_FRAME_BUCKETS],
|
|
13909
13828
|
["rtc_transport_packets_dropped", RTC_LOST_FRAME_BUCKETS],
|
|
13910
|
-
["rtc_transport_packets_out_of_order", RTC_LOST_FRAME_BUCKETS]
|
|
13829
|
+
["rtc_transport_packets_out_of_order", RTC_LOST_FRAME_BUCKETS],
|
|
13830
|
+
["rtc_transport_rtp_packets_lost", RTC_LOST_FRAME_BUCKETS],
|
|
13831
|
+
["rtc_transport_rtp_loss_rate_pct", RTC_PERCENT_BUCKETS],
|
|
13832
|
+
["rtc_transport_rtt_ms", [
|
|
13833
|
+
10,
|
|
13834
|
+
20,
|
|
13835
|
+
30,
|
|
13836
|
+
40,
|
|
13837
|
+
50,
|
|
13838
|
+
75,
|
|
13839
|
+
100,
|
|
13840
|
+
150,
|
|
13841
|
+
200,
|
|
13842
|
+
300,
|
|
13843
|
+
400,
|
|
13844
|
+
600,
|
|
13845
|
+
800,
|
|
13846
|
+
1e3
|
|
13847
|
+
]],
|
|
13848
|
+
["rtc_transport_jitter_ms", [
|
|
13849
|
+
1,
|
|
13850
|
+
2,
|
|
13851
|
+
3,
|
|
13852
|
+
5,
|
|
13853
|
+
8,
|
|
13854
|
+
12,
|
|
13855
|
+
16,
|
|
13856
|
+
20,
|
|
13857
|
+
25,
|
|
13858
|
+
30,
|
|
13859
|
+
40,
|
|
13860
|
+
60,
|
|
13861
|
+
80,
|
|
13862
|
+
120,
|
|
13863
|
+
200
|
|
13864
|
+
]]
|
|
13911
13865
|
];
|
|
13912
13866
|
var METRIC_EXPORT_INTERVAL_MS = 1e4;
|
|
13913
13867
|
var sdkVersion$2 = "1.0.0";
|
|
@@ -13942,11 +13896,11 @@ function initializeOtelMetrics(version, resourceAttrs) {
|
|
|
13942
13896
|
"dsm": resourceAttrs.dsm
|
|
13943
13897
|
}),
|
|
13944
13898
|
readers: [new PeriodicExportingMetricReader({
|
|
13945
|
-
exporter: new OTLPMetricExporter({
|
|
13899
|
+
exporter: observeExporter(new OTLPMetricExporter({
|
|
13946
13900
|
url: OTEL_METRICS_ENDPOINT,
|
|
13947
13901
|
headers: { "Authorization": buildBasicAuthHeader$1() },
|
|
13948
13902
|
temporalityPreference: AggregationTemporality.DELTA
|
|
13949
|
-
}),
|
|
13903
|
+
}), "/v1/metrics", OTEL_METRICS_ENDPOINT),
|
|
13950
13904
|
exportIntervalMillis: METRIC_EXPORT_INTERVAL_MS
|
|
13951
13905
|
})],
|
|
13952
13906
|
views: [{
|
|
@@ -13983,6 +13937,23 @@ function getHistogram(name) {
|
|
|
13983
13937
|
return h;
|
|
13984
13938
|
}
|
|
13985
13939
|
/**
|
|
13940
|
+
* 本周期内是否写入过**业务** metric。
|
|
13941
|
+
*
|
|
13942
|
+
* 观测遥测通道自身的那条记录(http.client.request.duration 的 /v1/* operation)有意
|
|
13943
|
+
* 不弄脏它:那条记录本身产生于「导出」这个动作,若也算数,就成了
|
|
13944
|
+
* 导出 → 写入 → 下周期非空 → 又导出 → 又写入 的永动机,用户什么都不做也每 10s 发一包。
|
|
13945
|
+
*
|
|
13946
|
+
* 只有真实业务 metric 才把它置真;metrics 导出前若为假就整轮跳过,观测记录攒着等下一趟
|
|
13947
|
+
* 真有业务数据的车捎走。
|
|
13948
|
+
*/
|
|
13949
|
+
var hasBusinessMetric = false;
|
|
13950
|
+
/** 供导出侧读取并复位。@internal */
|
|
13951
|
+
function takeBusinessMetricFlag() {
|
|
13952
|
+
const v = hasBusinessMetric;
|
|
13953
|
+
hasBusinessMetric = false;
|
|
13954
|
+
return v;
|
|
13955
|
+
}
|
|
13956
|
+
/**
|
|
13986
13957
|
* 记录一个 metric 数据点(Histogram)。
|
|
13987
13958
|
* @param name metric 名
|
|
13988
13959
|
* @param value 数值(延迟 ms / 帧率 / 时长 / 次数 / 比率等)
|
|
@@ -13995,6 +13966,7 @@ function recordMetric(name, value, attributes = {}) {
|
|
|
13995
13966
|
const h = getHistogram(name);
|
|
13996
13967
|
if (!h) return;
|
|
13997
13968
|
try {
|
|
13969
|
+
if (name !== HTTP_CLIENT_DURATION_METRIC || !String(attributes.operation ?? "").startsWith("/v1/")) hasBusinessMetric = true;
|
|
13998
13970
|
h.record(value, attributes);
|
|
13999
13971
|
} catch (error) {
|
|
14000
13972
|
logger.warn(`[OTel-Metrics] Failed to record ${name}:`, error instanceof Error ? error.message : String(error));
|
|
@@ -14013,12 +13985,13 @@ function recordMetric(name, value, attributes = {}) {
|
|
|
14013
13985
|
* @internal
|
|
14014
13986
|
*/
|
|
14015
13987
|
function recordHttpClientDuration(params) {
|
|
14016
|
-
const { operation, method, durationMs, statusCode, serverAddress } = params;
|
|
13988
|
+
const { operation, method, durationMs, statusCode, serverAddress, cacheHit } = params;
|
|
14017
13989
|
const attrs = {
|
|
14018
13990
|
"http.request.method": method,
|
|
14019
13991
|
"operation": operation || "_OTHER"
|
|
14020
13992
|
};
|
|
14021
13993
|
if (serverAddress) attrs["server.address"] = serverAddress;
|
|
13994
|
+
if (typeof cacheHit === "boolean") attrs["cache_hit"] = cacheHit;
|
|
14022
13995
|
if (typeof statusCode === "number") attrs["http.response.status_code"] = statusCode;
|
|
14023
13996
|
else attrs["error.type"] = "transport_error";
|
|
14024
13997
|
recordMetric(HTTP_CLIENT_DURATION_METRIC, durationMs, attrs);
|
|
@@ -14043,6 +14016,226 @@ function cleanupOtelMetrics() {
|
|
|
14043
14016
|
}
|
|
14044
14017
|
}
|
|
14045
14018
|
//#endregion
|
|
14019
|
+
//#region utils/otel-export-observer.ts
|
|
14020
|
+
/**
|
|
14021
|
+
* 遥测通道自身的送达可观测性 —— 给 OTLP 导出请求做 Transaction 上报。
|
|
14022
|
+
*
|
|
14023
|
+
* 三条通道(logs / metrics / traces)此前把导出结果直接透传给 BatchProcessor 就丢了:
|
|
14024
|
+
* 导出批量失败(网络、CDN 缓存、鉴权、后端 5xx)在后台侧只表现为「某个 app_id 数据变少」,
|
|
14025
|
+
* 分不清是真没数据还是上报挂了。之前 EdgeOne 边缘缓存无 ACAO 头导致 CORS 全挂就是这类。
|
|
14026
|
+
*
|
|
14027
|
+
* 复用业务请求那套 `http.client.request.duration`(同 metric 名、同 Network profile 桶、
|
|
14028
|
+
* 同成败判定),只是新增 operation 取值。后台不必为此建新看板。
|
|
14029
|
+
*
|
|
14030
|
+
* ## 自举局限(务必知悉)
|
|
14031
|
+
*
|
|
14032
|
+
* 这条上报**走的正是它要观测的通道**,所以:
|
|
14033
|
+
* - 通道整体挂掉时,这条 metric 同样发不出去 —— 覆盖不了「全挂」场景。全挂在后台侧的
|
|
14034
|
+
* 表现是该 app_id 数据整体消失,那本身才是信号。
|
|
14035
|
+
* - 真正能观测到的是**部分失败**(三条通道各自独立 exporter,logs 挂了 metrics 可能还通)
|
|
14036
|
+
* 与**间歇失败**(断续失败后恢复,失败计数随下一批发出)。
|
|
14037
|
+
*
|
|
14038
|
+
* ## 记录时机:暂存,等下一次导出搭车
|
|
14039
|
+
*
|
|
14040
|
+
* 导出结束时**不直接写 histogram**,只记进内存里的 pending 列表;等下一次导出发生时,
|
|
14041
|
+
* 才在其**开始**把 pending 写进 histogram。
|
|
14042
|
+
*
|
|
14043
|
+
* 这样做是为了让 metrics 通道自己也能被观测。直接写会形成自反馈:metrics 的导出由
|
|
14044
|
+
* `PeriodicExportingMetricReader` 周期驱动,写入产生数据点 → 下周期必须导出 → 导出
|
|
14045
|
+
* 又写入 —— 空闲时也永不静默。改成暂存后,**写入本身不触发任何导出**,只是等下一次
|
|
14046
|
+
* 本来就要发生的导出把它带走,环就断了。
|
|
14047
|
+
*
|
|
14048
|
+
* 代价是延迟一个周期:reader 先 `collect()` 快照、再 `_export()` 发送(见
|
|
14049
|
+
* PeriodicExportingMetricReader 的 _runOnce),所以在导出时写入必然错过本轮快照,
|
|
14050
|
+
* 数据落在下一轮。对成功率这种趋势指标,慢一个周期无妨。
|
|
14051
|
+
*
|
|
14052
|
+
* 副作用是最后一批 pending 会随页面关闭丢失(进程结束,没有"下一次导出")。同样可接受:
|
|
14053
|
+
* 丢的是最后 10s 的成功率样本,不是业务数据。
|
|
14054
|
+
*
|
|
14055
|
+
* @internal
|
|
14056
|
+
*/
|
|
14057
|
+
/**
|
|
14058
|
+
* 已完成但尚未写进 histogram 的导出结果。上限用于兜底:正常情况下每个周期都会被
|
|
14059
|
+
* 排空,只有在「导出彻底停止但仍有 record 进来」这类异常下才会堆积。
|
|
14060
|
+
*/
|
|
14061
|
+
var pending = [];
|
|
14062
|
+
var PENDING_LIMIT = 256;
|
|
14063
|
+
/** 暂存一次 OTLP 导出的结果,等下一次导出搭车写入(见文件头「记录时机」)。 */
|
|
14064
|
+
function enqueueExportResult(p) {
|
|
14065
|
+
if (pending.length >= PENDING_LIMIT) pending.shift();
|
|
14066
|
+
pending.push(p);
|
|
14067
|
+
}
|
|
14068
|
+
/**
|
|
14069
|
+
* 把暂存的导出结果写进 histogram。在**导出开始时**调用:此时 reader 已完成本轮
|
|
14070
|
+
* collect 快照,写入的数据会随下一轮发出。
|
|
14071
|
+
*/
|
|
14072
|
+
function flushPendingExports() {
|
|
14073
|
+
if (pending.length === 0) return;
|
|
14074
|
+
const batch = pending.splice(0, pending.length);
|
|
14075
|
+
for (const p of batch) recordHttpClientDuration({
|
|
14076
|
+
operation: p.channel,
|
|
14077
|
+
method: "POST",
|
|
14078
|
+
durationMs: p.durationMs,
|
|
14079
|
+
statusCode: p.success ? 200 : void 0,
|
|
14080
|
+
serverAddress: p.serverAddress
|
|
14081
|
+
});
|
|
14082
|
+
}
|
|
14083
|
+
/** 从 OTLP endpoint URL 取 host(低基数),取不到时退回 `_OTHER`。 */
|
|
14084
|
+
function hostOf(endpoint) {
|
|
14085
|
+
try {
|
|
14086
|
+
return new URL(endpoint).host;
|
|
14087
|
+
} catch {
|
|
14088
|
+
return "_OTHER";
|
|
14089
|
+
}
|
|
14090
|
+
}
|
|
14091
|
+
/**
|
|
14092
|
+
* 给任意 OTLP exporter 包一层导出结果观测。原样透传参数与 callback,只在 callback
|
|
14093
|
+
* 回来时把结果暂存 —— 不改变导出行为,也不吞异常。
|
|
14094
|
+
*/
|
|
14095
|
+
function observeExporter(inner, channel, endpoint) {
|
|
14096
|
+
const serverAddress = hostOf(endpoint);
|
|
14097
|
+
const wrapped = new Proxy(inner, { get(target, prop, receiver) {
|
|
14098
|
+
if (prop === "export") return observedExport;
|
|
14099
|
+
const value = Reflect.get(target, prop, receiver);
|
|
14100
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
14101
|
+
} });
|
|
14102
|
+
const observedExport = function(items, resultCallback) {
|
|
14103
|
+
try {
|
|
14104
|
+
flushPendingExports();
|
|
14105
|
+
} catch {}
|
|
14106
|
+
if (channel === "/v1/metrics" && !takeBusinessMetricFlag()) {
|
|
14107
|
+
resultCallback({ code: 0 });
|
|
14108
|
+
return;
|
|
14109
|
+
}
|
|
14110
|
+
const startMs = Date.now();
|
|
14111
|
+
inner.export(items, (result) => {
|
|
14112
|
+
try {
|
|
14113
|
+
enqueueExportResult({
|
|
14114
|
+
channel,
|
|
14115
|
+
durationMs: Math.round(Date.now() - startMs),
|
|
14116
|
+
success: result.code === 0,
|
|
14117
|
+
serverAddress
|
|
14118
|
+
});
|
|
14119
|
+
} catch {}
|
|
14120
|
+
resultCallback(result);
|
|
14121
|
+
});
|
|
14122
|
+
};
|
|
14123
|
+
return wrapped;
|
|
14124
|
+
}
|
|
14125
|
+
//#endregion
|
|
14126
|
+
//#region utils/event-store.ts
|
|
14127
|
+
/**
|
|
14128
|
+
* Event Store - Persistent buffer for OTel telemetry events.
|
|
14129
|
+
*
|
|
14130
|
+
* Each emitted event is written here before being handed to the OTel
|
|
14131
|
+
* BatchProcessor. When the wrapper exporter picks a batch up for HTTP
|
|
14132
|
+
* upload it deletes those records — so anything still here on next
|
|
14133
|
+
* startup is unsent and gets re-emitted (replay).
|
|
14134
|
+
*
|
|
14135
|
+
* Schema is intentionally minimal: no status / retry_count. A record's
|
|
14136
|
+
* presence in the store means "not yet handed to inner.export".
|
|
14137
|
+
* @internal
|
|
14138
|
+
*/
|
|
14139
|
+
var DB_NAME = "avatarkit_events";
|
|
14140
|
+
var STORE_NAME = "events";
|
|
14141
|
+
var DB_VERSION = 1;
|
|
14142
|
+
var EventStore = class {
|
|
14143
|
+
db = null;
|
|
14144
|
+
initPromise = null;
|
|
14145
|
+
initialize() {
|
|
14146
|
+
if (this.db) return Promise.resolve();
|
|
14147
|
+
if (this.initPromise) return this.initPromise;
|
|
14148
|
+
this.initPromise = new Promise((resolve, reject) => {
|
|
14149
|
+
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
14150
|
+
req.onerror = () => reject(/* @__PURE__ */ new Error("Failed to open avatarkit_events"));
|
|
14151
|
+
req.onsuccess = () => {
|
|
14152
|
+
this.db = req.result;
|
|
14153
|
+
resolve();
|
|
14154
|
+
};
|
|
14155
|
+
req.onupgradeneeded = (e) => {
|
|
14156
|
+
const db = e.target.result;
|
|
14157
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, {
|
|
14158
|
+
keyPath: "id",
|
|
14159
|
+
autoIncrement: true
|
|
14160
|
+
});
|
|
14161
|
+
};
|
|
14162
|
+
});
|
|
14163
|
+
return this.initPromise;
|
|
14164
|
+
}
|
|
14165
|
+
/**
|
|
14166
|
+
* Append a record, returning the auto-generated id.
|
|
14167
|
+
* Caller must use this id as the `_index` attribute when emitting to OTel.
|
|
14168
|
+
*/
|
|
14169
|
+
async add(record) {
|
|
14170
|
+
if (!this.db) await this.initialize();
|
|
14171
|
+
if (!this.db) throw new Error("event-store not available");
|
|
14172
|
+
return new Promise((resolve, reject) => {
|
|
14173
|
+
const req = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).add(record);
|
|
14174
|
+
req.onsuccess = () => resolve(req.result);
|
|
14175
|
+
req.onerror = () => reject(/* @__PURE__ */ new Error("event-store add failed"));
|
|
14176
|
+
});
|
|
14177
|
+
}
|
|
14178
|
+
/**
|
|
14179
|
+
* Atomically: for each id, check whether it still exists in the store;
|
|
14180
|
+
* if so, delete it and include it in the returned set. Used by the
|
|
14181
|
+
* wrapper exporter to claim ownership of a batch — only records the
|
|
14182
|
+
* caller successfully "took" should be forwarded to inner.export.
|
|
14183
|
+
*
|
|
14184
|
+
* This is the multi-tab dedup mechanism: when Tab B starts and replays
|
|
14185
|
+
* records that Tab A also still has in memory, only one tab will win
|
|
14186
|
+
* the take and actually send.
|
|
14187
|
+
*
|
|
14188
|
+
* The get + delete pair runs inside a single readwrite transaction so
|
|
14189
|
+
* concurrent tabs see consistent state.
|
|
14190
|
+
*/
|
|
14191
|
+
async takeIfExists(ids) {
|
|
14192
|
+
const taken = /* @__PURE__ */ new Set();
|
|
14193
|
+
if (ids.length === 0) return taken;
|
|
14194
|
+
if (!this.db) await this.initialize();
|
|
14195
|
+
if (!this.db) return taken;
|
|
14196
|
+
return new Promise((resolve) => {
|
|
14197
|
+
const tx = this.db.transaction([STORE_NAME], "readwrite");
|
|
14198
|
+
const store = tx.objectStore(STORE_NAME);
|
|
14199
|
+
let pending = ids.length;
|
|
14200
|
+
const finish = () => {
|
|
14201
|
+
if (--pending === 0) resolve(taken);
|
|
14202
|
+
};
|
|
14203
|
+
for (const id of ids) {
|
|
14204
|
+
const req = store.get(id);
|
|
14205
|
+
req.onsuccess = () => {
|
|
14206
|
+
if (req.result !== void 0) {
|
|
14207
|
+
taken.add(id);
|
|
14208
|
+
store.delete(id);
|
|
14209
|
+
}
|
|
14210
|
+
finish();
|
|
14211
|
+
};
|
|
14212
|
+
req.onerror = () => finish();
|
|
14213
|
+
}
|
|
14214
|
+
tx.onerror = () => {
|
|
14215
|
+
logger.warn("[event-store] takeIfExists tx failed");
|
|
14216
|
+
resolve(taken);
|
|
14217
|
+
};
|
|
14218
|
+
});
|
|
14219
|
+
}
|
|
14220
|
+
/**
|
|
14221
|
+
* Return all stored records. Used on startup to replay anything left
|
|
14222
|
+
* over from a previous (possibly crashed) session.
|
|
14223
|
+
*/
|
|
14224
|
+
async getAll() {
|
|
14225
|
+
if (!this.db) await this.initialize();
|
|
14226
|
+
if (!this.db) return [];
|
|
14227
|
+
return new Promise((resolve) => {
|
|
14228
|
+
const req = this.db.transaction([STORE_NAME], "readonly").objectStore(STORE_NAME).getAll();
|
|
14229
|
+
req.onsuccess = () => resolve(req.result || []);
|
|
14230
|
+
req.onerror = () => {
|
|
14231
|
+
logger.warn("[event-store] getAll failed");
|
|
14232
|
+
resolve([]);
|
|
14233
|
+
};
|
|
14234
|
+
});
|
|
14235
|
+
}
|
|
14236
|
+
};
|
|
14237
|
+
var eventStore = new EventStore();
|
|
14238
|
+
//#endregion
|
|
14046
14239
|
//#region utils/bootstrap.ts
|
|
14047
14240
|
/**
|
|
14048
14241
|
* Bootstrap 全球接入入口
|
|
@@ -14339,6 +14532,23 @@ function monoTimestamp() {
|
|
|
14339
14532
|
value: performance.now()
|
|
14340
14533
|
};
|
|
14341
14534
|
}
|
|
14535
|
+
/**
|
|
14536
|
+
* 把**已经取好**的 `performance.now()` 读数包成 MonoMark。
|
|
14537
|
+
*
|
|
14538
|
+
* 用于时刻不在当前线程/当前时机产生的场景:Opus 编解码在 Worker 里逐包打点,那些
|
|
14539
|
+
* 时刻随结果回传后才到主线程手上,不能用 `monoTimestamp()` 重新取(那会变成
|
|
14540
|
+
* 「整批结果回来」的时刻,把逐包的时间分布抹平)。
|
|
14541
|
+
*
|
|
14542
|
+
* ⚠️ 传进来的值必须已换算到**主线程**的 `performance.now()` 轴上。Worker 有自己的
|
|
14543
|
+
* time origin,直接回传它的 `performance.now()` 会得到负的 dur_ms(实测 -8292);
|
|
14544
|
+
* 各 worker 均以 `timeOrigin + now()` 发绝对时刻,由 proxy 减去主线程 timeOrigin。
|
|
14545
|
+
*/
|
|
14546
|
+
function monoMarkFrom(value) {
|
|
14547
|
+
return {
|
|
14548
|
+
[MARK_KEY]: "mono",
|
|
14549
|
+
value
|
|
14550
|
+
};
|
|
14551
|
+
}
|
|
14342
14552
|
function isMark(v) {
|
|
14343
14553
|
return typeof v === "object" && v !== null && MARK_KEY in v;
|
|
14344
14554
|
}
|
|
@@ -14444,13 +14654,13 @@ function initializeOtel(version, resourceAttrs) {
|
|
|
14444
14654
|
"region": resourceAttrs.region,
|
|
14445
14655
|
"dsm": resourceAttrs.dsm
|
|
14446
14656
|
}),
|
|
14447
|
-
processors: [new BatchLogRecordProcessor(new ReliableExporter(new OTLPLogExporter({
|
|
14657
|
+
processors: [new BatchLogRecordProcessor(new ReliableExporter(observeExporter(new OTLPLogExporter({
|
|
14448
14658
|
url: OTEL_LOGS_ENDPOINT,
|
|
14449
14659
|
headers: {
|
|
14450
14660
|
"Authorization": buildBasicAuthHeader(),
|
|
14451
14661
|
"stream-name": OTEL_STREAM_NAME
|
|
14452
14662
|
}
|
|
14453
|
-
})))]
|
|
14663
|
+
}), "/v1/logs", OTEL_LOGS_ENDPOINT)))]
|
|
14454
14664
|
});
|
|
14455
14665
|
logs.setGlobalLoggerProvider(loggerProvider);
|
|
14456
14666
|
isInitialized$1 = true;
|
|
@@ -15329,7 +15539,7 @@ async function writeToLocalLog(message, level, extra = {}) {
|
|
|
15329
15539
|
try {
|
|
15330
15540
|
const { shouldPersist, sanitized } = sanitizeForLocalLog(message);
|
|
15331
15541
|
if (!shouldPersist) return;
|
|
15332
|
-
const { AvatarSDK } = await import("./AvatarSDK-
|
|
15542
|
+
const { AvatarSDK } = await import("./AvatarSDK-DqP8n0ex.js").then((n) => n.n);
|
|
15333
15543
|
const version = AvatarSDK.version;
|
|
15334
15544
|
const appId = idManager.getAppId();
|
|
15335
15545
|
const userId = idManager.getUserId();
|
|
@@ -15417,4 +15627,4 @@ function captureErrorContext(level, error) {
|
|
|
15417
15627
|
});
|
|
15418
15628
|
}
|
|
15419
15629
|
//#endregion
|
|
15420
|
-
export {
|
|
15630
|
+
export { isSpanContextValid as $, defaultResource as A, FrameStarvationMode as At, otperformance as B, recordMetric as C, ConnectionState as Ct, createResource as D, DEFAULT_REGION_REQUEST as Dt, createInstrumentationScope as E, DEFAULT_REGION as Et, hrTime as F, TransitionType as Ft, getNumberFromEnv as G, ATTR_EXCEPTION_STACKTRACE as H, hrTimeDuration as I, isTracingSuppressed as J, getStringFromEnv as K, isTimeInput as L, BindOnceFuture as M, LogLevel as Mt, ExportResultCode as N, RENDER_QUALITY_PARAMS as Nt, toAttributes$1 as O, DrivingServiceMode as Ot, addHrTimes as P, RenderQuality as Pt, context as Q, isTimeInputHrTime as R, recordHttpClientDuration as S, AvatarState as St, JSON_ENCODER as T, DEFAULT_OPUS_BITRATE as Tt, ATTR_EXCEPTION_TYPE as U, ATTR_EXCEPTION_MESSAGE as V, ATTR_SERVICE_NAME as W, trace as X, suppressTracing as Y, diag as Z, fetchBootstrap as _, isDebugMode as _t, clientContextFields as a, ROOT_CONTEXT as at, cleanupOtelMetrics as b, AnimationType as bt, logEvent as c, getGlobal as ct, cleanupOtel as d, OTEL_LOGS_ENDPOINT as dt, isValidTraceId as et, initializeOtel as f, OTEL_PASSWORD as ft, clockSync as g, OTEL_USERNAME as gt, resolveMarks as h, OTEL_TRACES_STREAM_NAME as ht, cleanupPostHog as i, createNoopMeter as it, resourceFromAttributes as j, LoadProgress as jt, OTLPExporterBase as k, ErrorCode as kt, logMetric as l, registerGlobal as lt, monoTimestamp as m, OTEL_TRACES_ENDPOINT as mt, setLogLevel as n, TraceFlags as nt, getLogsFeatureFlag as o, createContextKey as ot, monoMarkFrom as p, OTEL_STREAM_NAME as pt, globalErrorHandler as q, logSink as r, ContextAPI as rt, initializePostHog as s, DiagAPI as st, logger as t, INVALID_SPAN_CONTEXT as tt, updatePostHogPersonPropertiesForFlags as u, unregisterGlobal as ut, hostOf as v, idManager as vt, createLegacyOtlpBrowserExportDelegate as w, ConversationState as wt, initializeOtelMetrics as x, AvatarError as xt, observeExporter as y, generateTraceId as yt, millisToHrTime as z };
|