@arcware-cloud/pixelstreaming-websdk 1.2.18 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.umd.js CHANGED
@@ -22891,6 +22891,9 @@ const ArcwarePixelStreamingApplicationStyles_1 = __webpack_require__(2299);
22891
22891
  const AudioButton_1 = __webpack_require__(7734);
22892
22892
  const MicButton_1 = __webpack_require__(2293);
22893
22893
  const StopButton_1 = __webpack_require__(9684);
22894
+ const ArcwareEventUtil_1 = __webpack_require__(471);
22895
+ const ArcwareFileTransferUtil_1 = __webpack_require__(2768);
22896
+ const common_1 = __webpack_require__(2483);
22894
22897
  const DEBUG = false;
22895
22898
  const TextKeyRegex = /^(\['([a-zA-Z 0-9-_]+)'\] )/;
22896
22899
  function findElementByTextContent(root, selector) {
@@ -22940,7 +22943,7 @@ const removals = {
22940
22943
  // By default the UI settings should be disabled, since we have a custom method for them.
22941
22944
  "['UI'] section.settingsContainer:not([id])": true,
22942
22945
  // Hide option of start audio muted or not
22943
- "#StartVideoMuted": true,
22946
+ "#StartVideoMuted": true
22944
22947
  };
22945
22948
  if (DEBUG)
22946
22949
  Object.keys(removals).forEach((key) => {
@@ -22966,6 +22969,8 @@ class ArcwareApplication extends lib_pixelstreamingfrontend_ui_ue5_5_1.Applicati
22966
22969
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
22967
22970
  super(options);
22968
22971
  this.responseCallback = null;
22972
+ this.analyticsEventCallback = null;
22973
+ this.fileDownloadCallback = null;
22969
22974
  this.ArcwareSection = this.configUI.buildSectionWithHeading(this.settingsPanel.settingsContentElement, "Arcware Cloud");
22970
22975
  this.stream = options === null || options === void 0 ? void 0 : options.stream;
22971
22976
  this.emitUIInteraction = this.emitUIInteraction.bind(this);
@@ -22978,7 +22983,7 @@ class ArcwareApplication extends lib_pixelstreamingfrontend_ui_ue5_5_1.Applicati
22978
22983
  this.parentElement = (_b = this === null || this === void 0 ? void 0 : this.videoElementParent) === null || _b === void 0 ? void 0 : _b.parentElement;
22979
22984
  this.webRtcController = (_c = this === null || this === void 0 ? void 0 : this.stream) === null || _c === void 0 ? void 0 : _c["_webRtcController"];
22980
22985
  this.addLoveLetterhandler();
22981
- (_d = this === null || this === void 0 ? void 0 : this.stream) === null || _d === void 0 ? void 0 : _d.addResponseEventListener("ue-response", (response) => this === null || this === void 0 ? void 0 : this.applicationResponse(response));
22986
+ (_d = this === null || this === void 0 ? void 0 : this.stream) === null || _d === void 0 ? void 0 : _d.addResponseEventListener("ue-response-internal", (response) => this.applicationResponse(response));
22982
22987
  (_f = (_e = this === null || this === void 0 ? void 0 : this.stream) === null || _e === void 0 ? void 0 : _e.sessionIdHandler) === null || _f === void 0 ? void 0 : _f.add((sessionId) => {
22983
22988
  var _a;
22984
22989
  (_a = this === null || this === void 0 ? void 0 : this.statsPanel) === null || _a === void 0 ? void 0 : _a.addOrUpdateSessionStat("sessionId", "SessionId", sessionId);
@@ -23007,6 +23012,8 @@ class ArcwareApplication extends lib_pixelstreamingfrontend_ui_ue5_5_1.Applicati
23007
23012
  this.uiElementsVisibility(false);
23008
23013
  this.addTextToConnectOverlay();
23009
23014
  this.preventDefaultKeyboardEvents();
23015
+ this.analyticsEventCallback = this.analyticsEvent;
23016
+ this.fileDownloadCallback = this.fileDownload;
23010
23017
  }
23011
23018
  addLoveLetterhandler() {
23012
23019
  var _a, _b;
@@ -23237,8 +23244,26 @@ class ArcwareApplication extends lib_pixelstreamingfrontend_ui_ue5_5_1.Applicati
23237
23244
  });
23238
23245
  }
23239
23246
  applicationResponse(response) {
23240
- if (this === null || this === void 0 ? void 0 : this.responseCallback) {
23241
- this === null || this === void 0 ? void 0 : this.responseCallback(response);
23247
+ var _a, _b;
23248
+ let obj = null;
23249
+ try {
23250
+ obj = (0, common_1.parseUnknownToObject)(response, { allowJsonish: true });
23251
+ }
23252
+ catch (_c) {
23253
+ // If not parseable, fall through to generic callback
23254
+ }
23255
+ const t = (0, common_1.normalizeType)(obj["type"]);
23256
+ // route to exactly one callback
23257
+ if (t === "event") {
23258
+ (_a = this.analyticsEventCallback) === null || _a === void 0 ? void 0 : _a.call(this, response);
23259
+ return;
23260
+ }
23261
+ if (t === "filetransfer" || t === "createscreenshot") {
23262
+ (_b = this.fileDownloadCallback) === null || _b === void 0 ? void 0 : _b.call(this, response);
23263
+ return;
23264
+ }
23265
+ if (this.responseCallback) {
23266
+ this.responseCallback(response);
23242
23267
  }
23243
23268
  }
23244
23269
  applyArcwareStyles() {
@@ -23277,6 +23302,49 @@ class ArcwareApplication extends lib_pixelstreamingfrontend_ui_ue5_5_1.Applicati
23277
23302
  }
23278
23303
  }
23279
23304
  }
23305
+ analyticsEvent(response) {
23306
+ var _a;
23307
+ const event = (0, ArcwareEventUtil_1.toAnalyticsEvent)(response, { maxPayloadBytes: 512, allowJsonish: true });
23308
+ if (event.ok === false) {
23309
+ console.warn("Invalid AnalyticsEvent:", event.error, response);
23310
+ return;
23311
+ }
23312
+ (_a = this.stream) === null || _a === void 0 ? void 0 : _a.send(event.value); // send the sanitized object
23313
+ }
23314
+ fileDownload(response) {
23315
+ var _a;
23316
+ let msg;
23317
+ try {
23318
+ msg = (0, ArcwareFileTransferUtil_1.parseControl)(response, ArcwareFileTransferUtil_1.ZFileTransfer);
23319
+ }
23320
+ catch (e) {
23321
+ console.warn("Invalid control message:", e);
23322
+ return;
23323
+ }
23324
+ const file = (_a = this.webRtcController) === null || _a === void 0 ? void 0 : _a.file;
23325
+ if (!file || !file.data || !file.mimetype)
23326
+ return;
23327
+ const ext = (0, common_1.extFromMime)(file.mimetype);
23328
+ const base = msg.filename && msg.filename.trim() ? (0, common_1.sanitizeFilename)(msg.filename) : (0, common_1.randomHash)();
23329
+ const name = base + ext;
23330
+ try {
23331
+ // ✅ Make safe Blob parts (clone each chunk to ensure regular ArrayBuffer)
23332
+ const chunks = file.data; // <-- remove the generic
23333
+ const safeParts = chunks.map((u8) => u8.slice()); // fresh ArrayBuffers
23334
+ const blob = new Blob(safeParts, { type: file.mimetype });
23335
+ const url = URL.createObjectURL(blob);
23336
+ const a = document.createElement("a");
23337
+ a.href = url;
23338
+ a.download = name;
23339
+ document.body.appendChild(a);
23340
+ a.click();
23341
+ document.body.removeChild(a);
23342
+ setTimeout(() => URL.revokeObjectURL(url), 0);
23343
+ }
23344
+ catch (err) {
23345
+ console.error("Download failed:", err);
23346
+ }
23347
+ }
23280
23348
  }
