@eyepop.ai/eyepop 0.12.0 → 0.14.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
@@ -8599,11 +8599,11 @@ var require_mime_types = __commonJS({
8599
8599
  }
8600
8600
  return exts[0];
8601
8601
  }
8602
- function lookup(path) {
8603
- if (!path || typeof path !== "string") {
8602
+ function lookup(path2) {
8603
+ if (!path2 || typeof path2 !== "string") {
8604
8604
  return false;
8605
8605
  }
8606
- var extension2 = extname("x." + path).toLowerCase().substr(1);
8606
+ var extension2 = extname("x." + path2).toLowerCase().substr(1);
8607
8607
  if (!extension2) {
8608
8608
  return false;
8609
8609
  }
@@ -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,100 @@ 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
+ import path from "path";
9288
+ var WebrtcBase = class _WebrtcBase {
9289
+ constructor(getSession, client, ingressId, urlBasePath, requestLogger) {
9290
9290
  this._getSession = getSession;
9291
9291
  this._client = client;
9292
9292
  this._requestLogger = requestLogger;
9293
+ this._ingressId = ingressId;
9294
+ this._urlPath = `${urlBasePath}/${this._ingressId}`;
9295
+ this._pc = null;
9293
9296
  this._eTag = "";
9297
+ this._location = null;
9294
9298
  this._queuedCandidates = [];
9295
9299
  this._offerData = null;
9296
9300
  }
9301
+ async close() {
9302
+ const pc = this._pc;
9303
+ this._pc = null;
9304
+ if (pc) {
9305
+ return pc.close();
9306
+ }
9307
+ }
9308
+ ingressId() {
9309
+ return this._ingressId;
9310
+ }
9311
+ async onLocalOffer(offer) {
9312
+ if (!this._pc) {
9313
+ throw new Error("onLocalOffer no peer connection");
9314
+ }
9315
+ if (!offer.sdp) {
9316
+ throw new Error("onLocalOffer no sdp");
9317
+ }
9318
+ this._offerData = _WebrtcBase.parseOffer(offer.sdp);
9319
+ await this._pc.setLocalDescription(offer);
9320
+ const session = await this._getSession();
9321
+ const ingressUrl = new URL(this._urlPath, session.baseUrl);
9322
+ this._requestLogger.debug("before POST: %s", ingressUrl);
9323
+ const headers = {
9324
+ "Authorization": `Bearer ${session.accessToken}`,
9325
+ "Content-Type": "application/sdp"
9326
+ };
9327
+ const response = await this._client.fetch(ingressUrl, {
9328
+ headers,
9329
+ method: "POST",
9330
+ body: offer.sdp
9331
+ });
9332
+ if (response.status != 201) {
9333
+ return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9334
+ }
9335
+ this._eTag = response.headers.get("ETag") ?? "";
9336
+ this._location = response.headers.get("Location");
9337
+ return this.onRemoteAnswer(new RTCSessionDescription({
9338
+ type: "answer",
9339
+ sdp: await response.text()
9340
+ }));
9341
+ }
9342
+ async sendLocalCandidates(candidates) {
9343
+ if (!this._offerData) {
9344
+ throw new Error("sendLocalCandidates no offerData");
9345
+ }
9346
+ let urlPath = void 0;
9347
+ const session = await this._getSession();
9348
+ if (this._location) {
9349
+ urlPath = path.join(this._urlPath, this._location);
9350
+ } else {
9351
+ urlPath = this._urlPath;
9352
+ }
9353
+ const ingressUrl = new URL(urlPath, session.baseUrl);
9354
+ this._requestLogger.debug("before PATCH: %s", ingressUrl);
9355
+ const headers = {
9356
+ "Authorization": `Bearer ${session.accessToken}`,
9357
+ "Content-Type": "application/trickle-ice-sdpfrag",
9358
+ "If-Match": this._eTag
9359
+ };
9360
+ const response = await this._client.fetch(ingressUrl, {
9361
+ headers,
9362
+ method: "PATCH",
9363
+ body: _WebrtcBase.generateSdpFragment(this._offerData, candidates)
9364
+ });
9365
+ if (response.status != 204) {
9366
+ return Promise.reject(`unknown status code for PATCH '${ingressUrl}': ${response.status} (${response.statusText})`);
9367
+ }
9368
+ return this;
9369
+ }
9370
+ async onLocalCandidate(evt) {
9371
+ if (evt.candidate !== null) {
9372
+ if (this._eTag === "") {
9373
+ this._queuedCandidates.push(evt.candidate);
9374
+ } else {
9375
+ await this.sendLocalCandidates([evt.candidate]);
9376
+ }
9377
+ }
9378
+ return this;
9379
+ }
9297
9380
  static linkToIceServers(links) {
9298
9381
  const servers = links !== null ? links.split(", ").map((link) => {
9299
9382
  const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
@@ -9302,8 +9385,8 @@ var WebRtcBase = class {
9302
9385
  }
9303
9386
  const iceServer = {
9304
9387
  urls: [m[1]],
9305
- username: Whip.unquoteCredential(m[3]),
9306
- credential: Whip.unquoteCredential(m[4])
9388
+ username: WebrtcWhip.unquoteCredential(m[3]),
9389
+ credential: WebrtcWhip.unquoteCredential(m[4])
9307
9390
  // credentialType: m[3]? "password" : undefined
9308
9391
  };
9309
9392
  return iceServer;
@@ -9317,9 +9400,9 @@ var WebRtcBase = class {
9317
9400
  for (let i = 0; i < sections.length; i++) {
9318
9401
  const section = sections[i];
9319
9402
  if (section.startsWith("video")) {
9320
- sections[i] = Whip.setVideoBitrate(Whip.setCodec(section, videoCodec), videoBitrate);
9403
+ sections[i] = WebrtcWhip.setVideoBitrate(WebrtcWhip.setCodec(section, videoCodec), videoBitrate);
9321
9404
  } else if (section.startsWith("audio")) {
9322
- sections[i] = Whip.setAudioBitrate(Whip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9405
+ sections[i] = WebrtcWhip.setAudioBitrate(WebrtcWhip.setCodec(section, audioCodec), audioBitrate, audioVoice);
9323
9406
  }
9324
9407
  }
9325
9408
  return sections.join("m=");
@@ -9441,22 +9524,15 @@ var WebRtcBase = class {
9441
9524
  }
9442
9525
  };
9443
9526
 
9444
- // src/whip.ts
9445
- var Whip = class _Whip extends WebRtcBase {
9527
+ // src/webrtc_whip.ts
9528
+ var WebrtcWhip = class _WebrtcWhip extends WebrtcBase {
9446
9529
  constructor(stream, getSession, client, requestLogger) {
9447
- super(getSession, client, requestLogger);
9448
- this._ingressId = v4_default().toString();
9530
+ super(getSession, client, v4_default().toString(), "/liveIngress/whip", requestLogger);
9449
9531
  this._stream = stream;
9450
9532
  }
9451
- ingressId() {
9452
- if (!this._ingressId) {
9453
- throw new Error("not initialized");
9454
- }
9455
- return this._ingressId;
9456
- }
9457
9533
  async start() {
9458
9534
  const session = await this._getSession();
9459
- const ingressUrl = new URL("/liveIngress/whip/" + this._ingressId, session.baseUrl);
9535
+ const ingressUrl = new URL(this._urlPath, session.baseUrl);
9460
9536
  this._requestLogger.debug("before GET: %s", ingressUrl);
9461
9537
  const headers = {
9462
9538
  "Authorization": `Bearer ${session.accessToken}`
@@ -9470,13 +9546,42 @@ var Whip = class _Whip extends WebRtcBase {
9470
9546
  }
9471
9547
  return this.onIceServers(response);
9472
9548
  }
9549
+ async stream() {
9550
+ if (this._stream) {
9551
+ return Promise.resolve(this._stream);
9552
+ } else {
9553
+ return Promise.reject("fetching stream failed");
9554
+ }
9555
+ }
9556
+ async onRemoteAnswer(answer) {
9557
+ if (!this._pc) {
9558
+ throw new Error("onLocalOffer no peer connection");
9559
+ }
9560
+ answer = new RTCSessionDescription({
9561
+ type: "answer",
9562
+ sdp: _WebrtcWhip.editAnswer(
9563
+ answer.sdp,
9564
+ "h264/90000",
9565
+ void 0,
9566
+ 5e3,
9567
+ void 0,
9568
+ true
9569
+ )
9570
+ });
9571
+ await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9572
+ if (this._queuedCandidates.length !== 0) {
9573
+ await this.sendLocalCandidates(this._queuedCandidates);
9574
+ this._queuedCandidates = [];
9575
+ }
9576
+ return this;
9577
+ }
9473
9578
  async onIceServers(response) {
9474
9579
  if (!this._stream) {
9475
9580
  throw new Error("onIceServers no stream");
9476
9581
  }
9477
9582
  const stream = this._stream;
9478
9583
  const pc = new RTCPeerConnection({
9479
- iceServers: _Whip.linkToIceServers(response.headers.get("Link"))
9584
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9480
9585
  });
9481
9586
  this._pc = pc;
9482
9587
  pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
@@ -9503,61 +9608,63 @@ var Whip = class _Whip extends WebRtcBase {
9503
9608
  await this.onLocalOffer(await pc.createOffer());
9504
9609
  });
9505
9610
  }
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
- }
9611
+ async close() {
9612
+ const stream = this._stream;
9613
+ this._stream = null;
9614
+ if (stream) {
9615
+ const tracks = stream.getTracks();
9616
+ tracks.forEach((track) => {
9617
+ track.stop();
9618
+ });
9513
9619
  }
9514
- return this;
9620
+ return super.close();
9515
9621
  }
9516
- async onLocalOffer(offer) {
9517
- if (!this._pc) {
9518
- throw new Error("onLocalOffer no peer connection");
9622
+ };
9623
+
9624
+ // src/webrtc_whep.ts
9625
+ var WebrtcWhep = class extends WebrtcBase {
9626
+ constructor(ingressId, getSession, client, requestLogger) {
9627
+ super(getSession, client, ingressId, "/liveIngress/whep", requestLogger);
9628
+ this._stream = null;
9629
+ }
9630
+ async close() {
9631
+ const stream = this._stream;
9632
+ this._stream = null;
9633
+ if (stream) {
9634
+ const tracks = stream.getTracks();
9635
+ tracks.forEach((track) => {
9636
+ track.stop();
9637
+ });
9519
9638
  }
9520
- if (!offer.sdp) {
9521
- throw new Error("onLocalOffer no sdp");
9639
+ return super.close();
9640
+ }
9641
+ async stream() {
9642
+ if (this._stream) {
9643
+ return Promise.resolve(this._stream);
9644
+ } else {
9645
+ return Promise.reject("fetching stream failed");
9522
9646
  }
9523
- this._offerData = _Whip.parseOffer(offer.sdp);
9524
- await this._pc.setLocalDescription(offer);
9647
+ }
9648
+ async start() {
9525
9649
  const session = await this._getSession();
9526
- const ingressUrl = new URL("/liveIngress/whip/" + this._ingressId, session.baseUrl);
9527
- this._requestLogger.debug("before GET: %s", ingressUrl);
9650
+ const egressUrl = new URL(this._urlPath, session.baseUrl);
9651
+ this._requestLogger.debug("before GET: %s", egressUrl);
9528
9652
  const headers = {
9529
- "Authorization": `Bearer ${session.accessToken}`,
9530
- "Content-Type": "application/sdp"
9653
+ "Authorization": `Bearer ${session.accessToken}`
9531
9654
  };
9532
- const response = await this._client.fetch(ingressUrl, {
9655
+ const response = await this._client.fetch(egressUrl, {
9533
9656
  headers,
9534
- method: "POST",
9535
- body: offer.sdp
9657
+ method: "GET"
9536
9658
  });
9537
- if (response.status != 201) {
9538
- return Promise.reject(`unknown status code for POST '${ingressUrl}': ${response.status} (${response.statusText})`);
9659
+ if (response.status >= 300) {
9660
+ return Promise.reject(`unknown status code for GET '${egressUrl}': ${response.status} (${response.statusText})`);
9539
9661
  }
9540
- this._eTag = response.headers.get("ETag") ?? "";
9541
- return this.onRemoteAnswer(new RTCSessionDescription({
9542
- type: "answer",
9543
- sdp: await response.text()
9544
- }));
9662
+ return this.onIceServers(response);
9545
9663
  }
9546
9664
  async onRemoteAnswer(answer) {
9547
9665
  if (!this._pc) {
9548
9666
  throw new Error("onLocalOffer no peer connection");
9549
9667
  }
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
9668
  await this._pc.setRemoteDescription(new RTCSessionDescription(answer));
9562
9669
  if (this._queuedCandidates.length !== 0) {
9563
9670
  await this.sendLocalCandidates(this._queuedCandidates);
@@ -9565,42 +9672,38 @@ var Whip = class _Whip extends WebRtcBase {
9565
9672
  }
9566
9673
  return this;
9567
9674
  }
9568
- async sendLocalCandidates(candidates) {
9569
- if (!this._offerData) {
9570
- throw new Error("sendLocalCandidates no offerData");
9675
+ async onIceServers(response) {
9676
+ if (this._stream) {
9677
+ throw new Error("onIceServers has stream");
9571
9678
  }
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)
9679
+ const pc = new RTCPeerConnection({
9680
+ iceServers: WebrtcBase.linkToIceServers(response.headers.get("Link"))
9681
+ });
9682
+ const direction = "sendrecv";
9683
+ pc.addTransceiver("video", { direction });
9684
+ pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
9685
+ this._pc = pc;
9686
+ this._requestLogger.info("before", pc.iceConnectionState);
9687
+ return new Promise(async (resolve, reject) => {
9688
+ pc.ontrack = (evt) => {
9689
+ this._requestLogger.debug("new track:", evt.track.kind);
9690
+ this._stream = evt.streams[0];
9691
+ resolve(this);
9692
+ };
9693
+ pc.oniceconnectionstatechange = () => {
9694
+ switch (pc.iceConnectionState) {
9695
+ case "failed":
9696
+ case "disconnected":
9697
+ this._requestLogger.debug("disconnected");
9698
+ reject(`peer connection: ${pc.iceConnectionState}`);
9699
+ break;
9700
+ case "connected":
9701
+ this._requestLogger.debug("connected");
9702
+ break;
9703
+ }
9704
+ };
9705
+ await this.onLocalOffer(await pc.createOffer());
9584
9706
  });
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
9707
  }
9605
9708
  };
9606
9709
 
@@ -9626,6 +9729,43 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
9626
9729
 
9627
9730
  // src/endpoint.ts
9628
9731
  import { pino } from "pino";
9732
+
9733
+ // src/shims/browser_session.ts
9734
+ import { createAuth0Client } from "@auth0/auth0-spa-js";
9735
+ var authenticateBrowserSession;
9736
+ if ("document" in globalThis && "implementation" in globalThis.document) {
9737
+ authenticateBrowserSession = async (auth0, eyepopUrl, popId) => {
9738
+ const options = {
9739
+ clientId: auth0.clientId,
9740
+ domain: auth0.domain,
9741
+ authorizationParams: {
9742
+ scope: auth0.scope,
9743
+ audience: auth0.audience
9744
+ }
9745
+ };
9746
+ const auth0Client = await createAuth0Client(options);
9747
+ if (!await auth0Client.isAuthenticated()) {
9748
+ await auth0Client.loginWithPopup();
9749
+ }
9750
+ const accessToken = await auth0Client.getTokenSilently();
9751
+ if (!accessToken) {
9752
+ return Promise.reject("auth0 login failed");
9753
+ }
9754
+ const session = {
9755
+ eyepopUrl,
9756
+ popId,
9757
+ accessToken,
9758
+ validUntil: Date.now() + 60 * 60 * 1e3
9759
+ };
9760
+ return session;
9761
+ };
9762
+ } else {
9763
+ authenticateBrowserSession = (auth0, eyepopUrl, popId) => {
9764
+ return Promise.reject("auth0 login not supported server-side");
9765
+ };
9766
+ }
9767
+
9768
+ // src/endpoint.ts
9629
9769
  var Endpoint = class {
9630
9770
  constructor(options) {
9631
9771
  this._options = options;
@@ -9649,55 +9789,13 @@ var Endpoint = class {
9649
9789
  this._logger = rootLogger.child({ module: "eyepop" });
9650
9790
  this._requestLogger = this._logger.child({ module: "requests" });
9651
9791
  }
9652
- onStateChanged(handler) {
9653
- this._stateChangeHandler = handler;
9654
- return this;
9655
- }
9656
- async startIngressWs() {
9657
- if (this._ingressEventWs || !this._baseUrl || !this._client) {
9658
- return;
9659
- }
9660
- try {
9661
- const getTokenUrl = `${this._baseUrl}/liveIngress/events/token`;
9662
- const headers = {
9663
- "Authorization": await this.authorizationHeader()
9664
- };
9665
- this._requestLogger.debug("before GET %s", getTokenUrl);
9666
- let response = await this._client.fetch(getTokenUrl, { headers });
9667
- if (response.status == 401) {
9668
- this._requestLogger.debug("after GET %s: 401, about to retry with fresh access token", getTokenUrl);
9669
- this._token = null;
9670
- const headers2 = {
9671
- "Authorization": await this.authorizationHeader()
9672
- };
9673
- this.updateState("FetchConfig" /* FetchConfig */);
9674
- response = await this._client.fetch(getTokenUrl, { headers: headers2 });
9675
- }
9676
- if (response.status != 200) {
9677
- const message = await response.text();
9678
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9679
- }
9680
- const wsAuthToken = await response.json();
9681
- const wsUrl = this._baseUrl.startsWith("https://") ? `${this._baseUrl.replace("https://", "wss://")}/liveIngress/events/${wsAuthToken.token}` : `${this._baseUrl.replace("http://", "ws://")}/liveIngress/events/${wsAuthToken.token}`;
9682
- this._ingressEventWs = new WebSocket(wsUrl);
9683
- this._ingressEventWs.addEventListener("message", (event) => {
9684
- const ingressEvent = JSON.parse(event.data);
9685
- if (this._ingressEventHandler) {
9686
- this._ingressEventHandler(ingressEvent);
9687
- }
9688
- });
9689
- } finally {
9690
- this.updateState();
9792
+ eyepopUrl() {
9793
+ if (this._options.eyepopUrl) {
9794
+ return this._options.eyepopUrl.replace(/\/+$/, "");
9795
+ } else {
9796
+ return "https://api.eyepop.ai";
9691
9797
  }
9692
9798
  }
9693
- onIngressEvent(handler) {
9694
- this._ingressEventHandler = handler;
9695
- this.startIngressWs().then(() => {
9696
- }).catch((reason) => {
9697
- this._logger.warn("unexpected error starting the ingress event handler: %s", reason);
9698
- });
9699
- return this;
9700
- }
9701
9799
  state() {
9702
9800
  return this._state;
9703
9801
  }
@@ -9710,6 +9808,80 @@ var Endpoint = class {
9710
9808
  popId() {
9711
9809
  return this._options.popId || null;
9712
9810
  }
9811
+ onStateChanged(handler) {
9812
+ this._stateChangeHandler = handler;
9813
+ return this;
9814
+ }
9815
+ onIngressEvent(handler) {
9816
+ this._ingressEventHandler = handler;
9817
+ this.startIngressWs().then(() => {
9818
+ }).catch((reason) => {
9819
+ this._logger.warn("unexpected error starting the ingress event handler: %s", reason);
9820
+ });
9821
+ return this;
9822
+ }
9823
+ async connect() {
9824
+ if (this._client) {
9825
+ this._logger.warn("endpoint already connected");
9826
+ return this;
9827
+ }
9828
+ if (!this._options.eyepopUrl) {
9829
+ return Promise.reject("option eyepopUrl or environment variable EYEPOP_URL is required");
9830
+ }
9831
+ if (!this._options.popId) {
9832
+ return Promise.reject("option popId or environment variable EYEPOP_POP_ID is required");
9833
+ }
9834
+ if (this._options.auth === void 0) {
9835
+ return Promise.reject("cannot connect without defined auth option");
9836
+ }
9837
+ if (this._options.auth.session !== void 0) {
9838
+ this._token = this._options.auth.session.accessToken;
9839
+ this._expire_token_time = this._options.auth.session.validUntil / 1e3;
9840
+ } else if (this._options.auth.oAuth2 !== void 0) {
9841
+ } else if (this._options.auth.secretKey !== void 0) {
9842
+ } else {
9843
+ return Promise.reject("option secretKey or environment variable EYEPOP_SECRET_KEY is required");
9844
+ }
9845
+ this._client = await createHttpClient();
9846
+ let result = await this.reconnect();
9847
+ if (this._baseUrl && this._options.stopJobs) {
9848
+ const stop_url = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=preempt&processing=sync`;
9849
+ const body = { "sourceType": "NONE" };
9850
+ const headers = {
9851
+ "Authorization": await this.authorizationHeader(),
9852
+ "Content-Type": "application/json"
9853
+ };
9854
+ this._requestLogger.debug("before PATCH %s", stop_url);
9855
+ let response = await this._client.fetch(stop_url, {
9856
+ method: "PATCH",
9857
+ headers,
9858
+ body: JSON.stringify(body)
9859
+ });
9860
+ if (response.status >= 300) {
9861
+ const message = await response.text();
9862
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9863
+ }
9864
+ this._requestLogger.debug("after PATCH %s", stop_url);
9865
+ }
9866
+ if (!this._ingressEventWs && this._ingressEventHandler) {
9867
+ await this.startIngressWs();
9868
+ }
9869
+ return result;
9870
+ }
9871
+ async disconnect(wait = true) {
9872
+ if (wait && this._limit) {
9873
+ for (let j = 0; j < this._options.jobQueueLength; j++) {
9874
+ await this._limit.acquire();
9875
+ }
9876
+ }
9877
+ this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9878
+ if (this._client) {
9879
+ await this._client.close();
9880
+ this._client = null;
9881
+ }
9882
+ this._baseUrl = null;
9883
+ this._pipelineId = null;
9884
+ }
9713
9885
  async session() {
9714
9886
  await this.currentAccessToken();
9715
9887
  if (this._token == null || this._expire_token_time == null) {
@@ -9734,11 +9906,20 @@ var Endpoint = class {
9734
9906
  if (!this._baseUrl || !this._client) {
9735
9907
  return Promise.reject("endpoint not connected, use connect() before ingress()");
9736
9908
  }
9737
- const whip = new Whip(stream, async () => {
9909
+ const whip = new WebrtcWhip(stream, async () => {
9738
9910
  return this.session();
9739
9911
  }, this._client, this._requestLogger);
9740
9912
  return whip.start();
9741
9913
  }
9914
+ async liveEgress(ingressId) {
9915
+ if (!this._baseUrl || !this._client) {
9916
+ return Promise.reject("endpoint not connected, use connect() before ingress()");
9917
+ }
9918
+ const whep = new WebrtcWhep(ingressId, async () => {
9919
+ return this.session();
9920
+ }, this._client, this._requestLogger);
9921
+ return whep.start();
9922
+ }
9742
9923
  async process(source) {
9743
9924
  if (source.file !== void 0) {
9744
9925
  return this.uploadFile(source);
@@ -9754,6 +9935,43 @@ var Endpoint = class {
9754
9935
  return Promise.reject("unknown source type");
9755
9936
  }
9756
9937
  }
9938
+ async startIngressWs() {
9939
+ if (this._ingressEventWs || !this._baseUrl || !this._client) {
9940
+ return;
9941
+ }
9942
+ try {
9943
+ const getTokenUrl = `${this._baseUrl}/liveIngress/events/token`;
9944
+ const headers = {
9945
+ "Authorization": await this.authorizationHeader()
9946
+ };
9947
+ this._requestLogger.debug("before GET %s", getTokenUrl);
9948
+ let response = await this._client.fetch(getTokenUrl, { headers });
9949
+ if (response.status == 401) {
9950
+ this._requestLogger.debug("after GET %s: 401, about to retry with fresh access token", getTokenUrl);
9951
+ this._token = null;
9952
+ const headers2 = {
9953
+ "Authorization": await this.authorizationHeader()
9954
+ };
9955
+ this.updateState("FetchConfig" /* FetchConfig */);
9956
+ response = await this._client.fetch(getTokenUrl, { headers: headers2 });
9957
+ }
9958
+ if (response.status != 200) {
9959
+ const message = await response.text();
9960
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9961
+ }
9962
+ const wsAuthToken = await response.json();
9963
+ const wsUrl = this._baseUrl.startsWith("https://") ? `${this._baseUrl.replace("https://", "wss://")}/liveIngress/events/${wsAuthToken.token}` : `${this._baseUrl.replace("http://", "ws://")}/liveIngress/events/${wsAuthToken.token}`;
9964
+ this._ingressEventWs = new WebSocket(wsUrl);
9965
+ this._ingressEventWs.addEventListener("message", (event) => {
9966
+ const ingressEvent = JSON.parse(event.data);
9967
+ if (this._ingressEventHandler) {
9968
+ this._ingressEventHandler(ingressEvent);
9969
+ }
9970
+ });
9971
+ } finally {
9972
+ this.updateState();
9973
+ }
9974
+ }
9757
9975
  async uploadFile(source) {
9758
9976
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
9759
9977
  return Promise.reject("endpoint not connected, use connect() before upload()");
@@ -9796,6 +10014,7 @@ var Endpoint = class {
9796
10014
  }
9797
10015
  async uploadPath(source) {
9798
10016
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10017
+ console.log(this);
9799
10018
  throw new Error("endpoint not connected, use connect() before process()");
9800
10019
  }
9801
10020
  await this._limit.acquire();
@@ -9855,48 +10074,6 @@ var Endpoint = class {
9855
10074
  throw e;
9856
10075
  }
9857
10076
  }
9858
- // noinspection TypeScriptValidateTypes
9859
- async connect() {
9860
- if (this._client) {
9861
- console.info("endpoint already connected");
9862
- return this;
9863
- }
9864
- if (!this._options.eyepopUrl) {
9865
- return Promise.reject("option eyepopUrl or environment variable EYEPOP_URL is required");
9866
- }
9867
- if (!this._options.popId) {
9868
- return Promise.reject("option popId or environment variable EYEPOP_POP_ID is required");
9869
- }
9870
- if (this._options.auth && this._options.auth.session) {
9871
- this._token = this._options.auth.session.accessToken;
9872
- this._expire_token_time = this._options.auth.session.validUntil / 1e3;
9873
- } else {
9874
- if (!this._options.auth || !this._options.auth.secretKey) {
9875
- return Promise.reject("option secretKey or environment variable EYEPOP_SECRET_KEY is required");
9876
- }
9877
- }
9878
- this._client = await createHttpClient();
9879
- let result = await this.reconnect();
9880
- if (this._baseUrl && this._options.stopJobs) {
9881
- const stop_url = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=preempt&processing=sync`;
9882
- const body = { "sourceType": "NONE" };
9883
- const headers = {
9884
- "Authorization": await this.authorizationHeader(),
9885
- "Content-Type": "application/json"
9886
- };
9887
- this._requestLogger.debug("before PATCH %s", stop_url);
9888
- let response = await this._client.fetch(stop_url, { method: "PATCH", headers, body: JSON.stringify(body) });
9889
- if (response.status >= 300) {
9890
- const message = await response.text();
9891
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9892
- }
9893
- this._requestLogger.debug("after PATCH %s", stop_url);
9894
- }
9895
- if (!this._ingressEventWs && this._ingressEventHandler) {
9896
- await this.startIngressWs();
9897
- }
9898
- return result;
9899
- }
9900
10077
  jobDone(job) {
9901
10078
  this._limit?.release();
9902
10079
  this.updateState();
@@ -9971,7 +10148,8 @@ var Endpoint = class {
9971
10148
  }
9972
10149
  config = await response.json();
9973
10150
  }
9974
- this._baseUrl = config.base_url;
10151
+ const baseUrl = new URL(config.base_url, this.eyepopUrl());
10152
+ this._baseUrl = baseUrl.toString();
9975
10153
  this._pipelineId = config.pipeline_id;
9976
10154
  this._popName = config.name;
9977
10155
  this._requestLogger.debug("after GET %s: %s / %s", config_url, this._baseUrl, this._pipelineId);
@@ -9983,20 +10161,6 @@ var Endpoint = class {
9983
10161
  return Promise.resolve(this);
9984
10162
  }
9985
10163
  }
9986
- async disconnect(wait = true) {
9987
- if (wait && this._limit) {
9988
- for (let j = 0; j < this._options.jobQueueLength; j++) {
9989
- await this._limit.acquire();
9990
- }
9991
- }
9992
- this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9993
- if (this._client) {
9994
- await this._client.close();
9995
- this._client = null;
9996
- }
9997
- this._baseUrl = null;
9998
- this._pipelineId = null;
9999
- }
10000
10164
  async authorizationHeader() {
10001
10165
  return `Bearer ${await this.currentAccessToken()}`;
10002
10166
  }
@@ -10004,41 +10168,49 @@ var Endpoint = class {
10004
10168
  const now = Date.now() / 1e3;
10005
10169
  if (!this._token || this._expire_token_time < now) {
10006
10170
  this.updateState("Authenticating" /* Authenticating */);
10007
- if (!this._client) {
10008
- return Promise.reject("endpoint not connected");
10009
- } else if (!this._options.auth || !this._options.auth.secretKey) {
10010
- return Promise.reject("temporary access token expired");
10011
- }
10012
- const body = { "secret_key": this._options.auth.secretKey };
10013
- const headers = { "Content-Type": "application/json" };
10014
- const post_url = `${this.eyepopUrl()}/authentication/token`;
10015
- this._requestLogger.debug("before POST %s", post_url);
10016
- const response = await this._client.fetch(post_url, {
10017
- method: "POST",
10018
- headers,
10019
- body: JSON.stringify(body)
10020
- });
10021
- if (response.status != 200) {
10022
- const message = await response.text();
10023
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10171
+ try {
10172
+ if (!this._client) {
10173
+ return Promise.reject("endpoint not connected");
10174
+ } else if (this._options.auth.session !== void 0) {
10175
+ return Promise.reject("temporary access token expired");
10176
+ } else if (this._options.auth.oAuth2 !== void 0 && this._options.eyepopUrl && this._options.popId) {
10177
+ const session = await authenticateBrowserSession(
10178
+ this._options.auth.oAuth2,
10179
+ this._options.eyepopUrl,
10180
+ this._options.popId
10181
+ );
10182
+ this._token = session.accessToken;
10183
+ this._expire_token_time = session.validUntil / 1e3;
10184
+ } else if (this._options.auth.secretKey !== void 0 && this._options.eyepopUrl) {
10185
+ const secretKeyAuth = this._options.auth;
10186
+ const body = { "secret_key": secretKeyAuth.secretKey };
10187
+ const headers = { "Content-Type": "application/json" };
10188
+ const post_url = `${this.eyepopUrl()}/authentication/token`;
10189
+ this._requestLogger.debug("before POST %s", post_url);
10190
+ const response = await this._client.fetch(post_url, {
10191
+ method: "POST",
10192
+ headers,
10193
+ body: JSON.stringify(body)
10194
+ });
10195
+ if (response.status != 200) {
10196
+ const message = await response.text();
10197
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10198
+ }
10199
+ this._requestLogger.debug("after POST %s", post_url);
10200
+ const data = await response.json();
10201
+ const token = data;
10202
+ this._token = token.access_token;
10203
+ this._expire_token_time = now + token.expires_in - 60;
10204
+ } else {
10205
+ return Promise.reject("no valid auth option");
10206
+ }
10207
+ } finally {
10208
+ this.updateState();
10024
10209
  }
10025
- this._requestLogger.debug("after POST %s", post_url);
10026
- const data = await response.json();
10027
- const token = data;
10028
- this._token = token.access_token;
10029
- this._expire_token_time = now + token.expires_in - 60;
10030
- this.updateState();
10031
10210
  }
10032
10211
  this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
10033
10212
  return this._token;
10034
10213
  }
10035
- eyepopUrl() {
10036
- if (this._options.eyepopUrl) {
10037
- return this._options.eyepopUrl.replace(/\/+$/, "");
10038
- } else {
10039
- return "https://api.eyepop.ai";
10040
- }
10041
- }
10042
10214
  };
10043
10215
 
10044
10216
  // src/visualize.ts
@@ -10154,40 +10326,6 @@ _EyePopPlot.colors = {
10154
10326
  };
10155
10327
  var EyePopPlot = _EyePopPlot;
10156
10328
 
10157
- // src/shims/browser_session.ts
10158
- var authenticateBrowserSession;
10159
- if ("document" in globalThis && "implementation" in globalThis.document) {
10160
- authenticateBrowserSession = (oauthUrl, eyepopUrl, popId) => {
10161
- const queryString = window.location.search;
10162
- const urlParams = new URLSearchParams(queryString);
10163
- let token = urlParams.get("pop_token");
10164
- if (token) {
10165
- urlParams.delete("pop_token");
10166
- const url = `${window.location.pathname}?${urlParams}`;
10167
- window.history.replaceState({}, document.title, url);
10168
- return {
10169
- eyepopUrl,
10170
- popId,
10171
- accessToken: token,
10172
- validUntil: Date.now() + 60 * 60 * 1e3
10173
- };
10174
- } else {
10175
- const url = `${oauthUrl}?url=${encodeURIComponent(window.location.href)}`;
10176
- window.location.replace(url);
10177
- return {
10178
- eyepopUrl,
10179
- popId,
10180
- accessToken: "in-progress",
10181
- validUntil: 0
10182
- };
10183
- }
10184
- };
10185
- } else {
10186
- authenticateBrowserSession = (oauthUrl, eyepopUrl, popId) => {
10187
- return null;
10188
- };
10189
- }
10190
-
10191
10329
  // src/index.ts
10192
10330
  var readEnv = (env) => {
10193
10331
  if (typeof process !== "undefined") {
@@ -10195,43 +10333,72 @@ var readEnv = (env) => {
10195
10333
  }
10196
10334
  return void 0;
10197
10335
  };
10198
- var EyePopSdk = class {
10199
- static endpoint({
10200
- auth = this.defaultAuth,
10201
- popId = readEnv("EYEPOP_POP_ID"),
10202
- eyepopUrl = readEnv("EYEPOP_URL"),
10203
- autoStart = true,
10204
- stopJobs = true,
10205
- jobQueueLength = 1024,
10206
- logger,
10207
- ...opts
10208
- } = {}) {
10209
- if (auth.oAuth && popId) {
10210
- let oauthUrl = "https://dashboard.eyepop.ai/sdkauth";
10211
- if (typeof auth.oAuth == "string") {
10212
- oauthUrl = auth.oAuth;
10336
+ var _EyePopSdk = class _EyePopSdk {
10337
+ static endpoint(opts = {}) {
10338
+ if (typeof opts.auth == "undefined") {
10339
+ if (typeof _EyePopSdk.defaultAuth == "undefined") {
10340
+ throw new Error("auth option or EYEPOP_SECRET_KEY environment variable is required");
10213
10341
  }
10214
- auth.session = authenticateBrowserSession(oauthUrl, eyepopUrl ?? "https://api.eyepop.ai", popId) ?? void 0;
10342
+ opts.auth = _EyePopSdk.defaultAuth;
10215
10343
  }
10216
- const options = {
10217
- auth,
10218
- popId: auth.session ? auth.session.popId : popId,
10219
- eyepopUrl: auth.session ? auth.session.eyepopUrl : eyepopUrl ?? "https://api.eyepop.ai",
10220
- autoStart,
10221
- stopJobs,
10222
- jobQueueLength,
10223
- logger
10224
- };
10225
- const endpoint = new Endpoint(options);
10344
+ if (typeof opts.popId == "undefined") {
10345
+ opts.popId = readEnv("EYEPOP_POP_ID");
10346
+ }
10347
+ if (typeof opts.eyepopUrl == "undefined") {
10348
+ opts.eyepopUrl = readEnv("EYEPOP_URL") || "https://api.eyepop.ai";
10349
+ }
10350
+ if (typeof opts.autoStart == "undefined") {
10351
+ opts.autoStart = true;
10352
+ }
10353
+ if (typeof opts.stopJobs == "undefined") {
10354
+ opts.stopJobs = true;
10355
+ }
10356
+ if (typeof opts.jobQueueLength == "undefined") {
10357
+ opts.jobQueueLength = 1024;
10358
+ }
10359
+ if (typeof opts.auth.oAuth2 != "undefined" && opts.popId) {
10360
+ if (typeof opts.auth.oAuth2 === "boolean") {
10361
+ if (opts.eyepopUrl.startsWith("https://staging-api.eyepop.ai")) {
10362
+ opts.auth = {
10363
+ oAuth2: {
10364
+ domain: "dev-eyepop.us.auth0.com",
10365
+ clientId: "jktx3YO2UnbkNPvr05PQWf26t1kNTJyg",
10366
+ audience: "https://dev-app.eyepop.ai",
10367
+ scope: "admin:clouds"
10368
+ }
10369
+ };
10370
+ } else {
10371
+ opts.auth = {
10372
+ oAuth2: {
10373
+ domain: "eyepop.us.auth0.com",
10374
+ clientId: "Lb9ubA9Hf3jlaqWLUx8XgA0zvotgViCl",
10375
+ audience: "https://api.eyepop.ai",
10376
+ scope: "admin:clouds"
10377
+ }
10378
+ };
10379
+ }
10380
+ }
10381
+ }
10382
+ if (opts.auth.session !== void 0) {
10383
+ if (opts.auth.session.popId) {
10384
+ opts.popId = opts.auth.session.popId;
10385
+ }
10386
+ if (opts.auth.session.eyepopUrl) {
10387
+ opts.eyepopUrl = opts.auth.session.eyepopUrl;
10388
+ }
10389
+ }
10390
+ const endpoint = new Endpoint(opts);
10226
10391
  return endpoint;
10227
10392
  }
10228
10393
  static plot(context) {
10229
10394
  return new EyePopPlot(context);
10230
10395
  }
10231
10396
  };
10232
- EyePopSdk.defaultAuth = {
10233
- secretKey: readEnv("EYEPOP_SECRET_KEY")
10234
- };
10397
+ _EyePopSdk.envSecretKey = readEnv("EYEPOP_SECRET_KEY");
10398
+ _EyePopSdk.defaultAuth = _EyePopSdk.envSecretKey ? {
10399
+ secretKey: _EyePopSdk.envSecretKey
10400
+ } : void 0;
10401
+ var EyePopSdk = _EyePopSdk;
10235
10402
  var src_default = EyePopSdk;
10236
10403
  export {
10237
10404
  EndpointState,