@eyepop.ai/eyepop 0.11.0 → 0.13.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/dist/index.mjs CHANGED
@@ -8915,7 +8915,7 @@ var AbstractJob = class {
8915
8915
  return Promise.reject("abstract class");
8916
8916
  }
8917
8917
  start(done, status) {
8918
- this._responseStream = new Promise(async (resolve2, reject) => {
8918
+ this._responseStream = new Promise(async (resolve, reject) => {
8919
8919
  try {
8920
8920
  let retries = 1;
8921
8921
  while (true) {
@@ -8944,7 +8944,7 @@ var AbstractJob = class {
8944
8944
  reject(`response body is empty`);
8945
8945
  break;
8946
8946
  } else {
8947
- resolve2(response.body);
8947
+ resolve(response.body);
8948
8948
  break;
8949
8949
  }
8950
8950
  } catch (error) {
@@ -9054,7 +9054,7 @@ var LoadLiveIngressJob = class extends AbstractJob {
9054
9054
  "Accept": "application/jsonl",
9055
9055
  "Content-Type": "application/json"
9056
9056
  };
9057
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9057
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
9058
9058
  this._requestLogger.debug("before PATCH %s with url %s as source", patchUrl, this._ingressId);
9059
9059
  const response = await this._client.fetch(patchUrl, {
9060
9060
  headers,
@@ -9283,17 +9283,91 @@ function v4(options, buf, offset) {
9283
9283
  }
9284
9284
  var v4_default = v4;
9285
9285
 
9286
- // src/web_rtc_base.ts
9287
- var WebRtcBase = class {
9288
- constructor(getSession, client, requestLogger) {
9289
- this._pc = null;
9286
+ // src/webrtc_base.ts
9287
+ var WebrtcBase = class _WebrtcBase {
9288
+ constructor(getSession, client, ingressId, urlBasePath, requestLogger) {
9290
9289
  this._getSession = getSession;
9291
9290
  this._client = client;
9292
9291
  this._requestLogger = requestLogger;
9292
+ this._ingressId = ingressId;
9293
+ this._urlPath = `${urlBasePath}/${this._ingressId}`;
9294
+ this._pc = null;
9293
9295
  this._eTag = "";
9294
9296
  this._queuedCandidates = [];
9295
9297
  this._offerData = null;
9296
9298
  }
9299
+ async close() {
9300
+ const pc = this._pc;
9301
+ this._pc = null;
9302
+ if (pc) {
9303
+ return pc.close();
9304
+ }
9305
+ }
9306
+ ingressId() {
9307
+ return this._ingressId;
9308
+ }
9309
+ async onLocalOffer(offer) {
9310
+ if (!this._pc) {
9311
+ throw new Error("onLocalOffer no peer connection");
9312
+ }
9313
+ if (!offer.sdp) {
9314
+ throw new Error("onLocalOffer no sdp");
9315
+ }
9316
+ this._offerData = _WebrtcBase.parseOffer(offer.sdp);
9317
+ await this._pc.setLocalDescription(offer);
9318
+ const session = await this._getSession();
9319
+ const ingressUrl = new URL(this._urlPath, session.baseUrl);
9320
+ this._requestLogger.debug("before POST: %s", ingressUrl);
9321
+ const headers = {
9322
+ "Authorization": `Bearer ${session.accessToken}`,
9323
+ "Content-Type": "application/sdp"
9324
+ };
9325
+ const response = await this._client.fetch(ingressUrl, {
9326
+ headers,
9327
+ method: "POST",
9328
+ body: offer.sdp
9329
+ });
9330
+ if (response.status != 201) {
9331
+ return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9332
+ }
9333
+ this._eTag = response.headers.get("ETag") ?? "";
9334
+ return this.onRemoteAnswer(new RTCSessionDescription({
9335
+ type: "answer",
9336
+ sdp: await response.text()
9337
+ }));
9338
+ }
9339
+ async sendLocalCandidates(candidates) {
9340
+ if (!this._offerData) {
9341
+ throw new Error("sendLocalCandidates no offerData");
9342
+ }
9343
+ const session = await this._getSession();
9344
+ const ingressUrl = new URL(this._urlPath, session.baseUrl);
9345
+ this._requestLogger.debug("before PATCH: %s", ingressUrl);
9346
+ const headers = {
9347
+ "Authorization": `Bearer ${session.accessToken}`,
9348
+ "Content-Type": "application/trickle-ice-sdpfrag",
9349
+ "If-Match": this._eTag
9350
+ };
9351
+ const response = await this._client.fetch(ingressUrl, {
9352
+ headers,
9353
+ method: "PATCH",
9354
+ body: _WebrtcBase.generateSdpFragment(this._offerData, candidates)
9355
+ });
9356
+ if (response.status != 204) {
9357
+ return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
9358
+ }
9359
+ return this;
9360
+ }
9361
+ async onLocalCandidate(evt) {
9362
+ if (evt.candidate !== null) {
9363
+ if (this._eTag === "") {
9364
+ this._queuedCandidates.push(evt.candidate);
9365
+ } else {
9366
+ await this.sendLocalCandidates([evt.candidate]);
9367
+ }
9368
+ }
9369
+ return this;
9370
+ }
9297
9371
  static linkToIceServers(links) {
9298
9372
  const servers = links !== null ? links.split(", ").map((link) => {
9299
9373
  const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
@@ -9302,8 +9376,8 @@ var WebRtcBase = class {
9302
9376
  }
9303
9377
  const iceServer = {
9304
9378
  urls: [m[1]],
9305
- username: Whip.unquoteCredential(m[3]),
9306
- credential: Whip.unquoteCredential(m[4])
9379
+ username: WebrtcWhip.unquoteCredential(m[3]),
9380
+ credential: WebrtcWhip.unquoteCredential(m[4])
9307
9381
  // credentialType: m[3]? "password" : undefined
9308
9382
  };
9309
9383
  return iceServer;
@@ -9317,9 +9391,9 @@ var WebRtcBase = class {
9317
9391
  for (let i = 0; i < sections.length; i++) {
9318
9392
  const section = sections[i];
9319
9393
  if (section.startsWith("video")) {
9320
- sections[i] = Whip.setVideoBitrate(Whip.setCodec(section, videoCodec), videoBitrate);
9394
+ sections[i] = WebrtcWhip.setVideoBitrate(WebrtcWhip.setCodec(section, videoCodec), videoBitrate);
9321
9395
  } else if (section.startsWith("audio")) {
9322
- sections[i] = Whip.setAudioBitrate(Whip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9396
+ sections[i] = WebrtcWhip.setAudioBitrate(WebrtcWhip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9323
9397
  }
9324
9398
  }
9325
9399
  return sections.join("m=");
@@ -9441,22 +9515,15 @@ var WebRtcBase = class {
9441
9515
  }
9442
9516
  };
9443
9517
 
9444
- // src/whip.ts
9445
- var Whip = class _Whip extends WebRtcBase {
9518
+ // src/webrtc_whip.ts
9519
+ var WebrtcWhip = class _WebrtcWhip extends WebrtcBase {
9446
9520
  constructor(stream, getSession, client, requestLogger) {
9447
- super(getSession, client, requestLogger);
9448
- this._ingressId = v4_default().toString();
9521
+ super(getSession, client, v4_default().toString(), "/liveIngress/whip", requestLogger);
9449
9522
  this._stream = stream;
9450
9523
  }
9451
- ingressId() {
9452
- if (!this._ingressId) {
9453
- throw new Error("not initialized");
9454
- }
9455
- return this._ingressId;
9456
- }
9457
9524
  async start() {
9458
9525
  const session = await this._getSession();
9459
- const ingressUrl = new URL("/liveIngress/whip/" + this._ingressId, session.baseUrl);
9526
+ const ingressUrl = new URL(this._urlPath, session.baseUrl);
9460
9527
  this._requestLogger.debug("before GET: %s", ingressUrl);
9461
9528
  const headers = {
9462
9529
  "Authorization": `Bearer ${session.accessToken}`
@@ -9470,17 +9537,46 @@ var Whip = class _Whip extends WebRtcBase {
9470
9537
  }
9471
9538
  return this.onIceServers(response);
9472
9539
  }
9540
+ async stream() {
9541
+ if (this._stream) {
9542
+ return Promise.resolve(this._stream);
9543
+ } else {
9544
+ return Promise.reject("fetching stream failed");
9545
+ }
9546
+ }
9547
+ async onRemoteAnswer(answer) {
9548
+ if (!this._pc) {
9549
+ throw new Error("onLocalOffer no peer connection");
9550
+ }
9551
+ answer = new RTCSessionDescription({
9552
+ type: "answer",
9553
+ sdp: _WebrtcWhip.editAnswer(
9554
+ answer.sdp,
9555
+ "h264/90000",
9556
+ void 0,
9557
+ 5e3,
9558
+ void 0,
9559
+ true
9560
+ )
9561
+ });
9562
+ await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9563
+ if (this._queuedCandidates.length !== 0) {
9564
+ await this.sendLocalCandidates(this._queuedCandidates);
9565
+ this._queuedCandidates = [];
9566
+ }
9567
+ return this;
9568
+ }
9473
9569
  async onIceServers(response) {
9474
9570
  if (!this._stream) {
9475
9571
  throw new Error("onIceServers no stream");
9476
9572
  }
9477
9573
  const stream = this._stream;
9478
9574
  const pc = new RTCPeerConnection({
9479
- iceServers: _Whip.linkToIceServers(response.headers.get("Link"))
9575
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9480
9576
  });
9481
9577
  this._pc = pc;
9482
9578
  pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9483
- return new Promise(async (resolve2, reject) => {
9579
+ return new Promise(async (resolve, reject) => {
9484
9580
  pc.oniceconnectionstatechange = () => {
9485
9581
  switch (pc.iceConnectionState) {
9486
9582
  case "failed":
@@ -9488,7 +9584,7 @@ var Whip = class _Whip extends WebRtcBase {
9488
9584
  reject(`peer connection: ${pc.iceConnectionState}`);
9489
9585
  break;
9490
9586
  case "connected":
9491
- resolve2(this);
9587
+ resolve(this);
9492
9588
  break;
9493
9589
  }
9494
9590
  };
@@ -9503,61 +9599,63 @@ var Whip = class _Whip extends WebRtcBase {
9503
9599
  await this.onLocalOffer(await pc.createOffer());
9504
9600
  });
9505
9601
  }
9506
- async onLocalCandidate(evt) {
9507
- if (evt.candidate !== null) {
9508
- if (this._eTag === "") {
9509
- this._queuedCandidates.push(evt.candidate);
9510
- } else {
9511
- await this.sendLocalCandidates([evt.candidate]);
9512
- }
9602
+ async close() {
9603
+ const stream = this._stream;
9604
+ this._stream = null;
9605
+ if (stream) {
9606
+ const tracks = stream.getTracks();
9607
+ tracks.forEach((track) => {
9608
+ track.stop();
9609
+ });
9513
9610
  }
9514
- return this;
9611
+ return super.close();
9515
9612
  }
9516
- async onLocalOffer(offer) {
9517
- if (!this._pc) {
9518
- throw new Error("onLocalOffer no peer connection");
9613
+ };
9614
+
9615
+ // src/webrtc_whep.ts
9616
+ var WebrtcWhep = class extends WebrtcBase {
9617
+ constructor(ingressId, getSession, client, requestLogger) {
9618
+ super(getSession, client, ingressId, "/liveIngress/whep", requestLogger);
9619
+ this._stream = null;
9620
+ }
9621
+ async close() {
9622
+ const stream = this._stream;
9623
+ this._stream = null;
9624
+ if (stream) {
9625
+ const tracks = stream.getTracks();
9626
+ tracks.forEach((track) => {
9627
+ track.stop();
9628
+ });
9519
9629
  }
9520
- if (!offer.sdp) {
9521
- throw new Error("onLocalOffer no sdp");
9630
+ return super.close();
9631
+ }
9632
+ async stream() {
9633
+ if (this._stream) {
9634
+ return Promise.resolve(this._stream);
9635
+ } else {
9636
+ return Promise.reject("fetching stream failed");
9522
9637
  }
9523
- this._offerData = _Whip.parseOffer(offer.sdp);
9524
- await this._pc.setLocalDescription(offer);
9638
+ }
9639
+ async start() {
9525
9640
  const session = await this._getSession();
9526
- const ingressUrl = new URL("/liveIngress/whip/" + this._ingressId, session.baseUrl);
9527
- this._requestLogger.debug("before GET: %s", ingressUrl);
9641
+ const egressUrl = new URL(this._urlPath, session.baseUrl);
9642
+ this._requestLogger.debug("before GET: %s", egressUrl);
9528
9643
  const headers = {
9529
- "Authorization": `Bearer ${session.accessToken}`,
9530
- "Content-Type": "application/sdp"
9644
+ "Authorization": `Bearer ${session.accessToken}`
9531
9645
  };
9532
- const response = await this._client.fetch(ingressUrl, {
9646
+ const response = await this._client.fetch(egressUrl, {
9533
9647
  headers,
9534
- method: "POST",
9535
- body: offer.sdp
9648
+ method: "GET"
9536
9649
  });
9537
- if (response.status != 201) {
9538
- return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9650
+ if (response.status >= 300) {
9651
+ return Promise.reject(`unknown status code for GET '${egressUrl}': ${response.status} (${response.statusText})`);
9539
9652
  }
9540
- this._eTag = response.headers.get("ETag") ?? "";
9541
- return this.onRemoteAnswer(new RTCSessionDescription({
9542
- type: "answer",
9543
- sdp: await response.text()
9544
- }));
9653
+ return this.onIceServers(response);
9545
9654
  }
9546
9655
  async onRemoteAnswer(answer) {
9547
9656
  if (!this._pc) {
9548
9657
  throw new Error("onLocalOffer no peer connection");
9549
9658
  }
9550
- answer = new RTCSessionDescription({
9551
- type: "answer",
9552
- sdp: _Whip.editAnswer(
9553
- answer.sdp,
9554
- "h264/90000",
9555
- void 0,
9556
- 5e3,
9557
- void 0,
9558
- true
9559
- )
9560
- });
9561
9659
  await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9562
9660
  if (this._queuedCandidates.length !== 0) {
9563
9661
  await this.sendLocalCandidates(this._queuedCandidates);
@@ -9565,89 +9663,100 @@ var Whip = class _Whip extends WebRtcBase {
9565
9663
  }
9566
9664
  return this;
9567
9665
  }
9568
- async sendLocalCandidates(candidates) {
9569
- if (!this._offerData) {
9570
- throw new Error("sendLocalCandidates no offerData");
9666
+ async onIceServers(response) {
9667
+ if (this._stream) {
9668
+ throw new Error("onIceServers has stream");
9571
9669
  }
9572
- const session = await this._getSession();
9573
- const ingressUrl = new URL("/liveIngress/whip/" + this._ingressId, session.baseUrl);
9574
- this._requestLogger.debug("before PATCH: %s", ingressUrl);
9575
- const headers = {
9576
- "Authorization": `Bearer ${session.accessToken}`,
9577
- "Content-Type": "application/trickle-ice-sdpfrag",
9578
- "If-Match": this._eTag
9579
- };
9580
- const response = await this._client.fetch(ingressUrl, {
9581
- headers,
9582
- method: "PATCH",
9583
- body: _Whip.generateSdpFragment(this._offerData, candidates)
9670
+ const pc = new RTCPeerConnection({
9671
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9672
+ });
9673
+ const direction = "sendrecv";
9674
+ pc.addTransceiver("video", { direction });
9675
+ pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9676
+ this._pc = pc;
9677
+ this._requestLogger.info("before", pc.iceConnectionState);
9678
+ return new Promise(async (resolve, reject) => {
9679
+ pc.ontrack = (evt) => {
9680
+ this._requestLogger.debug("new track:", evt.track.kind);
9681
+ this._stream = evt.streams[0];
9682
+ resolve(this);
9683
+ };
9684
+ pc.oniceconnectionstatechange = () => {
9685
+ switch (pc.iceConnectionState) {
9686
+ case "failed":
9687
+ case "disconnected":
9688
+ this._requestLogger.debug("disconnected");
9689
+ reject(`peer connection: ${pc.iceConnectionState}`);
9690
+ break;
9691
+ case "connected":
9692
+ this._requestLogger.debug("connected");
9693
+ break;
9694
+ }
9695
+ };
9696
+ await this.onLocalOffer(await pc.createOffer());
9584
9697
  });
9585
- if (response.status != 204) {
9586
- return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
9587
- }
9588
- return this;
9589
- }
9590
- async close() {
9591
- const pc = this._pc;
9592
- const stream = this._stream;
9593
- this._stream = null;
9594
- this._pc = null;
9595
- if (stream) {
9596
- const tracks = stream.getTracks();
9597
- tracks.forEach((track) => {
9598
- track.stop();
9599
- });
9600
- }
9601
- if (pc) {
9602
- return pc.close();
9603
- }
9604
9698
  }
9605
9699
  };
9606
9700
 
9607
9701
  // src/shims/local_file.ts
9608
- var resolve;
9702
+ var resolvePath;
9609
9703
  if ("document" in globalThis && "implementation" in globalThis.document) {
9610
- const isFileLike = (value) => value != null && typeof value === "object" && typeof value.name === "string" && typeof value.lastModified === "number";
9611
- resolve = async (params) => {
9612
- if (params.file) {
9613
- if (isFileLike(params.file)) {
9614
- params = {
9615
- mimeType: params.file.type,
9616
- file: params.file
9617
- };
9618
- return params;
9619
- }
9620
- }
9704
+ resolvePath = async (source) => {
9621
9705
  throw new DOMException("resolving a path to a file is not supported in browser");
9622
9706
  };
9623
9707
  } else {
9624
- resolve = async (params) => {
9625
- if (params.file) {
9626
- return params;
9627
- }
9708
+ resolvePath = async (source) => {
9628
9709
  const mime = require_mime_types();
9629
- const fs = __require("fs");
9630
- let mimeType;
9631
- let stream;
9632
- if (params.filePath) {
9633
- if (params.mimeType) {
9634
- mimeType = params.mimeType;
9635
- } else {
9636
- mimeType = mime.lookup(params.filePath) || void 0;
9710
+ const filehandle = __require("fs/promises");
9711
+ const fd = await filehandle.open(source.path, "r");
9712
+ const stream = fd.createReadStream();
9713
+ const mimeType = mime.lookup(source.path) || "application/octet-stream";
9714
+ return {
9715
+ stream,
9716
+ mimeType
9717
+ };
9718
+ };
9719
+ }
9720
+
9721
+ // src/endpoint.ts
9722
+ import { pino } from "pino";
9723
+
9724
+ // src/shims/browser_session.ts
9725
+ import { createAuth0Client } from "@auth0/auth0-spa-js";
9726
+ var authenticateBrowserSession;
9727
+ if ("document" in globalThis && "implementation" in globalThis.document) {
9728
+ authenticateBrowserSession = async (auth0, eyepopUrl, popId) => {
9729
+ const options = {
9730
+ clientId: auth0.clientId,
9731
+ domain: auth0.domain,
9732
+ authorizationParams: {
9733
+ scope: auth0.scope,
9734
+ audience: auth0.audience
9637
9735
  }
9638
- stream = fs.createReadStream(params.filePath);
9736
+ };
9737
+ const auth0Client = await createAuth0Client(options);
9738
+ if (!await auth0Client.isAuthenticated()) {
9739
+ await auth0Client.loginWithPopup();
9639
9740
  }
9640
- params = {
9641
- filePath: params.filePath,
9642
- mimeType,
9643
- file: stream
9741
+ const accessToken = await auth0Client.getTokenSilently();
9742
+ if (!accessToken) {
9743
+ return Promise.reject("auth0 login failed");
9744
+ }
9745
+ const session = {
9746
+ eyepopUrl,
9747
+ popId,
9748
+ accessToken,
9749
+ validUntil: Date.now() + 60 * 60 * 1e3
9644
9750
  };
9645
- return params;
9751
+ return session;
9752
+ };
9753
+ } else {
9754
+ authenticateBrowserSession = (auth0, eyepopUrl, popId) => {
9755
+ return Promise.reject("auth0 login not supported server-side");
9646
9756
  };
9647
9757
  }
9648
9758
 
9649
9759
  // src/endpoint.ts
9650
- import { pino } from "pino";
9651
9760
  var Endpoint = class {
9652
9761
  constructor(options) {
9653
9762
  this._options = options;
@@ -9671,54 +9780,12 @@ var Endpoint = class {
9671
9780
  this._logger = rootLogger.child({ module: "eyepop" });
9672
9781
  this._requestLogger = this._logger.child({ module: "requests" });
9673
9782
  }
9674
- onStateChanged(handler) {
9675
- this._stateChangeHandler = handler;
9676
- return this;
9677
- }
9678
- async startIngressWs() {
9679
- if (this._ingressEventWs || !this._baseUrl || !this._client) {
9680
- return;
9783
+ eyepopUrl() {
9784
+ if (this._options.eyepopUrl) {
9785
+ return this._options.eyepopUrl.replace(/\/+$/, "");
9786
+ } else {
9787
+ return "https://api.eyepop.ai";
9681
9788
  }
9682
- try {
9683
- const getTokenUrl = `${this._baseUrl}/liveIngress/events/token`;
9684
- const headers = {
9685
- "Authorization": await this.authorizationHeader()
9686
- };
9687
- this._requestLogger.debug("before GET %s", getTokenUrl);
9688
- let response = await this._client.fetch(getTokenUrl, { headers });
9689
- if (response.status == 401) {
9690
- this._requestLogger.debug("after GET %s: 401, about to retry with fresh access token", getTokenUrl);
9691
- this._token = null;
9692
- const headers2 = {
9693
- "Authorization": await this.authorizationHeader()
9694
- };
9695
- this.updateState("FetchConfig" /* FetchConfig */);
9696
- response = await this._client.fetch(getTokenUrl, { headers: headers2 });
9697
- }
9698
- if (response.status != 200) {
9699
- const message = await response.text();
9700
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9701
- }
9702
- const wsAuthToken = await response.json();
9703
- const wsUrl = this._baseUrl.startsWith("https://") ? `${this._baseUrl.replace("https://", "wss://")}/liveIngress/events/${wsAuthToken.token}` : `${this._baseUrl.replace("http://", "ws://")}/liveIngress/events/${wsAuthToken.token}`;
9704
- this._ingressEventWs = new WebSocket(wsUrl);
9705
- this._ingressEventWs.addEventListener("message", (event) => {
9706
- const ingressEvent = JSON.parse(event.data);
9707
- if (this._ingressEventHandler) {
9708
- this._ingressEventHandler(ingressEvent);
9709
- }
9710
- });
9711
- } finally {
9712
- this.updateState();
9713
- }
9714
- }
9715
- onIngressEvent(handler) {
9716
- this._ingressEventHandler = handler;
9717
- this.startIngressWs().then(() => {
9718
- }).catch((reason) => {
9719
- this._logger.warn("unexpected error starting the ingress event handler: %s", reason);
9720
- });
9721
- return this;
9722
9789
  }
9723
9790
  state() {
9724
9791
  return this._state;
@@ -9732,6 +9799,80 @@ var Endpoint = class {
9732
9799
  popId() {
9733
9800
  return this._options.popId || null;
9734
9801
  }
9802
+ onStateChanged(handler) {
9803
+ this._stateChangeHandler = handler;
9804
+ return this;
9805
+ }
9806
+ onIngressEvent(handler) {
9807
+ this._ingressEventHandler = handler;
9808
+ this.startIngressWs().then(() => {
9809
+ }).catch((reason) => {
9810
+ this._logger.warn("unexpected error starting the ingress event handler: %s", reason);
9811
+ });
9812
+ return this;
9813
+ }
9814
+ async connect() {
9815
+ if (this._client) {
9816
+ this._logger.warn("endpoint already connected");
9817
+ return this;
9818
+ }
9819
+ if (!this._options.eyepopUrl) {
9820
+ return Promise.reject("option eyepopUrl or environment variable EYEPOP_URL is required");
9821
+ }
9822
+ if (!this._options.popId) {
9823
+ return Promise.reject("option popId or environment variable EYEPOP_POP_ID is required");
9824
+ }
9825
+ if (this._options.auth === void 0) {
9826
+ return Promise.reject("cannot connect without defined auth option");
9827
+ }
9828
+ if (this._options.auth.session !== void 0) {
9829
+ this._token = this._options.auth.session.accessToken;
9830
+ this._expire_token_time = this._options.auth.session.validUntil / 1e3;
9831
+ } else if (this._options.auth.oAuth2 !== void 0) {
9832
+ } else if (this._options.auth.secretKey !== void 0) {
9833
+ } else {
9834
+ return Promise.reject("option secretKey or environment variable EYEPOP_SECRET_KEY is required");
9835
+ }
9836
+ this._client = await createHttpClient();
9837
+ let result = await this.reconnect();
9838
+ if (this._baseUrl && this._options.stopJobs) {
9839
+ const stop_url = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=preempt&processing=sync`;
9840
+ const body = { "sourceType": "NONE" };
9841
+ const headers = {
9842
+ "Authorization": await this.authorizationHeader(),
9843
+ "Content-Type": "application/json"
9844
+ };
9845
+ this._requestLogger.debug("before PATCH %s", stop_url);
9846
+ let response = await this._client.fetch(stop_url, {
9847
+ method: "PATCH",
9848
+ headers,
9849
+ body: JSON.stringify(body)
9850
+ });
9851
+ if (response.status >= 300) {
9852
+ const message = await response.text();
9853
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9854
+ }
9855
+ this._requestLogger.debug("after PATCH %s", stop_url);
9856
+ }
9857
+ if (!this._ingressEventWs && this._ingressEventHandler) {
9858
+ await this.startIngressWs();
9859
+ }
9860
+ return result;
9861
+ }
9862
+ async disconnect(wait = true) {
9863
+ if (wait && this._limit) {
9864
+ for (let j = 0; j < this._options.jobQueueLength; j++) {
9865
+ await this._limit.acquire();
9866
+ }
9867
+ }
9868
+ this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9869
+ if (this._client) {
9870
+ await this._client.close();
9871
+ this._client = null;
9872
+ }
9873
+ this._baseUrl = null;
9874
+ this._pipelineId = null;
9875
+ }
9735
9876
  async session() {
9736
9877
  await this.currentAccessToken();
9737
9878
  if (this._token == null || this._expire_token_time == null) {
@@ -9756,101 +9897,172 @@ var Endpoint = class {
9756
9897
  if (!this._baseUrl || !this._client) {
9757
9898
  return Promise.reject("endpoint not connected, use connect() before ingress()");
9758
9899
  }
9759
- const job = new Whip(stream, async () => {
9900
+ const whip = new WebrtcWhip(stream, async () => {
9760
9901
  return this.session();
9761
9902
  }, this._client, this._requestLogger);
9762
- return job.start();
9903
+ return whip.start();
9763
9904
  }
9764
- async upload(params) {
9765
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
9766
- return Promise.reject("endpoint not connected, use connect() before upload()");
9767
- }
9768
- params = await resolve(params);
9769
- if (!params.mimeType) {
9770
- return Promise.reject("upload for streams requires a mimeType");
9905
+ async liveEgress(ingressId) {
9906
+ if (!this._baseUrl || !this._client) {
9907
+ return Promise.reject("endpoint not connected, use connect() before ingress()");
9771
9908
  }
9772
- await this._limit.acquire();
9773
- this.updateState();
9774
- const job = new UploadJob(params.file, params.mimeType, async () => {
9909
+ const whep = new WebrtcWhep(ingressId, async () => {
9775
9910
  return this.session();
9776
9911
  }, this._client, this._requestLogger);
9777
- return job.start(() => {
9778
- this.jobDone(job);
9779
- }, (statusCode) => {
9780
- this.jobStatus(job, statusCode);
9781
- });
9912
+ return whep.start();
9913
+ }
9914
+ async process(source) {
9915
+ if (source.file !== void 0) {
9916
+ return this.uploadFile(source);
9917
+ } else if (source.stream !== void 0) {
9918
+ return this.uploadStream(source);
9919
+ } else if (source.path !== void 0) {
9920
+ return this.uploadPath(source);
9921
+ } else if (source.url !== void 0) {
9922
+ return this.loadFrom(source);
9923
+ } else if (source.ingressId !== void 0) {
9924
+ return this.loadLiveIngress(source);
9925
+ } else {
9926
+ return Promise.reject("unknown source type");
9927
+ }
9928
+ }
9929
+ async startIngressWs() {
9930
+ if (this._ingressEventWs || !this._baseUrl || !this._client) {
9931
+ return;
9932
+ }
9933
+ try {
9934
+ const getTokenUrl = `${this._baseUrl}/liveIngress/events/token`;
9935
+ const headers = {
9936
+ "Authorization": await this.authorizationHeader()
9937
+ };
9938
+ this._requestLogger.debug("before GET %s", getTokenUrl);
9939
+ let response = await this._client.fetch(getTokenUrl, { headers });
9940
+ if (response.status == 401) {
9941
+ this._requestLogger.debug("after GET %s: 401, about to retry with fresh access token", getTokenUrl);
9942
+ this._token = null;
9943
+ const headers2 = {
9944
+ "Authorization": await this.authorizationHeader()
9945
+ };
9946
+ this.updateState("FetchConfig" /* FetchConfig */);
9947
+ response = await this._client.fetch(getTokenUrl, { headers: headers2 });
9948
+ }
9949
+ if (response.status != 200) {
9950
+ const message = await response.text();
9951
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9952
+ }
9953
+ const wsAuthToken = await response.json();
9954
+ const wsUrl = this._baseUrl.startsWith("https://") ? `${this._baseUrl.replace("https://", "wss://")}/liveIngress/events/${wsAuthToken.token}` : `${this._baseUrl.replace("http://", "ws://")}/liveIngress/events/${wsAuthToken.token}`;
9955
+ this._ingressEventWs = new WebSocket(wsUrl);
9956
+ this._ingressEventWs.addEventListener("message", (event) => {
9957
+ const ingressEvent = JSON.parse(event.data);
9958
+ if (this._ingressEventHandler) {
9959
+ this._ingressEventHandler(ingressEvent);
9960
+ }
9961
+ });
9962
+ } finally {
9963
+ this.updateState();
9964
+ }
9782
9965
  }
9783
- async loadFrom(location) {
9966
+ async uploadFile(source) {
9784
9967
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
9785
- throw new Error("endpoint not connected, use connect() before loadFrom()");
9968
+ return Promise.reject("endpoint not connected, use connect() before upload()");
9786
9969
  }
9787
9970
  await this._limit.acquire();
9788
- this.updateState();
9789
- const job = new LoadFromJob(location, async () => {
9790
- return this.session();
9791
- }, this._client, this._requestLogger);
9792
- return job.start(() => {
9793
- this.jobDone(job);
9794
- }, (statusCode) => {
9795
- this.jobStatus(job, statusCode);
9796
- });
9971
+ try {
9972
+ this.updateState();
9973
+ const job = new UploadJob(source.file, source.file.type, async () => {
9974
+ return this.session();
9975
+ }, this._client, this._requestLogger);
9976
+ return job.start(() => {
9977
+ this.jobDone(job);
9978
+ }, (statusCode) => {
9979
+ this.jobStatus(job, statusCode);
9980
+ });
9981
+ } catch (e) {
9982
+ this._limit.release();
9983
+ throw e;
9984
+ }
9797
9985
  }
9798
- async loadLiveIngress(ingressId) {
9986
+ async uploadStream(source) {
9799
9987
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
9800
- throw new Error("endpoint not connected, use connect() before loadLiveIngress()");
9988
+ return Promise.reject("endpoint not connected, use connect() before upload()");
9801
9989
  }
9802
9990
  await this._limit.acquire();
9803
- this.updateState();
9804
- const job = new LoadLiveIngressJob(ingressId, async () => {
9805
- return this.session();
9806
- }, this._client, this._requestLogger);
9807
- return job.start(() => {
9808
- this.jobDone(job);
9809
- }, (statusCode) => {
9810
- this.jobStatus(job, statusCode);
9811
- });
9991
+ try {
9992
+ this.updateState();
9993
+ const job = new UploadJob(source.stream, source.mimeType, async () => {
9994
+ return this.session();
9995
+ }, this._client, this._requestLogger);
9996
+ return job.start(() => {
9997
+ this.jobDone(job);
9998
+ }, (statusCode) => {
9999
+ this.jobStatus(job, statusCode);
10000
+ });
10001
+ } catch (e) {
10002
+ this._limit.release();
10003
+ throw e;
10004
+ }
9812
10005
  }
9813
- // noinspection TypeScriptValidateTypes
9814
- async connect() {
9815
- if (this._client) {
9816
- console.info("endpoint already connected");
9817
- return this;
10006
+ async uploadPath(source) {
10007
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10008
+ throw new Error("endpoint not connected, use connect() before process()");
9818
10009
  }
9819
- if (!this._options.eyepopUrl) {
9820
- return Promise.reject("option eyepopUrl or environment variable EYEPOP_URL is required");
10010
+ await this._limit.acquire();
10011
+ try {
10012
+ this.updateState();
10013
+ const streamSource = await resolvePath(source);
10014
+ const job = new UploadJob(streamSource.stream, streamSource.mimeType, async () => {
10015
+ return this.session();
10016
+ }, this._client, this._requestLogger);
10017
+ return job.start(() => {
10018
+ this.jobDone(job);
10019
+ }, (statusCode) => {
10020
+ this.jobStatus(job, statusCode);
10021
+ });
10022
+ } catch (e) {
10023
+ this._limit.release();
10024
+ throw e;
9821
10025
  }
9822
- if (!this._options.popId) {
9823
- return Promise.reject("option popId or environment variable EYEPOP_POP_ID is required");
10026
+ }
10027
+ async loadFrom(source) {
10028
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10029
+ throw new Error("endpoint not connected, use connect() before loadFrom()");
9824
10030
  }
9825
- if (this._options.auth && this._options.auth.session) {
9826
- this._token = this._options.auth.session.accessToken;
9827
- this._expire_token_time = this._options.auth.session.validUntil / 1e3;
9828
- } else {
9829
- if (!this._options.auth || !this._options.auth.secretKey) {
9830
- return Promise.reject("option secretKey or environment variable EYEPOP_SECRET_KEY is required");
9831
- }
10031
+ await this._limit.acquire();
10032
+ try {
10033
+ this.updateState();
10034
+ const job = new LoadFromJob(source.url, async () => {
10035
+ return this.session();
10036
+ }, this._client, this._requestLogger);
10037
+ return job.start(() => {
10038
+ this.jobDone(job);
10039
+ }, (statusCode) => {
10040
+ this.jobStatus(job, statusCode);
10041
+ });
10042
+ } catch (e) {
10043
+ this._limit.release();
10044
+ throw e;
9832
10045
  }
9833
- this._client = await createHttpClient();
9834
- let result = await this.reconnect();
9835
- if (this._baseUrl && this._options.stopJobs) {
9836
- const stop_url = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=preempt&processing=sync`;
9837
- const body = { "sourceType": "NONE" };
9838
- const headers = {
9839
- "Authorization": await this.authorizationHeader(),
9840
- "Content-Type": "application/json"
9841
- };
9842
- this._requestLogger.debug("before PATCH %s", stop_url);
9843
- let response = await this._client.fetch(stop_url, { method: "PATCH", headers, body: JSON.stringify(body) });
9844
- if (response.status >= 300) {
9845
- const message = await response.text();
9846
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9847
- }
9848
- this._requestLogger.debug("after PATCH %s", stop_url);
10046
+ }
10047
+ async loadLiveIngress(source) {
10048
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10049
+ throw new Error("endpoint not connected, use connect() before loadLiveIngress()");
9849
10050
  }
9850
- if (!this._ingressEventWs && this._ingressEventHandler) {
9851
- await this.startIngressWs();
10051
+ await this._limit.acquire();
10052
+ try {
10053
+ this.updateState();
10054
+ const job = new LoadLiveIngressJob(source.ingressId, async () => {
10055
+ return this.session();
10056
+ }, this._client, this._requestLogger);
10057
+ return job.start(() => {
10058
+ this.jobDone(job);
10059
+ }, (statusCode) => {
10060
+ this.jobStatus(job, statusCode);
10061
+ });
10062
+ } catch (e) {
10063
+ this._limit.release();
10064
+ throw e;
9852
10065
  }
9853
- return result;
9854
10066
  }
9855
10067
  jobDone(job) {
9856
10068
  this._limit?.release();
@@ -9938,20 +10150,6 @@ var Endpoint = class {
9938
10150
  return Promise.resolve(this);
9939
10151
  }
9940
10152
  }
9941
- async disconnect(wait = true) {
9942
- if (wait && this._limit) {
9943
- for (let j = 0; j < this._options.jobQueueLength; j++) {
9944
- await this._limit.acquire();
9945
- }
9946
- }
9947
- this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9948
- if (this._client) {
9949
- await this._client.close();
9950
- this._client = null;
9951
- }
9952
- this._baseUrl = null;
9953
- this._pipelineId = null;
9954
- }
9955
10153
  async authorizationHeader() {
9956
10154
  return `Bearer ${await this.currentAccessToken()}`;
9957
10155
  }
@@ -9959,41 +10157,49 @@ var Endpoint = class {
9959
10157
  const now = Date.now() / 1e3;
9960
10158
  if (!this._token || this._expire_token_time < now) {
9961
10159
  this.updateState("Authenticating" /* Authenticating */);
9962
- if (!this._client) {
9963
- return Promise.reject("endpoint not connected");
9964
- } else if (!this._options.auth || !this._options.auth.secretKey) {
9965
- return Promise.reject("temporary access token expired");
9966
- }
9967
- const body = { "secret_key": this._options.auth.secretKey };
9968
- const headers = { "Content-Type": "application/json" };
9969
- const post_url = `${this.eyepopUrl()}/authentication/token`;
9970
- this._requestLogger.debug("before POST %s", post_url);
9971
- const response = await this._client.fetch(post_url, {
9972
- method: "POST",
9973
- headers,
9974
- body: JSON.stringify(body)
9975
- });
9976
- if (response.status != 200) {
9977
- const message = await response.text();
9978
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10160
+ try {
10161
+ if (!this._client) {
10162
+ return Promise.reject("endpoint not connected");
10163
+ } else if (this._options.auth.session !== void 0) {
10164
+ return Promise.reject("temporary access token expired");
10165
+ } else if (this._options.auth.oAuth2 !== void 0 && this._options.eyepopUrl && this._options.popId) {
10166
+ const session = await authenticateBrowserSession(
10167
+ this._options.auth.oAuth2,
10168
+ this._options.eyepopUrl,
10169
+ this._options.popId
10170
+ );
10171
+ this._token = session.accessToken;
10172
+ this._expire_token_time = session.validUntil / 1e3;
10173
+ } else if (this._options.auth.secretKey !== void 0 && this._options.eyepopUrl) {
10174
+ const secretKeyAuth = this._options.auth;
10175
+ const body = { "secret_key": secretKeyAuth.secretKey };
10176
+ const headers = { "Content-Type": "application/json" };
10177
+ const post_url = `${this.eyepopUrl()}/authentication/token`;
10178
+ this._requestLogger.debug("before POST %s", post_url);
10179
+ const response = await this._client.fetch(post_url, {
10180
+ method: "POST",
10181
+ headers,
10182
+ body: JSON.stringify(body)
10183
+ });
10184
+ if (response.status != 200) {
10185
+ const message = await response.text();
10186
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10187
+ }
10188
+ this._requestLogger.debug("after POST %s", post_url);
10189
+ const data = await response.json();
10190
+ const token = data;
10191
+ this._token = token.access_token;
10192
+ this._expire_token_time = now + token.expires_in - 60;
10193
+ } else {
10194
+ return Promise.reject("no valid auth option");
10195
+ }
10196
+ } finally {
10197
+ this.updateState();
9979
10198
  }
9980
- this._requestLogger.debug("after POST %s", post_url);
9981
- const data = await response.json();
9982
- const token = data;
9983
- this._token = token.access_token;
9984
- this._expire_token_time = now + token.expires_in - 60;
9985
- this.updateState();
9986
10199
  }
9987
10200
  this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
9988
10201
  return this._token;
9989
10202
  }
9990
- eyepopUrl() {
9991
- if (this._options.eyepopUrl) {
9992
- return this._options.eyepopUrl.replace(/\/+$/, "");
9993
- } else {
9994
- return "https://api.eyepop.ai";
9995
- }
9996
- }
9997
10203
  };
9998
10204
 
9999
10205
  // src/visualize.ts
@@ -10109,40 +10315,6 @@ _EyePopPlot.colors = {
10109
10315
  };
10110
10316
  var EyePopPlot = _EyePopPlot;
10111
10317
 
10112
- // src/shims/browser_session.ts
10113
- var authenticateBrowserSession;
10114
- if ("document" in globalThis && "implementation" in globalThis.document) {
10115
- authenticateBrowserSession = (oauthUrl, eyepopUrl, popId) => {
10116
- const queryString = window.location.search;
10117
- const urlParams = new URLSearchParams(queryString);
10118
- let token = urlParams.get("pop_token");
10119
- if (token) {
10120
- urlParams.delete("pop_token");
10121
- const url = `${window.location.pathname}?${urlParams}`;
10122
- window.history.replaceState({}, document.title, url);
10123
- return {
10124
- eyepopUrl,
10125
- popId,
10126
- accessToken: token,
10127
- validUntil: Date.now() + 60 * 60 * 1e3
10128
- };
10129
- } else {
10130
- const url = `${oauthUrl}?url=${encodeURIComponent(window.location.href)}`;
10131
- window.location.replace(url);
10132
- return {
10133
- eyepopUrl,
10134
- popId,
10135
- accessToken: "in-progress",
10136
- validUntil: 0
10137
- };
10138
- }
10139
- };
10140
- } else {
10141
- authenticateBrowserSession = (oauthUrl, eyepopUrl, popId) => {
10142
- return null;
10143
- };
10144
- }
10145
-
10146
10318
  // src/index.ts
10147
10319
  var readEnv = (env) => {
10148
10320
  if (typeof process !== "undefined") {
@@ -10150,7 +10322,7 @@ var readEnv = (env) => {
10150
10322
  }
10151
10323
  return void 0;
10152
10324
  };
10153
- var EyePopSdk = class {
10325
+ var _EyePopSdk = class _EyePopSdk {
10154
10326
  static endpoint({
10155
10327
  auth = this.defaultAuth,
10156
10328
  popId = readEnv("EYEPOP_POP_ID"),
@@ -10161,17 +10333,30 @@ var EyePopSdk = class {
10161
10333
  logger,
10162
10334
  ...opts
10163
10335
  } = {}) {
10164
- if (auth.oAuth && popId) {
10165
- let oauthUrl = "https://dashboard.eyepop.ai/sdkauth";
10166
- if (typeof auth.oAuth == "string") {
10167
- oauthUrl = auth.oAuth;
10336
+ if (auth.oAuth2 !== void 0 && popId) {
10337
+ if (typeof auth.oAuth2 === "boolean") {
10338
+ auth = {
10339
+ oAuth2: {
10340
+ domain: "eyepop.us.auth0.com",
10341
+ clientId: "Lb9ubA9Hf3jlaqWLUx8XgA0zvotgViCl",
10342
+ audience: "https://api.eyepop.ai",
10343
+ scope: "admin:clouds"
10344
+ }
10345
+ };
10346
+ }
10347
+ }
10348
+ if (auth.session !== void 0) {
10349
+ if (auth.session.popId) {
10350
+ popId = auth.session.popId;
10351
+ }
10352
+ if (auth.session.eyepopUrl) {
10353
+ eyepopUrl = auth.session.eyepopUrl;
10168
10354
  }
10169
- auth.session = authenticateBrowserSession(oauthUrl, eyepopUrl ?? "https://api.eyepop.ai", popId) ?? void 0;
10170
10355
  }
10171
10356
  const options = {
10172
10357
  auth,
10173
- popId: auth.session ? auth.session.popId : popId,
10174
- eyepopUrl: auth.session ? auth.session.eyepopUrl : eyepopUrl ?? "https://api.eyepop.ai",
10358
+ popId,
10359
+ eyepopUrl: eyepopUrl ?? "https://api.eyepop.ai",
10175
10360
  autoStart,
10176
10361
  stopJobs,
10177
10362
  jobQueueLength,
@@ -10184,9 +10369,11 @@ var EyePopSdk = class {
10184
10369
  return new EyePopPlot(context);
10185
10370
  }
10186
10371
  };
10187
- EyePopSdk.defaultAuth = {
10188
- secretKey: readEnv("EYEPOP_SECRET_KEY")
10189
- };
10372
+ _EyePopSdk.envSecretKey = readEnv("EYEPOP_SECRET_KEY");
10373
+ _EyePopSdk.defaultAuth = _EyePopSdk.envSecretKey ? {
10374
+ secretKey: _EyePopSdk.envSecretKey
10375
+ } : void 0;
10376
+ var EyePopSdk = _EyePopSdk;
10190
10377
  var src_default = EyePopSdk;
10191
10378
  export {
10192
10379
  EndpointState,