23281
23349
  ArcwareApplication.Flags = (_a = class {
23282
23350
  },
@@ -23361,7 +23429,7 @@ class ArcwareConfig extends lib_pixelstreamingfrontend_ue5_5_1.Config {
23361
23429
  if (!config.initialSettings.ss)
23362
23430
  config.initialSettings.ss = exports.DefaultUrl;
23363
23431
  super(config);
23364
- this.VERSION = "1.2.18";
23432
+ this.VERSION = "1.3.0";
23365
23433
  this.settings = settings;
23366
23434
  this.session = new Session_1.Session();
23367
23435
  this._initialSettings = config.initialSettings;
@@ -23513,7 +23581,7 @@ const LoveLetters_1 = __webpack_require__(4572);
23513
23581
  const ArcwareLogoLoader_1 = __webpack_require__(6469);
23514
23582
  const MicrophoneOverlay_1 = __webpack_require__(3613);
23515
23583
  const ConnectionIdentifier_1 = __webpack_require__(5999);
23516
- const DiagnosticsCollector_1 = __webpack_require__(6478);
23584
+ const DiagnosticsCollector_1 = __webpack_require__(8429);
23517
23585
  class ArcwarePixelStreaming extends lib_pixelstreamingfrontend_ue5_5_1.PixelStreaming {
23518
23586
  /** Returns a list of WebSocketStates of all PixelStreaming Instances generated. */
23519
23587
  get WebsocketStates() {
@@ -23562,7 +23630,7 @@ class ArcwarePixelStreaming extends lib_pixelstreamingfrontend_ue5_5_1.PixelStre
23562
23630
  this.loveLettersList = [];
23563
23631
  this.microphoneOverlay = new MicrophoneOverlay_1.MicrophoneOverlay(this);
23564
23632
  this.diagnosticsCollector = new DiagnosticsCollector_1.DiagnosticsCollector({
23565
- enableBandwidthProbe: false,
23633
+ enableBandwidthProbe: false
23566
23634
  });
23567
23635
  // this.loveLettersContainer = null;
23568
23636
  this.wrapWebSocketOnCloseHandler();
@@ -23590,7 +23658,7 @@ class ArcwarePixelStreaming extends lib_pixelstreamingfrontend_ue5_5_1.PixelStre
23590
23658
  this.webRtcController.streamMessageController.registerMessageHandler(0, // MessageDirection.ToStreamer,
23591
23659
  "TextboxEntry", (messageData) => {
23592
23660
  try {
23593
- this.webRtcController.sendMessageController.sendMessageToStreamer('TextboxEntry', messageData);
23661
+ this.webRtcController.sendMessageController.sendMessageToStreamer("TextboxEntry", messageData);
23594
23662
  }
23595
23663
  catch (e) {
23596
23664
  console.error(`Error: ToStreamer.TextboxEntry`, e);
@@ -23692,7 +23760,7 @@ class ArcwarePixelStreaming extends lib_pixelstreamingfrontend_ue5_5_1.PixelStre
23692
23760
  EventHandler_1.EventHandler.Emit(this.sessionIdHandler, message.sessionId);
23693
23761
  }
23694
23762
  onVideoInitialized() {
23695
- /** The videoInitialized event is important for the Arcware Cloud BAckend, since without that event the instance will be cleaned up (killed). */
23763
+ /** The videoInitialized event is important for the Arcware Cloud Backend, since without that event the instance will be cleaned up (killed). */
23696
23764
  this.handleMouseLock();
23697
23765
  this.send({ type: "onVideoInitialized" });
23698
23766
  EventHandler_1.EventHandler.Emit(this.videoInitializedHandler, undefined);
@@ -23959,767 +24027,959 @@ exports.ArcwarePixelStreaming = ArcwarePixelStreaming;
23959
24027
 
23960
24028
  /***/ }),
23961
24029
 
23962
- /***/ 6478:
24030
+ /***/ 5602:
23963
24031
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
23964
24032
 
23965
24033
 
23966
- /**
23967
- * DiagnosticsCollector.ts
23968
- * Lightweight, privacy-aware client diagnostics for troubleshooting + analytics.
23969
- * WebRTC collection intentionally omitted (to add later).
23970
- *
23971
- * Notes:
23972
- * - Some fields are best-effort due to browser restrictions; they're nullable.
23973
- * - Bandwidth probe requires a small binary served with:
23974
- * Content-Type: application/octet-stream
23975
- * Content-Encoding: identity
23976
- * Cache-Control: no-store
23977
- * Default location: /diag/probe.bin
23978
- */
23979
24034
  Object.defineProperty(exports, "__esModule", ({ value: true }));
23980
- exports.DiagnosticsCollector = exports.Type = exports.Orientation = void 0;
23981
- const tslib_1 = __webpack_require__(655);
23982
- var Orientation;
23983
- (function (Orientation) {
23984
- Orientation["Landscape"] = "landscape";
23985
- Orientation["Portrait"] = "portrait";
23986
- })(Orientation = exports.Orientation || (exports.Orientation = {}));
23987
- var Type;
23988
- (function (Type) {
23989
- Type["Desktop"] = "desktop";
23990
- Type["Mobile"] = "mobile";
23991
- Type["Tablet"] = "tablet";
23992
- })(Type = exports.Type || (exports.Type = {}));
23993
- /**
23994
- * DiagnosticsCollector
23995
- */
23996
- class DiagnosticsCollector {
23997
- constructor(opts = {}) {
23998
- var _a, _b, _c, _d;
23999
- this.uaInfoCache = null;
24000
- this.opts = {
24001
- enableBandwidthProbe: (_a = opts.enableBandwidthProbe) !== null && _a !== void 0 ? _a : true,
24002
- probeUrl: (_b = opts.probeUrl) !== null && _b !== void 0 ? _b : "/diag/probe.bin",
24003
- respectSaveData: (_c = opts.respectSaveData) !== null && _c !== void 0 ? _c : true,
24004
- sampleRate: Math.max(0, Math.min(1, (_d = opts.sampleRate) !== null && _d !== void 0 ? _d : 1))
24005
- };
24006
- }
24007
- /**
24008
- * Collect the diagnostics payload.
24009
- */
24010
- collect() {
24011
- var _a, _b;
24012
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24013
- if (!this.shouldSample()) {
24014
- return {
24015
- device: { type: Type.Desktop, touchSupport: false },
24016
- runtime: { browser: { family: "Unknown", version: null }, os: { family: "Unknown", version: null } },
24017
- powerHints: { saveData: null, prefersReducedData: null, prefersReducedMotion: null },
24018
- network: { type: null, effectiveType: null, downlinkMbps: null, rttMs: null, measuredDownlinkMbps: null },
24019
- timestamps: { collectedAt: Date.now() }
24020
- };
24021
- }
24022
- const collectedAt = Date.now();
24023
- const [uaInfo, codecSupport, measuredDownlinkMbps] = yield Promise.all([
24024
- this.getUAInfo(),
24025
- this.getCodecSupport(),
24026
- this.maybeMeasureBandwidth()
24027
- ]);
24028
- return {
24029
- device: {
24030
- type: this.inferDeviceType(),
24031
- vendor: null,
24032
- model: uaInfo.model,
24033
- architecture: uaInfo.architecture,
24034
- bitness: uaInfo.bitness,
24035
- touchSupport: this.hasTouch(),
24036
- hardwareConcurrency: (_a = navigator.hardwareConcurrency) !== null && _a !== void 0 ? _a : null,
24037
- deviceMemory: (_b = navigator.deviceMemory) !== null && _b !== void 0 ? _b : null,
24038
- orientation: this.getOrientation(),
24039
- webgl: this.hasWebGL(),
24040
- codecs: codecSupport
24041
- },
24042
- runtime: {
24043
- browser: { family: uaInfo.browserFamily, version: uaInfo.browserVersion },
24044
- os: { family: uaInfo.osFamily, version: uaInfo.osVersion }
24045
- },
24046
- powerHints: this.getPowerHints(),
24047
- network: this.getNetworkInfo(measuredDownlinkMbps),
24048
- timestamps: { collectedAt }
24049
- };
24050
- });
24051
- }
24052
- /**
24053
- * Collect a safe subset of WebRTC stats once PC is connected.
24035
+ exports.ArcwareSettingsSchema = void 0;
24036
+ const zod_1 = __webpack_require__(8754);
24037
+ const shared_pixelstreaming_websdk_1 = __webpack_require__(7910);
24038
+ /** Arcware Settings. */
24039
+ exports.ArcwareSettingsSchema = zod_1.z.object({
24040
+ /** Overwrites the Session-Tool and uses the provided session instead. */
24041
+ session: zod_1.z.string().optional(),
24042
+ /** Can be used to be added to the request in order to verify access to private projects.
24043
+ * For internal use only. => Preview page.
24054
24044
  */
24055
- collectWebRTC(pc) {
24056
- var _a, _b, _c, _d, _e, _f;
24057
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24058
- try {
24059
- const stats = yield pc.getStats();
24060
- let selected, transport, local, remote;
24061
- stats.forEach((r) => {
24062
- if (r.type === "transport" && r.selectedCandidatePairId)
24063
- transport = r;
24064
- if (r.type === "candidate-pair" && (r.selected || r.nominated))
24065
- selected = r;
24066
- });
24067
- if (selected) {
24068
- local = stats.get(selected.localCandidateId);
24069
- remote = stats.get(selected.remoteCandidateId);
24070
- }
24071
- return {
24072
- candidatePairId: (_a = selected === null || selected === void 0 ? void 0 : selected.id) !== null && _a !== void 0 ? _a : null,
24073
- localCandidateType: (_b = local === null || local === void 0 ? void 0 : local.candidateType) !== null && _b !== void 0 ? _b : null,
24074
- remoteCandidateType: (_c = remote === null || remote === void 0 ? void 0 : remote.candidateType) !== null && _c !== void 0 ? _c : null,
24075
- protocol: (_d = local === null || local === void 0 ? void 0 : local.protocol) !== null && _d !== void 0 ? _d : null,
24076
- networkType: (_e = local === null || local === void 0 ? void 0 : local.networkType) !== null && _e !== void 0 ? _e : null,
24077
- dtlsCipher: (_f = transport === null || transport === void 0 ? void 0 : transport.dtlsCipher) !== null && _f !== void 0 ? _f : null
24078
- };
24079
- }
24080
- catch (_g) {
24081
- return {};
24082
- }
24083
- });
24045
+ token: zod_1.z.string().optional(),
24046
+ /** @deprecated in there for legacy use. Can only be used when token is provided. */
24047
+ bypass: zod_1.z.boolean().optional(),
24048
+ // /** Configure DirectFlow Token. */
24049
+ // directFlow: z.string().optional(),
24050
+ /** Handler for server side error messages. */
24051
+ errorHandler: zod_1.z.function().args(shared_pixelstreaming_websdk_1.Messages.ZErrorMessage).returns(zod_1.z.void()).optional(),
24052
+ /** Handler for queue events. */
24053
+ queueHandler: zod_1.z.function().args(shared_pixelstreaming_websdk_1.Messages.ZQueue).returns(zod_1.z.void()).optional(),
24054
+ /** Handler for sessionId message. */
24055
+ sessionIdHandler: zod_1.z.function().args(zod_1.z.string()).returns(zod_1.z.void()).optional(),
24056
+ /** Handler for love letters.
24057
+ * "LoveLetters" are send from backend to the SDK to state what phase the connection currently is in. */
24058
+ loveLetterHandler: zod_1.z.function().args(shared_pixelstreaming_websdk_1.Messages.ZLoveLetter).returns(zod_1.z.void()).optional(),
24059
+ /** Show or hide the fullscreen button. */
24060
+ fullscreenButton: zod_1.z.boolean().optional(),
24061
+ /** Show or hide the settings button. */
24062
+ settingsButton: zod_1.z.boolean().optional(),
24063
+ /** Show or hide the info button. */
24064
+ infoButton: zod_1.z.boolean().optional(),
24065
+ /** Show or hide the audio button. */
24066
+ audioButton: zod_1.z.boolean().optional(),
24067
+ /** Show or hide the microphone button. */
24068
+ micButton: zod_1.z.boolean().optional(),
24069
+ /** Show or hide the microphone button. */
24070
+ stopButton: zod_1.z.boolean().optional(),
24071
+ /** Show or hide the connectionStrengthIcon button. */
24072
+ connectionStrengthIcon: zod_1.z.boolean().optional(),
24073
+ /** ShareId, used for sharing your project.
24074
+ * Using ArcwareInit will set this required property for you. */
24075
+ shareId: zod_1.z.string().startsWith("share-").optional(),
24076
+ /** Id of your project, only required if your shareId refers to multiple projects.
24077
+ * Using ArcwareInit will set this required property for you. */
24078
+ projectId: zod_1.z.string().optional(),
24079
+ /** Enable/Disable LoveLetter logging to the console. */
24080
+ loveLetterLogging: zod_1.z.boolean().optional(),
24081
+ /** Enable/Disable Connection Identifier logging to the console. */
24082
+ connectionIdentifierLoggingDisabled: zod_1.z.boolean().optional(),
24083
+ /** Width with which instance should be started */
24084
+ startWidth: zod_1.z.number().optional(),
24085
+ /** Height with which instance should be started */
24086
+ startHeight: zod_1.z.number().optional()
24087
+ });
24088
+
24089
+
24090
+ /***/ }),
24091
+
24092
+ /***/ 5999:
24093
+ /***/ ((__unused_webpack_module, exports) => {
24094
+
24095
+
24096
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
24097
+ exports.ConnectionIdentifier = exports.WebsocketState = void 0;
24098
+ var WebsocketState;
24099
+ (function (WebsocketState) {
24100
+ // Socket has been created. The connection is not yet open.
24101
+ WebsocketState[WebsocketState["CONNECTING"] = 0] = "CONNECTING";
24102
+ // The connection is open and ready to communicate.
24103
+ WebsocketState[WebsocketState["OPEN"] = 1] = "OPEN";
24104
+ // The connection is in the process of closing.
24105
+ WebsocketState[WebsocketState["CLOSING"] = 2] = "CLOSING";
24106
+ // The connection is closed or couldn't be opened.
24107
+ WebsocketState[WebsocketState["CLOSED"] = 3] = "CLOSED";
24108
+ })(WebsocketState = exports.WebsocketState || (exports.WebsocketState = {}));
24109
+ class ConnectionIdentifier {
24110
+ static get Instance() {
24111
+ if (!this.instance) {
24112
+ this.instance = new ConnectionIdentifier();
24113
+ }
24114
+ return this.instance;
24084
24115
  }
24085
- /**
24086
- * Helper to embed diagnostics in a hello envelope.
24087
- */
24088
- buildHelloEnvelope(base, pc) {
24089
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24090
- const diag = yield this.collect();
24091
- const webrtc = pc ? yield this.collectWebRTC(pc) : undefined;
24092
- const ext = webrtc ? { diag: Object.assign(Object.assign({}, diag), { webrtc }) } : { diag };
24093
- return Object.assign(Object.assign({}, base), { ext });
24094
- });
24116
+ get WebsocketStates() {
24117
+ return this.aps.map((ps) => ps.websocketState);
24095
24118
  }
24096
- // ---------- Internals ----------
24097
- shouldSample() {
24098
- if (this.opts.sampleRate >= 1)
24099
- return true;
24100
- return Math.random() < this.opts.sampleRate;
24119
+ constructor() {
24120
+ this.aps = [];
24121
+ this.enabled = true;
24101
24122
  }
24102
- hasTouch() {
24123
+ register(ps) {
24103
24124
  var _a;
24104
- const nav = navigator;
24105
- return "ontouchstart" in window || ((_a = nav.maxTouchPoints) !== null && _a !== void 0 ? _a : 0) > 0;
24125
+ if (!this.aps.includes(ps)) {
24126
+ this.aps.push(ps);
24127
+ if ((_a = ps.config.settings) === null || _a === void 0 ? void 0 : _a.connectionIdentifierLoggingDisabled)
24128
+ this.disable();
24129
+ }
24130
+ if (!this.interval)
24131
+ this.interval = setInterval(this.connectionWatcher.bind(this), 10 * 1000);
24106
24132
  }
24107
- getOrientation() {
24108
- var _a;
24109
- try {
24110
- if ((_a = screen.orientation) === null || _a === void 0 ? void 0 : _a.type) {
24111
- return screen.orientation.type.startsWith("portrait") ? Orientation.Portrait : Orientation.Landscape;
24112
- }
24113
- if (window.matchMedia) {
24114
- return window.matchMedia("(orientation: portrait)").matches ? Orientation.Portrait : Orientation.Landscape;
24115
- }
24116
- }
24117
- catch (_b) { }
24118
- return null;
24133
+ GetWebSocketStates() {
24134
+ return this.aps.map((ps) => ps.websocketState);
24119
24135
  }
24120
- hasWebGL() {
24121
- try {
24122
- const canvas = document.createElement("canvas");
24123
- return !!(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"));
24124
- }
24125
- catch (_a) {
24126
- return false;
24127
- }
24136
+ ActiveInstances() {
24137
+ return this.GetWebSocketStates().filter((state) => state <= WebsocketState.OPEN).length;
24138
+ }
24139
+ disable() {
24140
+ this.enabled = false;
24128
24141
  }
24129
24142
  /**
24130
- * Heuristic device type: best-effort only.
24131
- * Tries UA-CH, then UA fallback, then touch + screen size heuristics.
24143
+ * The purpose of this method is to periodically tell the developer the amount of connected instances.
24144
+ * This way the developer can figure out, when they messed up the implementation and there's shadow instances still running.
24145
+ * Or when they accidentally set up multiple connections!
24146
+ * Things like that can happen, for example if the React.useEffect was not setup perfectly!
24132
24147
  */
24133
- inferDeviceType() {
24134
- const uaData = navigator.userAgentData;
24135
- const ua = navigator.userAgent || "";
24136
- const isMobileCH = (uaData === null || uaData === void 0 ? void 0 : uaData.mobile) === true;
24137
- const minDim = Math.min(screen.width, screen.height);
24138
- // 1. Explicit iPad / tablet detection via UA (iOS often lies in CH)
24139
- if (/\b(iPad|Tablet)\b/i.test(ua)) {
24140
- return Type.Tablet;
24141
- }
24142
- // 2. UA-CH mobile hint (Chromium only, often accurate for Android)
24143
- if (isMobileCH) {
24144
- if (minDim >= 600)
24145
- return Type.Tablet;
24146
- return Type.Mobile;
24147
- }
24148
- // 3. UA sniffing fallback for mobile
24149
- if (/\b(iPhone|Android.*Mobile|Windows Phone)\b/i.test(ua)) {
24150
- return Type.Mobile;
24148
+ connectionWatcher() {
24149
+ if (!this.enabled)
24150
+ return;
24151
+ const activeStates = this.GetWebSocketStates().filter((state) => state <= WebsocketState.OPEN);
24152
+ if (activeStates.length === 1) {
24153
+ console.log(`PixelStreaming Instance is connected.`);
24151
24154
  }
24152
- // 4. Touch-capable with "tablet-like" dimensions
24153
- if (("ontouchstart" in window || navigator.maxTouchPoints > 0) && minDim >= 600 && minDim <= 1100) {
24154
- return Type.Tablet;
24155
+ else if (activeStates.length !== 0) {
24156
+ console.warn(`${activeStates.length} PixelStreaming Instances are connected!`);
24155
24157
  }
24156
- // 5. Default fallback
24157
- return Type.Desktop;
24158
24158
  }
24159
- getPowerHints() {
24160
- const conn = navigator.connection;
24161
- const saveData = typeof (conn === null || conn === void 0 ? void 0 : conn.saveData) === "boolean" ? conn.saveData : null;
24162
- let prefersReducedData = null;
24163
- let prefersReducedMotion = null;
24164
- if (typeof window.matchMedia === "function") {
24165
- try {
24166
- const mqData = window.matchMedia("(prefers-reduced-data: reduce)");
24167
- if (typeof mqData.matches === "boolean") {
24168
- prefersReducedData = mqData.matches;
24169
- }
24170
- }
24171
- catch (_a) { }
24172
- try {
24173
- const mqMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
24174
- if (typeof mqMotion.matches === "boolean") {
24175
- prefersReducedMotion = mqMotion.matches;
24176
- }
24177
- }
24178
- catch (_b) { }
24179
- }
24180
- return { saveData, prefersReducedData, prefersReducedMotion };
24159
+ }
24160
+ exports.ConnectionIdentifier = ConnectionIdentifier;
24161
+
24162
+
24163
+ /***/ }),
24164
+
24165
+ /***/ 3379:
24166
+ /***/ ((__unused_webpack_module, exports) => {
24167
+
24168
+
24169
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
24170
+ exports.EventHandler = void 0;
24171
+ /** A helper class to spread the event to additional event handlers added from external sources. */
24172
+ class EventHandler {
24173
+ /** Returns the added callback on success or null, if something went wrong. */
24174
+ add(callback) {
24175
+ const cb = this.callbacks.find((cb) => cb === callback);
24176
+ if (cb)
24177
+ return null;
24178
+ this.callbacks.push(callback);
24179
+ return callback;
24181
24180
  }
24182
- getNetworkInfo(measuredDownlinkMbps) {
24183
- const conn = navigator.connection;
24184
- return {
24185
- type: typeof (conn === null || conn === void 0 ? void 0 : conn.type) === "string" ? conn.type : null,
24186
- effectiveType: typeof (conn === null || conn === void 0 ? void 0 : conn.effectiveType) === "string" ? conn.effectiveType : null,
24187
- downlinkMbps: typeof (conn === null || conn === void 0 ? void 0 : conn.downlink) === "number" ? conn.downlink : null,
24188
- rttMs: typeof (conn === null || conn === void 0 ? void 0 : conn.rtt) === "number" ? conn.rtt : null,
24189
- measuredDownlinkMbps
24190
- };
24181
+ /** Returns true if the input callback has been found and removed and false if not. */
24182
+ remove(callback) {
24183
+ const cb = this.callbacks.find((cb) => cb === callback);
24184
+ if (cb)
24185
+ this.callbacks.splice(this.callbacks.indexOf(cb), 1);
24186
+ return !!cb;
24191
24187
  }
24192
- maybeMeasureBandwidth() {
24193
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24194
- const conn = navigator.connection;
24195
- const saveData = (conn === null || conn === void 0 ? void 0 : conn.saveData) === true;
24196
- if (!this.opts.enableBandwidthProbe)
24197
- return null;
24198
- if (this.opts.respectSaveData && saveData)
24199
- return null;
24200
- try {
24201
- const t0 = performance.now();
24202
- const res = yield fetch(this.withCacheBuster(this.opts.probeUrl), {
24203
- cache: "no-store",
24204
- credentials: "omit"
24205
- });
24206
- const buf = yield res.arrayBuffer();
24207
- const t1 = performance.now();
24208
- const bits = buf.byteLength * 8;
24209
- const seconds = (t1 - t0) / 1000;
24210
- if (seconds <= 0.05)
24211
- return null; // ignore unstable samples
24212
- return +(bits / seconds / 1000000).toFixed(2);
24213
- }
24214
- catch (_a) {
24215
- return null;
24216
- }
24217
- });
24188
+ constructor() {
24189
+ this.callbacks = new Array();
24218
24190
  }
24219
- withCacheBuster(url) {
24220
- var _a, _b;
24221
- const u = new URL(url, location.origin);
24222
- const token = (_b = (_a = crypto === null || crypto === void 0 ? void 0 : crypto.randomUUID) === null || _a === void 0 ? void 0 : _a.call(crypto)) !== null && _b !== void 0 ? _b : String(Date.now());
24223
- u.searchParams.set("cb", token);
24224
- return u.toString();
24191
+ /** Emits the event-data for each of the existing handlers. */
24192
+ static Emit(handler, event) {
24193
+ handler.callbacks.forEach((callback) => callback(event));
24225
24194
  }
24226
- getCodecSupport() {
24227
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24228
- const [h264, hevc] = yield Promise.all([
24229
- this.supportsCodec('video/mp4; codecs="avc1.42E01E"'),
24230
- this.supportsCodec('video/mp4; codecs="hvc1.1.6.L93.B0"')
24231
- ]);
24232
- return { h264, hevc };
24233
- });
24195
+ }
24196
+ exports.EventHandler = EventHandler;
24197
+
24198
+
24199
+ /***/ }),
24200
+
24201
+ /***/ 2469:
24202
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24203
+
24204
+
24205
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
24206
+ exports.Session = void 0;
24207
+ const addSeconds_1 = __webpack_require__(1973);
24208
+ const isBefore_1 = __webpack_require__(313);
24209
+ const zod_1 = __webpack_require__(8754);
24210
+ const ZSession = zod_1.z
24211
+ .object({
24212
+ id: zod_1.z.string().min(1),
24213
+ created: zod_1.z
24214
+ .date()
24215
+ .or(zod_1.z.string())
24216
+ .transform((arg) => new Date(arg)),
24217
+ expire: zod_1.z.boolean().default(false).optional(),
24218
+ })
24219
+ .strict();
24220
+ const ZOptions = zod_1.z.object({
24221
+ localStorageKey: zod_1.z.string().min(3).default("pxss"),
24222
+ keepSession: zod_1.z
24223
+ .number()
24224
+ .int()
24225
+ .min(3)
24226
+ /** Default keep is 10, since this is how long instances will be kept alive by default. */
24227
+ .default(10),
24228
+ });
24229
+ /** The sessionId is stored in the localStorage to allow for reconnection and
24230
+ * to prevent spamming start requests of instances, when smashing F5.
24231
+ */
24232
+ class Session {
24233
+ get current() {
24234
+ return this._current;
24234
24235
  }
24235
- supportsCodec(mime) {
24236
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24237
- try {
24238
- if ("mediaCapabilities" in navigator) {
24239
- // @ts-ignore
24240
- const res = yield navigator.mediaCapabilities.decodingInfo({
24241
- type: "file",
24242
- video: { contentType: mime, width: 1920, height: 1080, bitrate: 5000000, framerate: 30 }
24243
- });
24244
- return !!(res === null || res === void 0 ? void 0 : res.supported);
24245
- }
24246
- }
24247
- catch (_a) { }
24248
- try {
24249
- const v = document.createElement("video");
24250
- return v.canPlayType(mime) !== "";
24251
- }
24252
- catch (_b) {
24253
- return false;
24254
- }
24255
- });
24236
+ get noSession() {
24237
+ return new URLSearchParams(window.location.search).has("noSession");
24256
24238
  }
24257
- getUAInfo() {
24258
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
24259
- if (this.uaInfoCache)
24260
- return this.uaInfoCache;
24261
- const nav = navigator;
24262
- const uaData = nav.userAgentData;
24263
- const ua = navigator.userAgent;
24264
- let browserFamily = "Unknown";
24265
- let browserVersion = null;
24266
- let osFamily = "Unknown";
24267
- let osVersion = null;
24268
- let model = null;
24269
- let architecture = null;
24270
- let bitness = null;
24271
- // --- UA-CH (modern browsers) ---
24272
- if (uaData === null || uaData === void 0 ? void 0 : uaData.getHighEntropyValues) {
24273
- try {
24274
- const high = yield uaData.getHighEntropyValues([
24275
- "platform",
24276
- "platformVersion",
24277
- "model",
24278
- "architecture",
24279
- "bitness",
24280
- "fullVersionList"
24281
- ]);
24282
- osFamily = high.platform || "Unknown";
24283
- osVersion = high.platformVersion || null;
24284
- model = high.model || null;
24285
- architecture = high.architecture || null;
24286
- bitness = high.bitness || null;
24287
- const brands = high.fullVersionList || uaData.brands || [];
24288
- const realBrand = brands.find((b) => {
24289
- if (!b.brand)
24290
- return false;
24291
- const normalized = b.brand.replace(/[^a-zA-Z]/g, "").toLowerCase();
24292
- return !normalized.includes("chromium") && !normalized.includes("notabrand");
24293
- });
24294
- if (realBrand) {
24295
- browserFamily = realBrand.brand;
24296
- browserVersion = realBrand.version;
24297
- }
24298
- }
24299
- catch (_a) {
24300
- // silently fail
24301
- }
24302
- }
24303
- // --- Fallback to classic UA parsing if browser unknown ---
24304
- if (!browserFamily || browserFamily === "Unknown") {
24305
- const browserRegexes = [
24306
- [/Edg\/(\S+)/, "Edge"],
24307
- [/OPR\/(\S+)/, "Opera"],
24308
- [/SamsungBrowser\/(\S+)/, "Samsung Internet"],
24309
- [/Firefox\/(\S+)/, "Firefox"],
24310
- [/Chrome\/(\S+)/, "Chrome"],
24311
- [/Version\/(\S+).*Safari/, "Safari"]
24312
- ];
24313
- for (const [regex, name] of browserRegexes) {
24314
- const match = ua.match(regex);
24315
- if (match) {
24316
- browserFamily = name;
24317
- browserVersion = match[1];
24318
- break;
24319
- }
24320
- }
24321
- }
24322
- // --- OS detection (always run, iOS first) ---
24323
- if (/iPhone|iPad|iPod/.test(ua)) {
24324
- osFamily = "iOS";
24325
- const match = ua.match(/OS (\d+[_\d]*)/);
24326
- if (match)
24327
- osVersion = match[1].replace(/_/g, ".");
24328
- }
24329
- else if (uaData === null || uaData === void 0 ? void 0 : uaData.platform) {
24330
- osFamily = uaData.platform;
24331
- }
24332
- else if (/Windows/.test(ua)) {
24333
- osFamily = "Windows";
24334
- const match = ua.match(/Windows NT (\d+\.\d+)/);
24335
- if (match)
24336
- osVersion = match[1];
24337
- }
24338
- else if (/Mac OS X/.test(ua)) {
24339
- osFamily = "macOS";
24340
- const match = ua.match(/Mac OS X (\d+[_\d]*)/);
24341
- if (match)
24342
- osVersion = match[1].replace(/_/g, ".");
24343
- }
24344
- else if (/Android/.test(ua)) {
24345
- osFamily = "Android";
24346
- const match = ua.match(/Android (\d+(\.\d+)?)/);
24347
- if (match)
24348
- osVersion = match[1];
24349
- }
24350
- else if (/Linux/.test(ua)) {
24351
- osFamily = "Linux";
24352
- }
24353
- const result = { browserFamily, browserVersion, osFamily, osVersion, model, architecture, bitness };
24354
- this.uaInfoCache = result;
24355
- return result;
24356
- });
24239
+ get id() {
24240
+ // If the query-parameter "noSession" or "nosession" is set, it will be skipped.
24241
+ if (this.noSession)
24242
+ return null;
24243
+ const sessionString = window.localStorage.getItem(this.localStorageKey);
24244
+ // No session yet set.
24245
+ if (sessionString === null) {
24246
+ return null;
24247
+ }
24248
+ const parsed = ZSession.safeParse(JSON.parse(sessionString));
24249
+ // Clear the session if it's not valid.
24250
+ if (!parsed.success) {
24251
+ this.unset();
24252
+ return null;
24253
+ }
24254
+ const session = parsed.data;
24255
+ const expirationTime = (0, addSeconds_1.default)(new Date(session.created), this.options.keepSession);
24256
+ if ((0, isBefore_1.default)(expirationTime, new Date())) {
24257
+ this.unset();
24258
+ return null;
24259
+ }
24260
+ // Return the sessionId.
24261
+ return session.id;
24262
+ }
24263
+ get localStorageKey() {
24264
+ return this.options.localStorageKey;
24265
+ }
24266
+ constructor(options) {
24267
+ this.options = ZOptions.parse(options || {});
24357
24268
  }
24269
+ /** Set's the session with creation date for the given storage key to the localStorage. */
24270
+ set(sessionId) {
24271
+ ZSession.shape.id.parse(sessionId);
24272
+ this._current = sessionId;
24273
+ const session = {
24274
+ id: sessionId,
24275
+ created: new Date(),
24276
+ expire: false,
24277
+ };
24278
+ window.localStorage.setItem(this.localStorageKey, JSON.stringify(session));
24279
+ }
24280
+ /** Removes a session from the localStorage. */
24281
+ unset() {
24282
+ window.localStorage.removeItem(this.localStorageKey);
24283
+ }
24284
+ }
24285
+ exports.Session = Session;
24286
+
24287
+
24288
+ /***/ }),
24289
+
24290
+ /***/ 9764:
24291
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24292
+
24293
+
24294
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
24295
+ exports.Stats = void 0;
24296
+ const shared_pixelstreaming_websdk_1 = __webpack_require__(7910);
24297
+ function Stats(stats) {
24298
+ const result = shared_pixelstreaming_websdk_1.Messages.ZStats.shape.stats.parse({
24299
+ /**
24300
+ * Other known properties.
24301
+ */
24302
+ codecs: stats.codecs,
24303
+ candidatePair: stats.candidatePairs,
24304
+ localCandidates: stats.localCandidates,
24305
+ remoteCandidates: stats.remoteCandidates,
24306
+ DataChannelStats: stats.datachannelStats,
24307
+ // Received (last) for ticket
24308
+ bytesReceived: forceNumber(stats.inboundVideoStats.bytesReceived),
24309
+ // Packets Lost (last) for ticket
24310
+ packetsLost: forceNumber(stats.inboundVideoStats.packetsLost),
24311
+ // Video resolution: highest lowest average (portrait) and highest lowest average (landscape) for ticket
24312
+ frameWidth: forceNumber(stats.inboundVideoStats.frameWidth),
24313
+ // Video resolution: highest lowest average (portrait) and highest lowest average (landscape) for ticket
24314
+ frameHeight: forceNumber(stats.inboundVideoStats.frameHeight),
24315
+ // Frames decoded (Last) for ticket
24316
+ framesDecoded: forceNumber(stats.inboundVideoStats.framesDecoded),
24317
+ // Framerate (low / high, avg) for ticket
24318
+ framesPerSecond: forceNumber(stats.inboundVideoStats.framesPerSecond),
24319
+ // frames dropped (last) for ticket
24320
+ framesDropped: forceNumber(stats.inboundVideoStats.framesDropped),
24321
+ // Videocodec (once) for ticket
24322
+ videoCodec: stats.inboundVideoStats.codecId,
24323
+ // audiocodec (once) for ticket
24324
+ audioCodec: stats.inboundAudioStats.codecId,
24325
+ // browser type and version for ticket (arcware / addition)
24326
+ browserInfo: {
24327
+ // to be received!
24328
+ userAgent: navigator.userAgent,
24329
+ platform: navigator.oscpu || navigator.platform || null,
24330
+ language: navigator.language,
24331
+ },
24332
+ // Net RTT (low, high, avg)
24333
+ // BEGIN TW CHANGE
24334
+ currentRTT: calcRTT(stats.candidatePairs),
24335
+ // END TW CHANGE
24336
+ // Duration (last)
24337
+ sessionRunTime: stats.sessionStats.runTime,
24338
+ // Controls stream input (??)
24339
+ controlsStreamInput: stats.sessionStats.controlsStreamInput,
24340
+ // video quantization parameter (low / high / avg if applicable)
24341
+ videoEncoderAvgQP: forceNumber(stats.sessionStats.videoEncoderAvgQP),
24342
+ // Video bitrate (min / max / avg)
24343
+ videoBitrate: forceNumber(stats.inboundVideoStats.bitrate),
24344
+ // Audio bitrate (min / max / avg)
24345
+ audioBitrate: forceNumber(stats.inboundAudioStats.bitrate),
24346
+ });
24347
+ return result;
24348
+ }
24349
+ exports.Stats = Stats;
24350
+ /**
24351
+ * Given an array of candidate pairs this function will find the highest RTT for the selected pair
24352
+ * @param pairs - An array of candidate pairs
24353
+ * @returns The highest round trip time of a selected candidate pair
24354
+ */
24355
+ function calcRTT(pairs) {
24356
+ let rtt = 0;
24357
+ pairs.forEach((pair) => {
24358
+ if (pair.selected && pair.currentRoundTripTime > rtt) {
24359
+ rtt = pair.currentRoundTripTime;
24360
+ }
24361
+ });
24362
+ return rtt;
24363
+ }
24364
+ /**
24365
+ * Takes a number and forces it to return a number. If the value passed in is NaN, 0 will be returned.
24366
+ * @param value - A number
24367
+ * @returns The number passed in as value or 0 if vlaue was NaN
24368
+ */
24369
+ function forceNumber(value) {
24370
+ return isNaN(value) ? 0 : value;
24358
24371
  }
24359
- exports.DiagnosticsCollector = DiagnosticsCollector;
24360
24372
 
24361
24373
 
24362
24374
  /***/ }),
24363
24375
 
24364
- /***/ 5602:
24376
+ /***/ 9580:
24377
+ /***/ ((__unused_webpack_module, exports) => {
24378
+
24379
+
24380
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
24381
+ function debounce(func, wait) {
24382
+ let timeout;
24383
+ return function (...args) {
24384
+ clearTimeout(timeout);
24385
+ timeout = window.setTimeout(() => func(...args), wait);
24386
+ };
24387
+ }
24388
+ exports["default"] = debounce;
24389
+
24390
+
24391
+ /***/ }),
24392
+
24393
+ /***/ 471:
24365
24394
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24366
24395
 
24367
24396
 
24368
24397
  Object.defineProperty(exports, "__esModule", ({ value: true }));
24369
- exports.ArcwareSettingsSchema = void 0;
24398
+ exports.isAnalyticsEvent = exports.toAnalyticsEvent = void 0;
24399
+ // analytics.ts
24400
+ const common_1 = __webpack_require__(2483);
24401
+ /**
24402
+ * Build an AnalyticsEvent from unknown input.
24403
+ * - accepts object or JSON/JSON-ish string
24404
+ * - drops extra fields
24405
+ * - requires non-empty string `type` and `customName`
24406
+ * - accepts `payload` only if it's a string; truncates by **bytes**
24407
+ */
24408
+ function toAnalyticsEvent(input, opts = {}) {
24409
+ var _a, _b, _c;
24410
+ const maxBytes = (_a = opts.maxPayloadBytes) !== null && _a !== void 0 ? _a : 512;
24411
+ let obj;
24412
+ try {
24413
+ obj = (0, common_1.parseUnknownToObject)(input, { allowJsonish: (_b = opts.allowJsonish) !== null && _b !== void 0 ? _b : true });
24414
+ }
24415
+ catch (e) {
24416
+ return { ok: false, error: (_c = e === null || e === void 0 ? void 0 : e.message) !== null && _c !== void 0 ? _c : "Invalid input" };
24417
+ }
24418
+ const typeVal = safeString(obj["type"]);
24419
+ const customNameVal = safeString(obj["customName"]);
24420
+ if (!typeVal)
24421
+ return { ok: false, error: "`type` must be a non-empty string." };
24422
+ if (!customNameVal)
24423
+ return { ok: false, error: "`customName` must be a non-empty string." };
24424
+ let payloadStr;
24425
+ if ("payload" in obj && typeof obj["payload"] === "string") {
24426
+ payloadStr = (0, common_1.truncateByBytes)(obj["payload"], maxBytes);
24427
+ }
24428
+ const value = payloadStr !== undefined
24429
+ ? { type: typeVal, customName: customNameVal, payload: payloadStr }
24430
+ : { type: typeVal, customName: customNameVal };
24431
+ return { ok: true, value };
24432
+ }
24433
+ exports.toAnalyticsEvent = toAnalyticsEvent;
24434
+ /** Type guard */
24435
+ function isAnalyticsEvent(x) {
24436
+ return (!!x &&
24437
+ typeof x === "object" &&
24438
+ typeof x.type === "string" &&
24439
+ typeof x.customName === "string" &&
24440
+ (typeof x.payload === "string" || x.payload === undefined));
24441
+ }
24442
+ exports.isAnalyticsEvent = isAnalyticsEvent;
24443
+ function safeString(x) {
24444
+ if (typeof x !== "string")
24445
+ return undefined;
24446
+ const trimmed = x.trim();
24447
+ return trimmed.length ? trimmed : undefined;
24448
+ }
24449
+
24450
+
24451
+ /***/ }),
24452
+
24453
+ /***/ 2768:
24454
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24455
+
24456
+
24457
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
24458
+ exports.parseControl = exports.ZFileTransfer = void 0;
24459
+ // fileTransfer.ts
24370
24460
  const zod_1 = __webpack_require__(8754);
24371
- const shared_pixelstreaming_websdk_1 = __webpack_require__(7910);
24372
- /** Arcware Settings. */
24373
- exports.ArcwareSettingsSchema = zod_1.z.object({
24374
- /** Overwrites the Session-Tool and uses the provided session instead. */
24375
- session: zod_1.z.string().optional(),
24376
- /** Can be used to be added to the request in order to verify access to private projects.
24377
- * For internal use only. => Preview page.
24378
- */
24379
- token: zod_1.z.string().optional(),
24380
- /** @deprecated in there for legacy use. Can only be used when token is provided. */
24381
- bypass: zod_1.z.boolean().optional(),
24382
- // /** Configure DirectFlow Token. */
24383
- // directFlow: z.string().optional(),
24384
- /** Handler for server side error messages. */
24385
- errorHandler: zod_1.z.function().args(shared_pixelstreaming_websdk_1.Messages.ZErrorMessage).returns(zod_1.z.void()).optional(),
24386
- /** Handler for queue events. */
24387
- queueHandler: zod_1.z.function().args(shared_pixelstreaming_websdk_1.Messages.ZQueue).returns(zod_1.z.void()).optional(),
24388
- /** Handler for sessionId message. */
24389
- sessionIdHandler: zod_1.z.function().args(zod_1.z.string()).returns(zod_1.z.void()).optional(),
24390
- /** Handler for love letters.
24391
- * "LoveLetters" are send from backend to the SDK to state what phase the connection currently is in. */
24392
- loveLetterHandler: zod_1.z.function().args(shared_pixelstreaming_websdk_1.Messages.ZLoveLetter).returns(zod_1.z.void()).optional(),
24393
- /** Show or hide the fullscreen button. */
24394
- fullscreenButton: zod_1.z.boolean().optional(),
24395
- /** Show or hide the settings button. */
24396
- settingsButton: zod_1.z.boolean().optional(),
24397
- /** Show or hide the info button. */
24398
- infoButton: zod_1.z.boolean().optional(),
24399
- /** Show or hide the audio button. */
24400
- audioButton: zod_1.z.boolean().optional(),
24401
- /** Show or hide the microphone button. */
24402
- micButton: zod_1.z.boolean().optional(),
24403
- /** Show or hide the microphone button. */
24404
- stopButton: zod_1.z.boolean().optional(),
24405
- /** Show or hide the connectionStrengthIcon button. */
24406
- connectionStrengthIcon: zod_1.z.boolean().optional(),
24407
- /** ShareId, used for sharing your project.
24408
- * Using ArcwareInit will set this required property for you. */
24409
- shareId: zod_1.z.string().startsWith("share-").optional(),
24410
- /** Id of your project, only required if your shareId refers to multiple projects.
24411
- * Using ArcwareInit will set this required property for you. */
24412
- projectId: zod_1.z.string().optional(),
24413
- /** Enable/Disable LoveLetter logging to the console. */
24414
- loveLetterLogging: zod_1.z.boolean().optional(),
24415
- /** Enable/Disable Connection Identifier logging to the console. */
24416
- connectionIdentifierLoggingDisabled: zod_1.z.boolean().optional(),
24417
- /** Width with which instance should be started */
24418
- startWidth: zod_1.z.number().optional(),
24419
- /** Height with which instance should be started */
24420
- startHeight: zod_1.z.number().optional()
24461
+ const common_1 = __webpack_require__(2483);
24462
+ // Schema
24463
+ exports.ZFileTransfer = zod_1.z.object({
24464
+ type: zod_1.z.literal("fileTransfer"),
24465
+ filename: zod_1.z.string().trim().optional()
24421
24466
  });
24467
+ // Normalized parse + validate
24468
+ function parseControl(input, schema) {
24469
+ const obj = (0, common_1.parseUnknownToObject)(input, { allowJsonish: true });
24470
+ return schema.parse(obj);
24471
+ }
24472
+ exports.parseControl = parseControl;
24422
24473
 
24423
24474
 
24424
24475
  /***/ }),
24425
24476
 
24426
- /***/ 5999:
24427
- /***/ ((__unused_webpack_module, exports) => {
24477
+ /***/ 8429:
24478
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24428
24479
 
24429
24480
 
24481
+ /**
24482
+ * DiagnosticsCollector.ts
24483
+ * Lightweight, privacy-aware client diagnostics for troubleshooting + analytics.
24484
+ * WebRTC collection intentionally omitted (to add later).
24485
+ *
24486
+ * Notes:
24487
+ * - Some fields are best-effort due to browser restrictions; they're nullable.
24488
+ * - Bandwidth probe requires a small binary served with:
24489
+ * Content-Type: application/octet-stream
24490
+ * Content-Encoding: identity
24491
+ * Cache-Control: no-store
24492
+ * Default location: /diag/probe.bin
24493
+ */
24430
24494
  Object.defineProperty(exports, "__esModule", ({ value: true }));
24431
- exports.ConnectionIdentifier = exports.WebsocketState = void 0;
24432
- var WebsocketState;
24433
- (function (WebsocketState) {
24434
- // Socket has been created. The connection is not yet open.
24435
- WebsocketState[WebsocketState["CONNECTING"] = 0] = "CONNECTING";
24436
- // The connection is open and ready to communicate.
24437
- WebsocketState[WebsocketState["OPEN"] = 1] = "OPEN";
24438
- // The connection is in the process of closing.
24439
- WebsocketState[WebsocketState["CLOSING"] = 2] = "CLOSING";
24440
- // The connection is closed or couldn't be opened.
24441
- WebsocketState[WebsocketState["CLOSED"] = 3] = "CLOSED";
24442
- })(WebsocketState = exports.WebsocketState || (exports.WebsocketState = {}));
24443
- class ConnectionIdentifier {
24444
- static get Instance() {
24445
- if (!this.instance) {
24446
- this.instance = new ConnectionIdentifier();
24447
- }
24448
- return this.instance;
24495
+ exports.DiagnosticsCollector = exports.Type = exports.Orientation = void 0;
24496
+ const tslib_1 = __webpack_require__(655);
24497
+ var Orientation;
24498
+ (function (Orientation) {
24499
+ Orientation["Landscape"] = "landscape";
24500
+ Orientation["Portrait"] = "portrait";
24501
+ })(Orientation = exports.Orientation || (exports.Orientation = {}));
24502
+ var Type;
24503
+ (function (Type) {
24504
+ Type["Desktop"] = "desktop";
24505
+ Type["Mobile"] = "mobile";
24506
+ Type["Tablet"] = "tablet";
24507
+ })(Type = exports.Type || (exports.Type = {}));
24508
+ /**
24509
+ * DiagnosticsCollector
24510
+ */
24511
+ class DiagnosticsCollector {
24512
+ constructor(opts = {}) {
24513
+ var _a, _b, _c, _d;
24514
+ this.uaInfoCache = null;
24515
+ this.opts = {
24516
+ enableBandwidthProbe: (_a = opts.enableBandwidthProbe) !== null && _a !== void 0 ? _a : true,
24517
+ probeUrl: (_b = opts.probeUrl) !== null && _b !== void 0 ? _b : "/diag/probe.bin",
24518
+ respectSaveData: (_c = opts.respectSaveData) !== null && _c !== void 0 ? _c : true,
24519
+ sampleRate: Math.max(0, Math.min(1, (_d = opts.sampleRate) !== null && _d !== void 0 ? _d : 1))
24520
+ };
24521
+ }
24522
+ /**
24523
+ * Collect the diagnostics payload.
24524
+ */
24525
+ collect() {
24526
+ var _a, _b;
24527
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24528
+ if (!this.shouldSample()) {
24529
+ return {
24530
+ device: { type: Type.Desktop, touchSupport: false },
24531
+ runtime: { browser: { family: "Unknown", version: null }, os: { family: "Unknown", version: null } },
24532
+ powerHints: { saveData: null, prefersReducedData: null, prefersReducedMotion: null },
24533
+ network: { type: null, effectiveType: null, downlinkMbps: null, rttMs: null, measuredDownlinkMbps: null },
24534
+ timestamps: { collectedAt: Date.now() }
24535
+ };
24536
+ }
24537
+ const collectedAt = Date.now();
24538
+ const [uaInfo, codecSupport, measuredDownlinkMbps] = yield Promise.all([
24539
+ this.getUAInfo(),
24540
+ this.getCodecSupport(),
24541
+ this.maybeMeasureBandwidth()
24542
+ ]);
24543
+ return {
24544
+ device: {
24545
+ type: this.inferDeviceType(),
24546
+ vendor: null,
24547
+ model: uaInfo.model,
24548
+ architecture: uaInfo.architecture,
24549
+ bitness: uaInfo.bitness,
24550
+ touchSupport: this.hasTouch(),
24551
+ hardwareConcurrency: (_a = navigator.hardwareConcurrency) !== null && _a !== void 0 ? _a : null,
24552
+ deviceMemory: (_b = navigator.deviceMemory) !== null && _b !== void 0 ? _b : null,
24553
+ orientation: this.getOrientation(),
24554
+ webgl: this.hasWebGL(),
24555
+ codecs: codecSupport
24556
+ },
24557
+ runtime: {
24558
+ browser: { family: uaInfo.browserFamily, version: uaInfo.browserVersion },
24559
+ os: { family: uaInfo.osFamily, version: uaInfo.osVersion }
24560
+ },
24561
+ powerHints: this.getPowerHints(),
24562
+ network: this.getNetworkInfo(measuredDownlinkMbps),
24563
+ timestamps: { collectedAt }
24564
+ };
24565
+ });
24566
+ }
24567
+ /**
24568
+ * Collect a safe subset of WebRTC stats once PC is connected.
24569
+ */
24570
+ collectWebRTC(pc) {
24571
+ var _a, _b, _c, _d, _e, _f;
24572
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24573
+ try {
24574
+ const stats = yield pc.getStats();
24575
+ let selected, transport, local, remote;
24576
+ stats.forEach((r) => {
24577
+ if (r.type === "transport" && r.selectedCandidatePairId)
24578
+ transport = r;
24579
+ if (r.type === "candidate-pair" && (r.selected || r.nominated))
24580
+ selected = r;
24581
+ });
24582
+ if (selected) {
24583
+ local = stats.get(selected.localCandidateId);
24584
+ remote = stats.get(selected.remoteCandidateId);
24585
+ }
24586
+ return {
24587
+ candidatePairId: (_a = selected === null || selected === void 0 ? void 0 : selected.id) !== null && _a !== void 0 ? _a : null,
24588
+ localCandidateType: (_b = local === null || local === void 0 ? void 0 : local.candidateType) !== null && _b !== void 0 ? _b : null,
24589
+ remoteCandidateType: (_c = remote === null || remote === void 0 ? void 0 : remote.candidateType) !== null && _c !== void 0 ? _c : null,
24590
+ protocol: (_d = local === null || local === void 0 ? void 0 : local.protocol) !== null && _d !== void 0 ? _d : null,
24591
+ networkType: (_e = local === null || local === void 0 ? void 0 : local.networkType) !== null && _e !== void 0 ? _e : null,
24592
+ dtlsCipher: (_f = transport === null || transport === void 0 ? void 0 : transport.dtlsCipher) !== null && _f !== void 0 ? _f : null
24593
+ };
24594
+ }
24595
+ catch (_g) {
24596
+ return {};
24597
+ }
24598
+ });
24449
24599
  }
24450
- get WebsocketStates() {
24451
- return this.aps.map((ps) => ps.websocketState);
24600
+ /**
24601
+ * Helper to embed diagnostics in a hello envelope.
24602
+ */
24603
+ buildHelloEnvelope(base, pc) {
24604
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24605
+ const diag = yield this.collect();
24606
+ const webrtc = pc ? yield this.collectWebRTC(pc) : undefined;
24607
+ const ext = webrtc ? { diag: Object.assign(Object.assign({}, diag), { webrtc }) } : { diag };
24608
+ return Object.assign(Object.assign({}, base), { ext });
24609
+ });
24452
24610
  }
24453
- constructor() {
24454
- this.aps = [];
24455
- this.enabled = true;
24611
+ // ---------- Internals ----------
24612
+ shouldSample() {
24613
+ if (this.opts.sampleRate >= 1)
24614
+ return true;
24615
+ return Math.random() < this.opts.sampleRate;
24456
24616
  }
24457
- register(ps) {
24617
+ hasTouch() {
24458
24618
  var _a;
24459
- if (!this.aps.includes(ps)) {
24460
- this.aps.push(ps);
24461
- if ((_a = ps.config.settings) === null || _a === void 0 ? void 0 : _a.connectionIdentifierLoggingDisabled)
24462
- this.disable();
24463
- }
24464
- if (!this.interval)
24465
- this.interval = setInterval(this.connectionWatcher.bind(this), 10 * 1000);
24466
- }
24467
- GetWebSocketStates() {
24468
- return this.aps.map((ps) => ps.websocketState);
24619
+ const nav = navigator;
24620
+ return "ontouchstart" in window || ((_a = nav.maxTouchPoints) !== null && _a !== void 0 ? _a : 0) > 0;
24469
24621
  }
24470
- ActiveInstances() {
24471
- return this.GetWebSocketStates().filter((state) => state <= WebsocketState.OPEN).length;
24622
+ getOrientation() {
24623
+ var _a;
24624
+ try {
24625
+ if ((_a = screen.orientation) === null || _a === void 0 ? void 0 : _a.type) {
24626
+ return screen.orientation.type.startsWith("portrait") ? Orientation.Portrait : Orientation.Landscape;
24627
+ }
24628
+ if (window.matchMedia) {
24629
+ return window.matchMedia("(orientation: portrait)").matches ? Orientation.Portrait : Orientation.Landscape;
24630
+ }
24631
+ }
24632
+ catch (_b) { }
24633
+ return null;
24472
24634
  }
24473
- disable() {
24474
- this.enabled = false;
24635
+ hasWebGL() {
24636
+ try {
24637
+ const canvas = document.createElement("canvas");
24638
+ return !!(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"));
24639
+ }
24640
+ catch (_a) {
24641
+ return false;
24642
+ }
24475
24643
  }
24476
24644
  /**
24477
- * The purpose of this method is to periodically tell the developer the amount of connected instances.
24478
- * This way the developer can figure out, when they messed up the implementation and there's shadow instances still running.
24479
- * Or when they accidentally set up multiple connections!
24480
- * Things like that can happen, for example if the React.useEffect was not setup perfectly!
24645
+ * Heuristic device type: best-effort only.
24646
+ * Tries UA-CH, then UA fallback, then touch + screen size heuristics.
24481
24647
  */
24482
- connectionWatcher() {
24483
- if (!this.enabled)
24484
- return;
24485
- const activeStates = this.GetWebSocketStates().filter((state) => state <= WebsocketState.OPEN);
24486
- if (activeStates.length === 1) {
24487
- console.log(`PixelStreaming Instance is connected.`);
24648
+ inferDeviceType() {
24649
+ const uaData = navigator.userAgentData;
24650
+ const ua = navigator.userAgent || "";
24651
+ const isMobileCH = (uaData === null || uaData === void 0 ? void 0 : uaData.mobile) === true;
24652
+ const minDim = Math.min(screen.width, screen.height);
24653
+ // 1. Explicit iPad / tablet detection via UA (iOS often lies in CH)
24654
+ if (/\b(iPad|Tablet)\b/i.test(ua)) {
24655
+ return Type.Tablet;
24488
24656
  }
24489
- else if (activeStates.length !== 0) {
24490
- console.warn(`${activeStates.length} PixelStreaming Instances are connected!`);
24657
+ // 2. UA-CH mobile hint (Chromium only, often accurate for Android)
24658
+ if (isMobileCH) {
24659
+ if (minDim >= 600)
24660
+ return Type.Tablet;
24661
+ return Type.Mobile;
24491
24662
  }
24492
- }
24493
- }
24494
- exports.ConnectionIdentifier = ConnectionIdentifier;
24495
-
24496
-
24497
- /***/ }),
24498
-
24499
- /***/ 3379:
24500
- /***/ ((__unused_webpack_module, exports) => {
24501
-
24502
-
24503
- Object.defineProperty(exports, "__esModule", ({ value: true }));
24504
- exports.EventHandler = void 0;
24505
- /** A helper class to spread the event to additional event handlers added from external sources. */
24506
- class EventHandler {
24507
- /** Returns the added callback on success or null, if something went wrong. */
24508
- add(callback) {
24509
- const cb = this.callbacks.find((cb) => cb === callback);
24510
- if (cb)
24511
- return null;
24512
- this.callbacks.push(callback);
24513
- return callback;
24514
- }
24515
- /** Returns true if the input callback has been found and removed and false if not. */
24516
- remove(callback) {
24517
- const cb = this.callbacks.find((cb) => cb === callback);
24518
- if (cb)
24519
- this.callbacks.splice(this.callbacks.indexOf(cb), 1);
24520
- return !!cb;
24521
- }
24522
- constructor() {
24523
- this.callbacks = new Array();
24524
- }
24525
- /** Emits the event-data for each of the existing handlers. */
24526
- static Emit(handler, event) {
24527
- handler.callbacks.forEach((callback) => callback(event));
24528
- }
24529
- }
24530
- exports.EventHandler = EventHandler;
24531
-
24532
-
24533
- /***/ }),
24534
-
24535
- /***/ 2469:
24536
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24537
-
24538
-
24539
- Object.defineProperty(exports, "__esModule", ({ value: true }));
24540
- exports.Session = void 0;
24541
- const addSeconds_1 = __webpack_require__(1973);
24542
- const isBefore_1 = __webpack_require__(313);
24543
- const zod_1 = __webpack_require__(8754);
24544
- const ZSession = zod_1.z
24545
- .object({
24546
- id: zod_1.z.string().min(1),
24547
- created: zod_1.z
24548
- .date()
24549
- .or(zod_1.z.string())
24550
- .transform((arg) => new Date(arg)),
24551
- expire: zod_1.z.boolean().default(false).optional(),
24552
- })
24553
- .strict();
24554
- const ZOptions = zod_1.z.object({
24555
- localStorageKey: zod_1.z.string().min(3).default("pxss"),
24556
- keepSession: zod_1.z
24557
- .number()
24558
- .int()
24559
- .min(3)
24560
- /** Default keep is 10, since this is how long instances will be kept alive by default. */
24561
- .default(10),
24562
- });
24563
- /** The sessionId is stored in the localStorage to allow for reconnection and
24564
- * to prevent spamming start requests of instances, when smashing F5.
24565
- */
24566
- class Session {
24567
- get current() {
24568
- return this._current;
24569
- }
24570
- get noSession() {
24571
- return new URLSearchParams(window.location.search).has("noSession");
24572
- }
24573
- get id() {
24574
- // If the query-parameter "noSession" or "nosession" is set, it will be skipped.
24575
- if (this.noSession)
24576
- return null;
24577
- const sessionString = window.localStorage.getItem(this.localStorageKey);
24578
- // No session yet set.
24579
- if (sessionString === null) {
24580
- return null;
24663
+ // 3. UA sniffing fallback for mobile
24664
+ if (/\b(iPhone|Android.*Mobile|Windows Phone)\b/i.test(ua)) {
24665
+ return Type.Mobile;
24581
24666
  }
24582
- const parsed = ZSession.safeParse(JSON.parse(sessionString));
24583
- // Clear the session if it's not valid.
24584
- if (!parsed.success) {
24585
- this.unset();
24586
- return null;
24667
+ // 4. Touch-capable with "tablet-like" dimensions
24668
+ if (("ontouchstart" in window || navigator.maxTouchPoints > 0) && minDim >= 600 && minDim <= 1100) {
24669
+ return Type.Tablet;
24587
24670
  }
24588
- const session = parsed.data;
24589
- const expirationTime = (0, addSeconds_1.default)(new Date(session.created), this.options.keepSession);
24590
- if ((0, isBefore_1.default)(expirationTime, new Date())) {
24591
- this.unset();
24592
- return null;
24671
+ // 5. Default fallback
24672
+ return Type.Desktop;
24673
+ }
24674
+ getPowerHints() {
24675
+ const conn = navigator.connection;
24676
+ const saveData = typeof (conn === null || conn === void 0 ? void 0 : conn.saveData) === "boolean" ? conn.saveData : null;
24677
+ let prefersReducedData = null;
24678
+ let prefersReducedMotion = null;
24679
+ if (typeof window.matchMedia === "function") {
24680
+ try {
24681
+ const mqData = window.matchMedia("(prefers-reduced-data: reduce)");
24682
+ if (typeof mqData.matches === "boolean") {
24683
+ prefersReducedData = mqData.matches;
24684
+ }
24685
+ }
24686
+ catch (_a) { }
24687
+ try {
24688
+ const mqMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
24689
+ if (typeof mqMotion.matches === "boolean") {
24690
+ prefersReducedMotion = mqMotion.matches;
24691
+ }
24692
+ }
24693
+ catch (_b) { }
24593
24694
  }
24594
- // Return the sessionId.
24595
- return session.id;
24695
+ return { saveData, prefersReducedData, prefersReducedMotion };
24696
+ }
24697
+ getNetworkInfo(measuredDownlinkMbps) {
24698
+ const conn = navigator.connection;
24699
+ return {
24700
+ type: typeof (conn === null || conn === void 0 ? void 0 : conn.type) === "string" ? conn.type : null,
24701
+ effectiveType: typeof (conn === null || conn === void 0 ? void 0 : conn.effectiveType) === "string" ? conn.effectiveType : null,
24702
+ downlinkMbps: typeof (conn === null || conn === void 0 ? void 0 : conn.downlink) === "number" ? conn.downlink : null,
24703
+ rttMs: typeof (conn === null || conn === void 0 ? void 0 : conn.rtt) === "number" ? conn.rtt : null,
24704
+ measuredDownlinkMbps
24705
+ };
24706
+ }
24707
+ maybeMeasureBandwidth() {
24708
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24709
+ const conn = navigator.connection;
24710
+ const saveData = (conn === null || conn === void 0 ? void 0 : conn.saveData) === true;
24711
+ if (!this.opts.enableBandwidthProbe)
24712
+ return null;
24713
+ if (this.opts.respectSaveData && saveData)
24714
+ return null;
24715
+ try {
24716
+ const t0 = performance.now();
24717
+ const res = yield fetch(this.withCacheBuster(this.opts.probeUrl), {
24718
+ cache: "no-store",
24719
+ credentials: "omit"
24720
+ });
24721
+ const buf = yield res.arrayBuffer();
24722
+ const t1 = performance.now();
24723
+ const bits = buf.byteLength * 8;
24724
+ const seconds = (t1 - t0) / 1000;
24725
+ if (seconds <= 0.05)
24726
+ return null; // ignore unstable samples
24727
+ return +(bits / seconds / 1000000).toFixed(2);
24728
+ }
24729
+ catch (_a) {
24730
+ return null;
24731
+ }
24732
+ });
24596
24733
  }
24597
- get localStorageKey() {
24598
- return this.options.localStorageKey;
24734
+ withCacheBuster(url) {
24735
+ var _a, _b;
24736
+ const u = new URL(url, location.origin);
24737
+ const token = (_b = (_a = crypto === null || crypto === void 0 ? void 0 : crypto.randomUUID) === null || _a === void 0 ? void 0 : _a.call(crypto)) !== null && _b !== void 0 ? _b : String(Date.now());
24738
+ u.searchParams.set("cb", token);
24739
+ return u.toString();
24599
24740
  }
24600
- constructor(options) {
24601
- this.options = ZOptions.parse(options || {});
24741
+ getCodecSupport() {
24742
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24743
+ const [h264, hevc] = yield Promise.all([
24744
+ this.supportsCodec('video/mp4; codecs="avc1.42E01E"'),
24745
+ this.supportsCodec('video/mp4; codecs="hvc1.1.6.L93.B0"')
24746
+ ]);
24747
+ return { h264, hevc };
24748
+ });
24602
24749
  }
24603
- /** Set's the session with creation date for the given storage key to the localStorage. */
24604
- set(sessionId) {
24605
- ZSession.shape.id.parse(sessionId);
24606
- this._current = sessionId;
24607
- const session = {
24608
- id: sessionId,
24609
- created: new Date(),
24610
- expire: false,
24611
- };
24612
- window.localStorage.setItem(this.localStorageKey, JSON.stringify(session));
24750
+ supportsCodec(mime) {
24751
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24752
+ try {
24753
+ if ("mediaCapabilities" in navigator) {
24754
+ // @ts-ignore
24755
+ const res = yield navigator.mediaCapabilities.decodingInfo({
24756
+ type: "file",
24757
+ video: { contentType: mime, width: 1920, height: 1080, bitrate: 5000000, framerate: 30 }
24758
+ });
24759
+ return !!(res === null || res === void 0 ? void 0 : res.supported);
24760
+ }
24761
+ }
24762
+ catch (_a) { }
24763
+ try {
24764
+ const v = document.createElement("video");
24765
+ return v.canPlayType(mime) !== "";
24766
+ }
24767
+ catch (_b) {
24768
+ return false;
24769
+ }
24770
+ });
24613
24771
  }
24614
- /** Removes a session from the localStorage. */
24615
- unset() {
24616
- window.localStorage.removeItem(this.localStorageKey);
24772
+ getUAInfo() {
24773
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
24774
+ if (this.uaInfoCache)
24775
+ return this.uaInfoCache;
24776
+ const nav = navigator;
24777
+ const uaData = nav.userAgentData;
24778
+ const ua = navigator.userAgent;
24779
+ let browserFamily = "Unknown";
24780
+ let browserVersion = null;
24781
+ let osFamily = "Unknown";
24782
+ let osVersion = null;
24783
+ let model = null;
24784
+ let architecture = null;
24785
+ let bitness = null;
24786
+ // --- UA-CH (modern browsers) ---
24787
+ if (uaData === null || uaData === void 0 ? void 0 : uaData.getHighEntropyValues) {
24788
+ try {
24789
+ const high = yield uaData.getHighEntropyValues([
24790
+ "platform",
24791
+ "platformVersion",
24792
+ "model",
24793
+ "architecture",
24794
+ "bitness",
24795
+ "fullVersionList"
24796
+ ]);
24797
+ osFamily = high.platform || "Unknown";
24798
+ osVersion = high.platformVersion || null;
24799
+ model = high.model || null;
24800
+ architecture = high.architecture || null;
24801
+ bitness = high.bitness || null;
24802
+ const brands = high.fullVersionList || uaData.brands || [];
24803
+ const realBrand = brands.find((b) => {
24804
+ if (!b.brand)
24805
+ return false;
24806
+ const normalized = b.brand.replace(/[^a-zA-Z]/g, "").toLowerCase();
24807
+ return !normalized.includes("chromium") && !normalized.includes("notabrand");
24808
+ });
24809
+ if (realBrand) {
24810
+ browserFamily = realBrand.brand;
24811
+ browserVersion = realBrand.version;
24812
+ }
24813
+ }
24814
+ catch (_a) {
24815
+ // silently fail
24816
+ }
24817
+ }
24818
+ // --- Fallback to classic UA parsing if browser unknown ---
24819
+ if (!browserFamily || browserFamily === "Unknown") {
24820
+ const browserRegexes = [
24821
+ [/Edg\/(\S+)/, "Edge"],
24822
+ [/OPR\/(\S+)/, "Opera"],
24823
+ [/SamsungBrowser\/(\S+)/, "Samsung Internet"],
24824
+ [/Firefox\/(\S+)/, "Firefox"],
24825
+ [/Chrome\/(\S+)/, "Chrome"],
24826
+ [/Version\/(\S+).*Safari/, "Safari"]
24827
+ ];
24828
+ for (const [regex, name] of browserRegexes) {
24829
+ const match = ua.match(regex);
24830
+ if (match) {
24831
+ browserFamily = name;
24832
+ browserVersion = match[1];
24833
+ break;
24834
+ }
24835
+ }
24836
+ }
24837
+ // --- OS detection (always run, iOS first) ---
24838
+ if (/iPhone|iPad|iPod/.test(ua)) {
24839
+ osFamily = "iOS";
24840
+ const match = ua.match(/OS (\d+[_\d]*)/);
24841
+ if (match)
24842
+ osVersion = match[1].replace(/_/g, ".");
24843
+ }
24844
+ else if (uaData === null || uaData === void 0 ? void 0 : uaData.platform) {
24845
+ osFamily = uaData.platform;
24846
+ }
24847
+ else if (/Windows/.test(ua)) {
24848
+ osFamily = "Windows";
24849
+ const match = ua.match(/Windows NT (\d+\.\d+)/);
24850
+ if (match)
24851
+ osVersion = match[1];
24852
+ }
24853
+ else if (/Mac OS X/.test(ua)) {
24854
+ osFamily = "macOS";
24855
+ const match = ua.match(/Mac OS X (\d+[_\d]*)/);
24856
+ if (match)
24857
+ osVersion = match[1].replace(/_/g, ".");
24858
+ }
24859
+ else if (/Android/.test(ua)) {
24860
+ osFamily = "Android";
24861
+ const match = ua.match(/Android (\d+(\.\d+)?)/);
24862
+ if (match)
24863
+ osVersion = match[1];
24864
+ }
24865
+ else if (/Linux/.test(ua)) {
24866
+ osFamily = "Linux";
24867
+ }
24868
+ const result = { browserFamily, browserVersion, osFamily, osVersion, model, architecture, bitness };
24869
+ this.uaInfoCache = result;
24870
+ return result;
24871
+ });
24617
24872
  }
24618
24873
  }
24619
- exports.Session = Session;
24874
+ exports.DiagnosticsCollector = DiagnosticsCollector;
24620
24875
 
24621
24876
 
24622
24877
  /***/ }),
24623
24878
 
24624
- /***/ 9764:
24625
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
24879
+ /***/ 2483:
24880
+ /***/ ((__unused_webpack_module, exports) => {
24626
24881
 
24627
24882
 
24628
24883
  Object.defineProperty(exports, "__esModule", ({ value: true }));
24629
- exports.Stats = void 0;
24630
- const shared_pixelstreaming_websdk_1 = __webpack_require__(7910);
24631
- function Stats(stats) {
24632
- const result = shared_pixelstreaming_websdk_1.Messages.ZStats.shape.stats.parse({
24633
- /**
24634
- * Other known properties.
24635
- */
24636
- codecs: stats.codecs,
24637
- candidatePair: stats.candidatePairs,
24638
- localCandidates: stats.localCandidates,
24639
- remoteCandidates: stats.remoteCandidates,
24640
- DataChannelStats: stats.datachannelStats,
24641
- // Received (last) for ticket
24642
- bytesReceived: forceNumber(stats.inboundVideoStats.bytesReceived),
24643
- // Packets Lost (last) for ticket
24644
- packetsLost: forceNumber(stats.inboundVideoStats.packetsLost),
24645
- // Video resolution: highest lowest average (portrait) and highest lowest average (landscape) for ticket
24646
- frameWidth: forceNumber(stats.inboundVideoStats.frameWidth),
24647
- // Video resolution: highest lowest average (portrait) and highest lowest average (landscape) for ticket
24648
- frameHeight: forceNumber(stats.inboundVideoStats.frameHeight),
24649
- // Frames decoded (Last) for ticket
24650
- framesDecoded: forceNumber(stats.inboundVideoStats.framesDecoded),
24651
- // Framerate (low / high, avg) for ticket
24652
- framesPerSecond: forceNumber(stats.inboundVideoStats.framesPerSecond),
24653
- // frames dropped (last) for ticket
24654
- framesDropped: forceNumber(stats.inboundVideoStats.framesDropped),
24655
- // Videocodec (once) for ticket
24656
- videoCodec: stats.inboundVideoStats.codecId,
24657
- // audiocodec (once) for ticket
24658
- audioCodec: stats.inboundAudioStats.codecId,
24659
- // browser type and version for ticket (arcware / addition)
24660
- browserInfo: {
24661
- // to be received!
24662
- userAgent: navigator.userAgent,
24663
- platform: navigator.oscpu || navigator.platform || null,
24664
- language: navigator.language,
24665
- },
24666
- // Net RTT (low, high, avg)
24667
- // BEGIN TW CHANGE
24668
- currentRTT: calcRTT(stats.candidatePairs),
24669
- // END TW CHANGE
24670
- // Duration (last)
24671
- sessionRunTime: stats.sessionStats.runTime,
24672
- // Controls stream input (??)
24673
- controlsStreamInput: stats.sessionStats.controlsStreamInput,
24674
- // video quantization parameter (low / high / avg if applicable)
24675
- videoEncoderAvgQP: forceNumber(stats.sessionStats.videoEncoderAvgQP),
24676
- // Video bitrate (min / max / avg)
24677
- videoBitrate: forceNumber(stats.inboundVideoStats.bitrate),
24678
- // Audio bitrate (min / max / avg)
24679
- audioBitrate: forceNumber(stats.inboundAudioStats.bitrate),
24680
- });
24681
- return result;
24682
- }
24683
- exports.Stats = Stats;
24684
- /**
24685
- * Given an array of candidate pairs this function will find the highest RTT for the selected pair
24686
- * @param pairs - An array of candidate pairs
24687
- * @returns The highest round trip time of a selected candidate pair
24688
- */
24689
- function calcRTT(pairs) {
24690
- let rtt = 0;
24691
- pairs.forEach((pair) => {
24692
- if (pair.selected && pair.currentRoundTripTime > rtt) {
24693
- rtt = pair.currentRoundTripTime;
24884
+ exports.normalizeType = exports.randomHash = exports.extFromMime = exports.sanitizeFilename = exports.truncateByBytes = exports.parseUnknownToObject = void 0;
24885
+ /** Conservative normalizer: accepts object, strict JSON string, or JSON-ish like {foo: "bar"} */
24886
+ function parseUnknownToObject(input, opts = { allowJsonish: true }) {
24887
+ if (input && typeof input === "object")
24888
+ return input;
24889
+ if (typeof input === "string") {
24890
+ try {
24891
+ const parsed = JSON.parse(input);
24892
+ if (parsed && typeof parsed === "object")
24893
+ return parsed;
24894
+ }
24895
+ catch (_a) { }
24896
+ if (opts.allowJsonish) {
24897
+ const repaired = input
24898
+ .trim()
24899
+ .replace(/'/g, '"')
24900
+ .replace(/([{,]\s*)([A-Za-z_][$\w-]*)(\s*:)/g, '$1"$2"$3');
24901
+ try {
24902
+ const parsed = JSON.parse(repaired);
24903
+ if (parsed && typeof parsed === "object")
24904
+ return parsed;
24905
+ }
24906
+ catch (_b) { }
24694
24907
  }
24695
- });
24696
- return rtt;
24697
- }
24698
- /**
24699
- * Takes a number and forces it to return a number. If the value passed in is NaN, 0 will be returned.
24700
- * @param value - A number
24701
- * @returns The number passed in as value or 0 if vlaue was NaN
24702
- */
24703
- function forceNumber(value) {
24704
- return isNaN(value) ? 0 : value;
24908
+ }
24909
+ throw new Error("Input must be an object or a JSON string of an object.");
24705
24910
  }
24706
-
24707
-
24708
- /***/ }),
24709
-
24710
- /***/ 9580:
24711
- /***/ ((__unused_webpack_module, exports) => {
24712
-
24713
-
24714
- Object.defineProperty(exports, "__esModule", ({ value: true }));
24715
- function debounce(func, wait) {
24716
- let timeout;
24717
- return function (...args) {
24718
- clearTimeout(timeout);
24719
- timeout = window.setTimeout(() => func(...args), wait);
24911
+ exports.parseUnknownToObject = parseUnknownToObject;
24912
+ /** Byte-safe string truncation (no broken unicode) */
24913
+ function truncateByBytes(input, maxBytes) {
24914
+ const enc = new TextEncoder();
24915
+ const dec = new TextDecoder();
24916
+ const bytes = enc.encode(input);
24917
+ if (bytes.byteLength <= maxBytes)
24918
+ return input;
24919
+ // Back off until decode works
24920
+ for (let cut = maxBytes; cut > 0; cut--) {
24921
+ try {
24922
+ return dec.decode(bytes.subarray(0, cut), { stream: false });
24923
+ }
24924
+ catch (_a) { }
24925
+ }
24926
+ return "";
24927
+ }
24928
+ exports.truncateByBytes = truncateByBytes;
24929
+ /** Cross-platform safe filename */
24930
+ function sanitizeFilename(input) {
24931
+ let name = input.replace(/\s+/g, " ").trim();
24932
+ name = name.replace(/[\\\/:\*\?"<>\|\u0000-\u001F]+/g, "");
24933
+ name = name.replace(/^\.+/, ""); // avoid hidden files
24934
+ const reserved = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
24935
+ if (reserved.test(name) || !name)
24936
+ name = randomHash();
24937
+ if (name.length > 100)
24938
+ name = name.slice(0, 100);
24939
+ return name;
24940
+ }
24941
+ exports.sanitizeFilename = sanitizeFilename;
24942
+ /** MIME -> extension */
24943
+ function extFromMime(mime) {
24944
+ const map = {
24945
+ "image/png": ".png",
24946
+ "image/jpeg": ".jpg",
24947
+ "image/jpg": ".jpg",
24948
+ "image/webp": ".webp",
24949
+ "image/gif": ".gif",
24950
+ "image/bmp": ".bmp",
24951
+ "image/tiff": ".tiff",
24952
+ "video/mp4": ".mp4",
24953
+ "video/webm": ".webm",
24954
+ "video/ogg": ".ogv",
24955
+ "audio/mpeg": ".mp3",
24956
+ "audio/ogg": ".ogg",
24957
+ "audio/wav": ".wav",
24958
+ "audio/webm": ".weba",
24959
+ "application/pdf": ".pdf",
24960
+ "application/zip": ".zip",
24961
+ "application/x-zip-compressed": ".zip",
24962
+ "application/json": ".json",
24963
+ "text/plain": ".txt",
24964
+ "text/csv": ".csv"
24720
24965
  };
24966
+ if (map[mime])
24967
+ return map[mime];
24968
+ if (mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/")) {
24969
+ return "." + mime.split("/")[1];
24970
+ }
24971
+ return "";
24721
24972
  }
24722
- exports["default"] = debounce;
24973
+ exports.extFromMime = extFromMime;
24974
+ /** Short random fallback */
24975
+ function randomHash() {
24976
+ return Math.random().toString(36).slice(2, 8);
24977
+ }
24978
+ exports.randomHash = randomHash;
24979
+ function normalizeType(v) {
24980
+ return typeof v === "string" ? v.trim().toLowerCase() : "";
24981
+ }
24982
+ exports.normalizeType = normalizeType;
24723
24983
 
24724
24984
 
24725
24985
  /***/ }),