@spatius/avatarkit 1.3.1-beta.7 → 1.3.1
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 +22 -0
- package/dist/{AvatarDownloader-B1tJTnIb.js → AvatarDownloader-DyhenPKd.js} +98 -43
- package/dist/{AvatarSDK-CTprrfJk.js → AvatarSDK-DgPvHU4L.js} +162 -2560
- package/dist/{OpusCodec-PWv7ynPp.js → OpusCodec-CXU7ysrU.js} +43 -9
- package/dist/{OpusDecoderProxy-m0fVN_M2.js → OpusDecoderProxy-Cq8Z_MZY.js} +19 -6
- package/dist/{OpusEncoderProxy-DiW21sBK.js → OpusEncoderProxy-CzY-VckE.js} +42 -7
- package/dist/{StreamingAudioPlayer-C6hgkvbK.js → StreamingAudioPlayer-CIJmBp3z.js} +2 -2
- package/dist/assets/{AvatarDownloader-LO9uuYvm.js → AvatarDownloader-G5FO1bVl.js} +96 -41
- package/dist/assets/{AvatarSDK-CPEeTeLT.js → AvatarSDK-C_Anluwp.js} +149 -12
- package/dist/assets/{OpusDecoderWorker.worker-CSSWn75M.js → OpusDecoderWorker.worker-C2T0afkj.js} +21 -5
- package/dist/assets/{OpusEncoderWorker.worker-q4TdCVDZ.js → OpusEncoderWorker.worker-DLYO9vPK.js} +53 -11
- package/dist/assets/{logger-DCzHWd4N.js → logger-YPooEAbA.js} +319 -126
- package/dist/core/AvatarController.d.ts +10 -0
- package/dist/{error-utils-BCZDCrc6.js → error-utils-BogEllAd.js} +1 -1
- package/dist/index.js +274 -43
- package/dist/internal-telemetry.d.ts +45 -20
- package/dist/internal-telemetry.js +27 -2
- package/dist/{logger-DdmfSEn-.js → logger-BOpQ7u0w.js} +336 -126
- package/dist/otel-trace-Ct4T48nC.js +2565 -0
- package/dist/{pwa-cache-manager-BMKyJWXI.js → pwa-cache-manager-C7NbU52Q.js} +1 -1
- package/dist/types/index.d.ts +30 -4
- package/package.json +1 -1
|
@@ -1156,6 +1156,28 @@ let 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
|
let LogLevel = /* @__PURE__ */ function(LogLevel) {
|
|
@@ -7111,7 +7133,7 @@ let customRandom = (alphabet, defaultSize, getRandom) => {
|
|
|
7111
7133
|
};
|
|
7112
7134
|
};
|
|
7113
7135
|
let customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size | 0, random);
|
|
7114
|
-
let nanoid$
|
|
7136
|
+
let nanoid$2 = (size = 21) => {
|
|
7115
7137
|
let id = "";
|
|
7116
7138
|
let bytes = crypto.getRandomValues(new Uint8Array(size |= 0));
|
|
7117
7139
|
while (size--) id += urlAlphabet[bytes[size] & 63];
|
|
@@ -7136,11 +7158,11 @@ function getOrCreateClientId() {
|
|
|
7136
7158
|
try {
|
|
7137
7159
|
const stored = localStorage.getItem(CLIENT_ID_STORAGE_KEY);
|
|
7138
7160
|
if (stored) return stored;
|
|
7139
|
-
const clientId = nanoid$
|
|
7161
|
+
const clientId = nanoid$2(21);
|
|
7140
7162
|
localStorage.setItem(CLIENT_ID_STORAGE_KEY, clientId);
|
|
7141
7163
|
return clientId;
|
|
7142
7164
|
} catch (error) {
|
|
7143
|
-
return `temp_${nanoid$
|
|
7165
|
+
return `temp_${nanoid$2(21)}_${Date.now()}`;
|
|
7144
7166
|
}
|
|
7145
7167
|
}
|
|
7146
7168
|
//#endregion
|
|
@@ -7158,7 +7180,7 @@ function getOrCreateClientId() {
|
|
|
7158
7180
|
* Custom character set: only uppercase, lowercase letters and numbers (URL safe, no hyphens)
|
|
7159
7181
|
* Character set: A-Z, a-z, 0-9 (62 characters)
|
|
7160
7182
|
*/
|
|
7161
|
-
const nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);
|
|
7183
|
+
const nanoid$1 = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);
|
|
7162
7184
|
/**
|
|
7163
7185
|
* Generate unified conversation ID
|
|
7164
7186
|
*
|
|
@@ -7173,7 +7195,7 @@ const nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
|
|
7173
7195
|
const generateTraceId = () => generateConversationId();
|
|
7174
7196
|
function generateConversationId() {
|
|
7175
7197
|
const now = /* @__PURE__ */ new Date();
|
|
7176
|
-
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()}`;
|
|
7198
|
+
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()}`;
|
|
7177
7199
|
}
|
|
7178
7200
|
//#endregion
|
|
7179
7201
|
//#region utils/id-manager.ts
|
|
@@ -7182,9 +7204,12 @@ function generateConversationId() {
|
|
|
7182
7204
|
* Unified management of all types of IDs in SDK
|
|
7183
7205
|
* @internal
|
|
7184
7206
|
*/
|
|
7207
|
+
/** 与 conversation_id 同字符集:URL 安全、无连字符。 */
|
|
7208
|
+
const nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);
|
|
7185
7209
|
var IdManager = class {
|
|
7186
7210
|
ids = {
|
|
7187
7211
|
clientId: "",
|
|
7212
|
+
sessionId: "",
|
|
7188
7213
|
userId: null,
|
|
7189
7214
|
appId: null,
|
|
7190
7215
|
sessionToken: null,
|
|
@@ -7267,10 +7292,16 @@ var IdManager = class {
|
|
|
7267
7292
|
getLogContext() {
|
|
7268
7293
|
return {
|
|
7269
7294
|
client_id: this.ids.clientId,
|
|
7295
|
+
session_id: this.ids.sessionId,
|
|
7270
7296
|
user_id: this.ids.userId || "",
|
|
7271
7297
|
app_id: this.ids.appId || ""
|
|
7272
7298
|
};
|
|
7273
7299
|
}
|
|
7300
|
+
/** 本次启动的 session id;首次调用时生成。 @internal */
|
|
7301
|
+
getSessionId() {
|
|
7302
|
+
if (!this.ids.sessionId) this.ids.sessionId = nanoid();
|
|
7303
|
+
return this.ids.sessionId;
|
|
7304
|
+
}
|
|
7274
7305
|
/**
|
|
7275
7306
|
* Clear all IDs (for testing or reset)
|
|
7276
7307
|
* @internal
|
|
@@ -13329,119 +13360,6 @@ var OTLPLogExporter = class extends OTLPExporterBase {
|
|
|
13329
13360
|
}
|
|
13330
13361
|
};
|
|
13331
13362
|
//#endregion
|
|
13332
|
-
//#region utils/event-store.ts
|
|
13333
|
-
/**
|
|
13334
|
-
* Event Store - Persistent buffer for OTel telemetry events.
|
|
13335
|
-
*
|
|
13336
|
-
* Each emitted event is written here before being handed to the OTel
|
|
13337
|
-
* BatchProcessor. When the wrapper exporter picks a batch up for HTTP
|
|
13338
|
-
* upload it deletes those records — so anything still here on next
|
|
13339
|
-
* startup is unsent and gets re-emitted (replay).
|
|
13340
|
-
*
|
|
13341
|
-
* Schema is intentionally minimal: no status / retry_count. A record's
|
|
13342
|
-
* presence in the store means "not yet handed to inner.export".
|
|
13343
|
-
* @internal
|
|
13344
|
-
*/
|
|
13345
|
-
const DB_NAME = "avatarkit_events";
|
|
13346
|
-
const STORE_NAME = "events";
|
|
13347
|
-
const DB_VERSION = 1;
|
|
13348
|
-
var EventStore = class {
|
|
13349
|
-
db = null;
|
|
13350
|
-
initPromise = null;
|
|
13351
|
-
initialize() {
|
|
13352
|
-
if (this.db) return Promise.resolve();
|
|
13353
|
-
if (this.initPromise) return this.initPromise;
|
|
13354
|
-
this.initPromise = new Promise((resolve, reject) => {
|
|
13355
|
-
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
13356
|
-
req.onerror = () => reject(/* @__PURE__ */ new Error("Failed to open avatarkit_events"));
|
|
13357
|
-
req.onsuccess = () => {
|
|
13358
|
-
this.db = req.result;
|
|
13359
|
-
resolve();
|
|
13360
|
-
};
|
|
13361
|
-
req.onupgradeneeded = (e) => {
|
|
13362
|
-
const db = e.target.result;
|
|
13363
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, {
|
|
13364
|
-
keyPath: "id",
|
|
13365
|
-
autoIncrement: true
|
|
13366
|
-
});
|
|
13367
|
-
};
|
|
13368
|
-
});
|
|
13369
|
-
return this.initPromise;
|
|
13370
|
-
}
|
|
13371
|
-
/**
|
|
13372
|
-
* Append a record, returning the auto-generated id.
|
|
13373
|
-
* Caller must use this id as the `_index` attribute when emitting to OTel.
|
|
13374
|
-
*/
|
|
13375
|
-
async add(record) {
|
|
13376
|
-
if (!this.db) await this.initialize();
|
|
13377
|
-
if (!this.db) throw new Error("event-store not available");
|
|
13378
|
-
return new Promise((resolve, reject) => {
|
|
13379
|
-
const req = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).add(record);
|
|
13380
|
-
req.onsuccess = () => resolve(req.result);
|
|
13381
|
-
req.onerror = () => reject(/* @__PURE__ */ new Error("event-store add failed"));
|
|
13382
|
-
});
|
|
13383
|
-
}
|
|
13384
|
-
/**
|
|
13385
|
-
* Atomically: for each id, check whether it still exists in the store;
|
|
13386
|
-
* if so, delete it and include it in the returned set. Used by the
|
|
13387
|
-
* wrapper exporter to claim ownership of a batch — only records the
|
|
13388
|
-
* caller successfully "took" should be forwarded to inner.export.
|
|
13389
|
-
*
|
|
13390
|
-
* This is the multi-tab dedup mechanism: when Tab B starts and replays
|
|
13391
|
-
* records that Tab A also still has in memory, only one tab will win
|
|
13392
|
-
* the take and actually send.
|
|
13393
|
-
*
|
|
13394
|
-
* The get + delete pair runs inside a single readwrite transaction so
|
|
13395
|
-
* concurrent tabs see consistent state.
|
|
13396
|
-
*/
|
|
13397
|
-
async takeIfExists(ids) {
|
|
13398
|
-
const taken = /* @__PURE__ */ new Set();
|
|
13399
|
-
if (ids.length === 0) return taken;
|
|
13400
|
-
if (!this.db) await this.initialize();
|
|
13401
|
-
if (!this.db) return taken;
|
|
13402
|
-
return new Promise((resolve) => {
|
|
13403
|
-
const tx = this.db.transaction([STORE_NAME], "readwrite");
|
|
13404
|
-
const store = tx.objectStore(STORE_NAME);
|
|
13405
|
-
let pending = ids.length;
|
|
13406
|
-
const finish = () => {
|
|
13407
|
-
if (--pending === 0) resolve(taken);
|
|
13408
|
-
};
|
|
13409
|
-
for (const id of ids) {
|
|
13410
|
-
const req = store.get(id);
|
|
13411
|
-
req.onsuccess = () => {
|
|
13412
|
-
if (req.result !== void 0) {
|
|
13413
|
-
taken.add(id);
|
|
13414
|
-
store.delete(id);
|
|
13415
|
-
}
|
|
13416
|
-
finish();
|
|
13417
|
-
};
|
|
13418
|
-
req.onerror = () => finish();
|
|
13419
|
-
}
|
|
13420
|
-
tx.onerror = () => {
|
|
13421
|
-
logger.warn("[event-store] takeIfExists tx failed");
|
|
13422
|
-
resolve(taken);
|
|
13423
|
-
};
|
|
13424
|
-
});
|
|
13425
|
-
}
|
|
13426
|
-
/**
|
|
13427
|
-
* Return all stored records. Used on startup to replay anything left
|
|
13428
|
-
* over from a previous (possibly crashed) session.
|
|
13429
|
-
*/
|
|
13430
|
-
async getAll() {
|
|
13431
|
-
if (!this.db) await this.initialize();
|
|
13432
|
-
if (!this.db) return [];
|
|
13433
|
-
return new Promise((resolve) => {
|
|
13434
|
-
const req = this.db.transaction([STORE_NAME], "readonly").objectStore(STORE_NAME).getAll();
|
|
13435
|
-
req.onsuccess = () => resolve(req.result || []);
|
|
13436
|
-
req.onerror = () => {
|
|
13437
|
-
logger.warn("[event-store] getAll failed");
|
|
13438
|
-
resolve([]);
|
|
13439
|
-
};
|
|
13440
|
-
});
|
|
13441
|
-
}
|
|
13442
|
-
};
|
|
13443
|
-
const eventStore = new EventStore();
|
|
13444
|
-
//#endregion
|
|
13445
13363
|
//#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
|
|
13446
13364
|
var AggregationTemporalityPreference;
|
|
13447
13365
|
(function(AggregationTemporalityPreference) {
|
|
@@ -13826,13 +13744,49 @@ const BUCKET_PROFILES = [
|
|
|
13826
13744
|
["rtc_playback_start_transition_missing", RTC_LOST_FRAME_BUCKETS],
|
|
13827
13745
|
["rtc_playback_end_transition_missing", RTC_LOST_FRAME_BUCKETS],
|
|
13828
13746
|
["rtc_playback_skip_rate_pct", RTC_PERCENT_BUCKETS],
|
|
13747
|
+
["rtc_playback_stall_rate_pct", RTC_PERCENT_BUCKETS],
|
|
13829
13748
|
["rtc_playback_duration_ms", RTC_PLAYBACK_DURATION_BUCKETS_MS],
|
|
13830
13749
|
["rtc_playback_stall_total_ms", RTC_STALL_DURATION_BUCKETS_MS],
|
|
13831
13750
|
["rtc_playback_stall_max_ms", RTC_STALL_DURATION_BUCKETS_MS],
|
|
13832
13751
|
["rtc_transport_packets_lost", RTC_LOST_FRAME_BUCKETS],
|
|
13833
13752
|
["rtc_transport_packets_recovered", RTC_LOST_FRAME_BUCKETS],
|
|
13834
13753
|
["rtc_transport_packets_dropped", RTC_LOST_FRAME_BUCKETS],
|
|
13835
|
-
["rtc_transport_packets_out_of_order", RTC_LOST_FRAME_BUCKETS]
|
|
13754
|
+
["rtc_transport_packets_out_of_order", RTC_LOST_FRAME_BUCKETS],
|
|
13755
|
+
["rtc_transport_rtp_packets_lost", RTC_LOST_FRAME_BUCKETS],
|
|
13756
|
+
["rtc_transport_rtp_loss_rate_pct", RTC_PERCENT_BUCKETS],
|
|
13757
|
+
["rtc_transport_rtt_ms", [
|
|
13758
|
+
10,
|
|
13759
|
+
20,
|
|
13760
|
+
30,
|
|
13761
|
+
40,
|
|
13762
|
+
50,
|
|
13763
|
+
75,
|
|
13764
|
+
100,
|
|
13765
|
+
150,
|
|
13766
|
+
200,
|
|
13767
|
+
300,
|
|
13768
|
+
400,
|
|
13769
|
+
600,
|
|
13770
|
+
800,
|
|
13771
|
+
1e3
|
|
13772
|
+
]],
|
|
13773
|
+
["rtc_transport_jitter_ms", [
|
|
13774
|
+
1,
|
|
13775
|
+
2,
|
|
13776
|
+
3,
|
|
13777
|
+
5,
|
|
13778
|
+
8,
|
|
13779
|
+
12,
|
|
13780
|
+
16,
|
|
13781
|
+
20,
|
|
13782
|
+
25,
|
|
13783
|
+
30,
|
|
13784
|
+
40,
|
|
13785
|
+
60,
|
|
13786
|
+
80,
|
|
13787
|
+
120,
|
|
13788
|
+
200
|
|
13789
|
+
]]
|
|
13836
13790
|
];
|
|
13837
13791
|
const METRIC_EXPORT_INTERVAL_MS = 1e4;
|
|
13838
13792
|
let sdkVersion$2 = "1.0.0";
|
|
@@ -13866,11 +13820,11 @@ function initializeOtelMetrics(version, resourceAttrs) {
|
|
|
13866
13820
|
"dsm": resourceAttrs.dsm
|
|
13867
13821
|
}),
|
|
13868
13822
|
readers: [new PeriodicExportingMetricReader({
|
|
13869
|
-
exporter: new OTLPMetricExporter({
|
|
13823
|
+
exporter: observeExporter(new OTLPMetricExporter({
|
|
13870
13824
|
url: OTEL_METRICS_ENDPOINT,
|
|
13871
13825
|
headers: { "Authorization": buildBasicAuthHeader$1() },
|
|
13872
13826
|
temporalityPreference: AggregationTemporality.DELTA
|
|
13873
|
-
}),
|
|
13827
|
+
}), "/v1/metrics", OTEL_METRICS_ENDPOINT),
|
|
13874
13828
|
exportIntervalMillis: METRIC_EXPORT_INTERVAL_MS
|
|
13875
13829
|
})],
|
|
13876
13830
|
views: [{
|
|
@@ -13907,6 +13861,23 @@ function getHistogram(name) {
|
|
|
13907
13861
|
return h;
|
|
13908
13862
|
}
|
|
13909
13863
|
/**
|
|
13864
|
+
* 本周期内是否写入过**业务** metric。
|
|
13865
|
+
*
|
|
13866
|
+
* 观测遥测通道自身的那条记录(http.client.request.duration 的 /v1/* operation)有意
|
|
13867
|
+
* 不弄脏它:那条记录本身产生于「导出」这个动作,若也算数,就成了
|
|
13868
|
+
* 导出 → 写入 → 下周期非空 → 又导出 → 又写入 的永动机,用户什么都不做也每 10s 发一包。
|
|
13869
|
+
*
|
|
13870
|
+
* 只有真实业务 metric 才把它置真;metrics 导出前若为假就整轮跳过,观测记录攒着等下一趟
|
|
13871
|
+
* 真有业务数据的车捎走。
|
|
13872
|
+
*/
|
|
13873
|
+
let hasBusinessMetric = false;
|
|
13874
|
+
/** 供导出侧读取并复位。@internal */
|
|
13875
|
+
function takeBusinessMetricFlag() {
|
|
13876
|
+
const v = hasBusinessMetric;
|
|
13877
|
+
hasBusinessMetric = false;
|
|
13878
|
+
return v;
|
|
13879
|
+
}
|
|
13880
|
+
/**
|
|
13910
13881
|
* 记录一个 metric 数据点(Histogram)。
|
|
13911
13882
|
* @param name metric 名
|
|
13912
13883
|
* @param value 数值(延迟 ms / 帧率 / 时长 / 次数 / 比率等)
|
|
@@ -13919,6 +13890,7 @@ function recordMetric(name, value, attributes = {}) {
|
|
|
13919
13890
|
const h = getHistogram(name);
|
|
13920
13891
|
if (!h) return;
|
|
13921
13892
|
try {
|
|
13893
|
+
if (name !== HTTP_CLIENT_DURATION_METRIC || !String(attributes.operation ?? "").startsWith("/v1/")) hasBusinessMetric = true;
|
|
13922
13894
|
h.record(value, attributes);
|
|
13923
13895
|
} catch (error) {
|
|
13924
13896
|
logger.warn(`[OTel-Metrics] Failed to record ${name}:`, error instanceof Error ? error.message : String(error));
|
|
@@ -13937,12 +13909,13 @@ function recordMetric(name, value, attributes = {}) {
|
|
|
13937
13909
|
* @internal
|
|
13938
13910
|
*/
|
|
13939
13911
|
function recordHttpClientDuration(params) {
|
|
13940
|
-
const { operation, method, durationMs, statusCode, serverAddress } = params;
|
|
13912
|
+
const { operation, method, durationMs, statusCode, serverAddress, cacheHit } = params;
|
|
13941
13913
|
const attrs = {
|
|
13942
13914
|
"http.request.method": method,
|
|
13943
13915
|
"operation": operation || "_OTHER"
|
|
13944
13916
|
};
|
|
13945
13917
|
if (serverAddress) attrs["server.address"] = serverAddress;
|
|
13918
|
+
if (typeof cacheHit === "boolean") attrs["cache_hit"] = cacheHit;
|
|
13946
13919
|
if (typeof statusCode === "number") attrs["http.response.status_code"] = statusCode;
|
|
13947
13920
|
else attrs["error.type"] = "transport_error";
|
|
13948
13921
|
recordMetric(HTTP_CLIENT_DURATION_METRIC, durationMs, attrs);
|
|
@@ -13967,6 +13940,226 @@ function cleanupOtelMetrics() {
|
|
|
13967
13940
|
}
|
|
13968
13941
|
}
|
|
13969
13942
|
//#endregion
|
|
13943
|
+
//#region utils/otel-export-observer.ts
|
|
13944
|
+
/**
|
|
13945
|
+
* 遥测通道自身的送达可观测性 —— 给 OTLP 导出请求做 Transaction 上报。
|
|
13946
|
+
*
|
|
13947
|
+
* 三条通道(logs / metrics / traces)此前把导出结果直接透传给 BatchProcessor 就丢了:
|
|
13948
|
+
* 导出批量失败(网络、CDN 缓存、鉴权、后端 5xx)在后台侧只表现为「某个 app_id 数据变少」,
|
|
13949
|
+
* 分不清是真没数据还是上报挂了。之前 EdgeOne 边缘缓存无 ACAO 头导致 CORS 全挂就是这类。
|
|
13950
|
+
*
|
|
13951
|
+
* 复用业务请求那套 `http.client.request.duration`(同 metric 名、同 Network profile 桶、
|
|
13952
|
+
* 同成败判定),只是新增 operation 取值。后台不必为此建新看板。
|
|
13953
|
+
*
|
|
13954
|
+
* ## 自举局限(务必知悉)
|
|
13955
|
+
*
|
|
13956
|
+
* 这条上报**走的正是它要观测的通道**,所以:
|
|
13957
|
+
* - 通道整体挂掉时,这条 metric 同样发不出去 —— 覆盖不了「全挂」场景。全挂在后台侧的
|
|
13958
|
+
* 表现是该 app_id 数据整体消失,那本身才是信号。
|
|
13959
|
+
* - 真正能观测到的是**部分失败**(三条通道各自独立 exporter,logs 挂了 metrics 可能还通)
|
|
13960
|
+
* 与**间歇失败**(断续失败后恢复,失败计数随下一批发出)。
|
|
13961
|
+
*
|
|
13962
|
+
* ## 记录时机:暂存,等下一次导出搭车
|
|
13963
|
+
*
|
|
13964
|
+
* 导出结束时**不直接写 histogram**,只记进内存里的 pending 列表;等下一次导出发生时,
|
|
13965
|
+
* 才在其**开始**把 pending 写进 histogram。
|
|
13966
|
+
*
|
|
13967
|
+
* 这样做是为了让 metrics 通道自己也能被观测。直接写会形成自反馈:metrics 的导出由
|
|
13968
|
+
* `PeriodicExportingMetricReader` 周期驱动,写入产生数据点 → 下周期必须导出 → 导出
|
|
13969
|
+
* 又写入 —— 空闲时也永不静默。改成暂存后,**写入本身不触发任何导出**,只是等下一次
|
|
13970
|
+
* 本来就要发生的导出把它带走,环就断了。
|
|
13971
|
+
*
|
|
13972
|
+
* 代价是延迟一个周期:reader 先 `collect()` 快照、再 `_export()` 发送(见
|
|
13973
|
+
* PeriodicExportingMetricReader 的 _runOnce),所以在导出时写入必然错过本轮快照,
|
|
13974
|
+
* 数据落在下一轮。对成功率这种趋势指标,慢一个周期无妨。
|
|
13975
|
+
*
|
|
13976
|
+
* 副作用是最后一批 pending 会随页面关闭丢失(进程结束,没有"下一次导出")。同样可接受:
|
|
13977
|
+
* 丢的是最后 10s 的成功率样本,不是业务数据。
|
|
13978
|
+
*
|
|
13979
|
+
* @internal
|
|
13980
|
+
*/
|
|
13981
|
+
/**
|
|
13982
|
+
* 已完成但尚未写进 histogram 的导出结果。上限用于兜底:正常情况下每个周期都会被
|
|
13983
|
+
* 排空,只有在「导出彻底停止但仍有 record 进来」这类异常下才会堆积。
|
|
13984
|
+
*/
|
|
13985
|
+
const pending = [];
|
|
13986
|
+
const PENDING_LIMIT = 256;
|
|
13987
|
+
/** 暂存一次 OTLP 导出的结果,等下一次导出搭车写入(见文件头「记录时机」)。 */
|
|
13988
|
+
function enqueueExportResult(p) {
|
|
13989
|
+
if (pending.length >= PENDING_LIMIT) pending.shift();
|
|
13990
|
+
pending.push(p);
|
|
13991
|
+
}
|
|
13992
|
+
/**
|
|
13993
|
+
* 把暂存的导出结果写进 histogram。在**导出开始时**调用:此时 reader 已完成本轮
|
|
13994
|
+
* collect 快照,写入的数据会随下一轮发出。
|
|
13995
|
+
*/
|
|
13996
|
+
function flushPendingExports() {
|
|
13997
|
+
if (pending.length === 0) return;
|
|
13998
|
+
const batch = pending.splice(0, pending.length);
|
|
13999
|
+
for (const p of batch) recordHttpClientDuration({
|
|
14000
|
+
operation: p.channel,
|
|
14001
|
+
method: "POST",
|
|
14002
|
+
durationMs: p.durationMs,
|
|
14003
|
+
statusCode: p.success ? 200 : void 0,
|
|
14004
|
+
serverAddress: p.serverAddress
|
|
14005
|
+
});
|
|
14006
|
+
}
|
|
14007
|
+
/** 从 OTLP endpoint URL 取 host(低基数),取不到时退回 `_OTHER`。 */
|
|
14008
|
+
function hostOf(endpoint) {
|
|
14009
|
+
try {
|
|
14010
|
+
return new URL(endpoint).host;
|
|
14011
|
+
} catch {
|
|
14012
|
+
return "_OTHER";
|
|
14013
|
+
}
|
|
14014
|
+
}
|
|
14015
|
+
/**
|
|
14016
|
+
* 给任意 OTLP exporter 包一层导出结果观测。原样透传参数与 callback,只在 callback
|
|
14017
|
+
* 回来时把结果暂存 —— 不改变导出行为,也不吞异常。
|
|
14018
|
+
*/
|
|
14019
|
+
function observeExporter(inner, channel, endpoint) {
|
|
14020
|
+
const serverAddress = hostOf(endpoint);
|
|
14021
|
+
const wrapped = new Proxy(inner, { get(target, prop, receiver) {
|
|
14022
|
+
if (prop === "export") return observedExport;
|
|
14023
|
+
const value = Reflect.get(target, prop, receiver);
|
|
14024
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
14025
|
+
} });
|
|
14026
|
+
const observedExport = function(items, resultCallback) {
|
|
14027
|
+
try {
|
|
14028
|
+
flushPendingExports();
|
|
14029
|
+
} catch {}
|
|
14030
|
+
if (channel === "/v1/metrics" && !takeBusinessMetricFlag()) {
|
|
14031
|
+
resultCallback({ code: 0 });
|
|
14032
|
+
return;
|
|
14033
|
+
}
|
|
14034
|
+
const startMs = Date.now();
|
|
14035
|
+
inner.export(items, (result) => {
|
|
14036
|
+
try {
|
|
14037
|
+
enqueueExportResult({
|
|
14038
|
+
channel,
|
|
14039
|
+
durationMs: Math.round(Date.now() - startMs),
|
|
14040
|
+
success: result.code === 0,
|
|
14041
|
+
serverAddress
|
|
14042
|
+
});
|
|
14043
|
+
} catch {}
|
|
14044
|
+
resultCallback(result);
|
|
14045
|
+
});
|
|
14046
|
+
};
|
|
14047
|
+
return wrapped;
|
|
14048
|
+
}
|
|
14049
|
+
//#endregion
|
|
14050
|
+
//#region utils/event-store.ts
|
|
14051
|
+
/**
|
|
14052
|
+
* Event Store - Persistent buffer for OTel telemetry events.
|
|
14053
|
+
*
|
|
14054
|
+
* Each emitted event is written here before being handed to the OTel
|
|
14055
|
+
* BatchProcessor. When the wrapper exporter picks a batch up for HTTP
|
|
14056
|
+
* upload it deletes those records — so anything still here on next
|
|
14057
|
+
* startup is unsent and gets re-emitted (replay).
|
|
14058
|
+
*
|
|
14059
|
+
* Schema is intentionally minimal: no status / retry_count. A record's
|
|
14060
|
+
* presence in the store means "not yet handed to inner.export".
|
|
14061
|
+
* @internal
|
|
14062
|
+
*/
|
|
14063
|
+
const DB_NAME = "avatarkit_events";
|
|
14064
|
+
const STORE_NAME = "events";
|
|
14065
|
+
const DB_VERSION = 1;
|
|
14066
|
+
var EventStore = class {
|
|
14067
|
+
db = null;
|
|
14068
|
+
initPromise = null;
|
|
14069
|
+
initialize() {
|
|
14070
|
+
if (this.db) return Promise.resolve();
|
|
14071
|
+
if (this.initPromise) return this.initPromise;
|
|
14072
|
+
this.initPromise = new Promise((resolve, reject) => {
|
|
14073
|
+
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
14074
|
+
req.onerror = () => reject(/* @__PURE__ */ new Error("Failed to open avatarkit_events"));
|
|
14075
|
+
req.onsuccess = () => {
|
|
14076
|
+
this.db = req.result;
|
|
14077
|
+
resolve();
|
|
14078
|
+
};
|
|
14079
|
+
req.onupgradeneeded = (e) => {
|
|
14080
|
+
const db = e.target.result;
|
|
14081
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, {
|
|
14082
|
+
keyPath: "id",
|
|
14083
|
+
autoIncrement: true
|
|
14084
|
+
});
|
|
14085
|
+
};
|
|
14086
|
+
});
|
|
14087
|
+
return this.initPromise;
|
|
14088
|
+
}
|
|
14089
|
+
/**
|
|
14090
|
+
* Append a record, returning the auto-generated id.
|
|
14091
|
+
* Caller must use this id as the `_index` attribute when emitting to OTel.
|
|
14092
|
+
*/
|
|
14093
|
+
async add(record) {
|
|
14094
|
+
if (!this.db) await this.initialize();
|
|
14095
|
+
if (!this.db) throw new Error("event-store not available");
|
|
14096
|
+
return new Promise((resolve, reject) => {
|
|
14097
|
+
const req = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).add(record);
|
|
14098
|
+
req.onsuccess = () => resolve(req.result);
|
|
14099
|
+
req.onerror = () => reject(/* @__PURE__ */ new Error("event-store add failed"));
|
|
14100
|
+
});
|
|
14101
|
+
}
|
|
14102
|
+
/**
|
|
14103
|
+
* Atomically: for each id, check whether it still exists in the store;
|
|
14104
|
+
* if so, delete it and include it in the returned set. Used by the
|
|
14105
|
+
* wrapper exporter to claim ownership of a batch — only records the
|
|
14106
|
+
* caller successfully "took" should be forwarded to inner.export.
|
|
14107
|
+
*
|
|
14108
|
+
* This is the multi-tab dedup mechanism: when Tab B starts and replays
|
|
14109
|
+
* records that Tab A also still has in memory, only one tab will win
|
|
14110
|
+
* the take and actually send.
|
|
14111
|
+
*
|
|
14112
|
+
* The get + delete pair runs inside a single readwrite transaction so
|
|
14113
|
+
* concurrent tabs see consistent state.
|
|
14114
|
+
*/
|
|
14115
|
+
async takeIfExists(ids) {
|
|
14116
|
+
const taken = /* @__PURE__ */ new Set();
|
|
14117
|
+
if (ids.length === 0) return taken;
|
|
14118
|
+
if (!this.db) await this.initialize();
|
|
14119
|
+
if (!this.db) return taken;
|
|
14120
|
+
return new Promise((resolve) => {
|
|
14121
|
+
const tx = this.db.transaction([STORE_NAME], "readwrite");
|
|
14122
|
+
const store = tx.objectStore(STORE_NAME);
|
|
14123
|
+
let pending = ids.length;
|
|
14124
|
+
const finish = () => {
|
|
14125
|
+
if (--pending === 0) resolve(taken);
|
|
14126
|
+
};
|
|
14127
|
+
for (const id of ids) {
|
|
14128
|
+
const req = store.get(id);
|
|
14129
|
+
req.onsuccess = () => {
|
|
14130
|
+
if (req.result !== void 0) {
|
|
14131
|
+
taken.add(id);
|
|
14132
|
+
store.delete(id);
|
|
14133
|
+
}
|
|
14134
|
+
finish();
|
|
14135
|
+
};
|
|
14136
|
+
req.onerror = () => finish();
|
|
14137
|
+
}
|
|
14138
|
+
tx.onerror = () => {
|
|
14139
|
+
logger.warn("[event-store] takeIfExists tx failed");
|
|
14140
|
+
resolve(taken);
|
|
14141
|
+
};
|
|
14142
|
+
});
|
|
14143
|
+
}
|
|
14144
|
+
/**
|
|
14145
|
+
* Return all stored records. Used on startup to replay anything left
|
|
14146
|
+
* over from a previous (possibly crashed) session.
|
|
14147
|
+
*/
|
|
14148
|
+
async getAll() {
|
|
14149
|
+
if (!this.db) await this.initialize();
|
|
14150
|
+
if (!this.db) return [];
|
|
14151
|
+
return new Promise((resolve) => {
|
|
14152
|
+
const req = this.db.transaction([STORE_NAME], "readonly").objectStore(STORE_NAME).getAll();
|
|
14153
|
+
req.onsuccess = () => resolve(req.result || []);
|
|
14154
|
+
req.onerror = () => {
|
|
14155
|
+
logger.warn("[event-store] getAll failed");
|
|
14156
|
+
resolve([]);
|
|
14157
|
+
};
|
|
14158
|
+
});
|
|
14159
|
+
}
|
|
14160
|
+
};
|
|
14161
|
+
const eventStore = new EventStore();
|
|
14162
|
+
//#endregion
|
|
13970
14163
|
//#region utils/bootstrap.ts
|
|
13971
14164
|
/**
|
|
13972
14165
|
* Bootstrap 全球接入入口
|
|
@@ -14356,13 +14549,13 @@ function initializeOtel(version, resourceAttrs) {
|
|
|
14356
14549
|
"region": resourceAttrs.region,
|
|
14357
14550
|
"dsm": resourceAttrs.dsm
|
|
14358
14551
|
}),
|
|
14359
|
-
processors: [new BatchLogRecordProcessor(new ReliableExporter(new OTLPLogExporter({
|
|
14552
|
+
processors: [new BatchLogRecordProcessor(new ReliableExporter(observeExporter(new OTLPLogExporter({
|
|
14360
14553
|
url: OTEL_LOGS_ENDPOINT,
|
|
14361
14554
|
headers: {
|
|
14362
14555
|
"Authorization": buildBasicAuthHeader(),
|
|
14363
14556
|
"stream-name": OTEL_STREAM_NAME
|
|
14364
14557
|
}
|
|
14365
|
-
})))]
|
|
14558
|
+
}), "/v1/logs", OTEL_LOGS_ENDPOINT)))]
|
|
14366
14559
|
});
|
|
14367
14560
|
logs.setGlobalLoggerProvider(loggerProvider);
|
|
14368
14561
|
isInitialized$1 = true;
|
|
@@ -15241,7 +15434,7 @@ async function writeToLocalLog(message, level, extra = {}) {
|
|
|
15241
15434
|
try {
|
|
15242
15435
|
const { shouldPersist, sanitized } = sanitizeForLocalLog(message);
|
|
15243
15436
|
if (!shouldPersist) return;
|
|
15244
|
-
const { AvatarSDK } = await import("./AvatarSDK-
|
|
15437
|
+
const { AvatarSDK } = await import("./AvatarSDK-C_Anluwp.js").then((n) => n.n);
|
|
15245
15438
|
const version = AvatarSDK.version;
|
|
15246
15439
|
const appId = idManager.getAppId();
|
|
15247
15440
|
const userId = idManager.getUserId();
|
|
@@ -15331,4 +15524,4 @@ function captureErrorContext(level, error) {
|
|
|
15331
15524
|
});
|
|
15332
15525
|
}
|
|
15333
15526
|
//#endregion
|
|
15334
|
-
export {
|
|
15527
|
+
export { ContextAPI as $, addHrTimes as A, ATTR_SERVICE_NAME as B, createResource as C, resourceFromAttributes as D, defaultResource as E, millisToHrTime as F, suppressTracing as G, getStringFromEnv as H, otperformance as I, context as J, trace as K, ATTR_EXCEPTION_MESSAGE as L, hrTimeDuration as M, isTimeInput as N, BindOnceFuture as O, isTimeInputHrTime as P, TraceFlags as Q, ATTR_EXCEPTION_STACKTRACE as R, createInstrumentationScope as S, OTLPExporterBase as T, globalErrorHandler as U, getNumberFromEnv as V, isTracingSuppressed as W, isValidTraceId as X, isSpanContextValid as Y, INVALID_SPAN_CONTEXT as Z, cleanupOtelMetrics as _, DEFAULT_REGION as _t, clientContextFields as a, registerGlobal as at, createLegacyOtlpBrowserExportDelegate as b, ErrorCode as bt, logEvent as c, OTEL_PASSWORD as ct, cleanupOtel as d, OTEL_TRACES_STREAM_NAME as dt, createNoopMeter as et, initializeOtel as f, OTEL_USERNAME as ft, observeExporter as g, AvatarError as gt, hostOf as h, generateTraceId as ht, cleanupPostHog as i, getGlobal as it, hrTime as j, ExportResultCode as k, logMetric as l, OTEL_STREAM_NAME as lt, fetchBootstrap as m, idManager as mt, setLogLevel as n, createContextKey as nt, getLogsFeatureFlag as o, unregisterGlobal as ot, clockSync as p, isDebugMode as pt, diag as q, logSink as r, DiagAPI as rt, initializePostHog as s, OTEL_LOGS_ENDPOINT as st, logger as t, ROOT_CONTEXT as tt, updatePostHogPersonPropertiesForFlags as u, OTEL_TRACES_ENDPOINT as ut, initializeOtelMetrics as v, DEFAULT_REGION_REQUEST as vt, toAttributes$1 as w, JSON_ENCODER as x, LogLevel as xt, recordHttpClientDuration as y, DrivingServiceMode as yt, ATTR_EXCEPTION_TYPE as z };
|
|
@@ -95,6 +95,16 @@ export declare class AvatarController {
|
|
|
95
95
|
* frames received). Reset per conversation alongside latencyMarks.
|
|
96
96
|
*/
|
|
97
97
|
private traceActions;
|
|
98
|
+
/** 上行编码分段的接收钩子,构造时装上、dispose 时卸下(见 setOpusEncodeSegmentSink)。 */
|
|
99
|
+
private readonly onEncodeSegment;
|
|
100
|
+
/**
|
|
101
|
+
* 解码 span 的聚合状态:累计满 1 秒 PCM 收一段。
|
|
102
|
+
*
|
|
103
|
+
* 跨 chunk 累计——宿主流式喂时一块可能远不足 1 秒,按块收段会碎成几百个 span。
|
|
104
|
+
* `decodeSpanStart` 是上一段的收尾时刻(null = 用本次 decode 的发起时刻当起点)。
|
|
105
|
+
*/
|
|
106
|
+
private decodeSpanStart;
|
|
107
|
+
private decodeSpanBytes;
|
|
98
108
|
/**
|
|
99
109
|
* Highest Ogg granule forwarded so far this round, for Opus input. The granule
|
|
100
110
|
* accumulates across a stream, so a send's audio seconds is its own end granule
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _t as isDebugMode } from "./logger-BOpQ7u0w.js";
|
|
2
2
|
//#region config/app-config.ts
|
|
3
3
|
var GLOBAL_FLAME_CDN_BASE = "https://cdn.spatialwalk.cloud/public";
|
|
4
4
|
var CN_FLAME_CDN_BASE = "https://cdn.spatialwalk.top/public";
|