@eyepop.ai/eyepop 0.9.1 → 0.10.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
@@ -1,9 +1,4 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
1
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
2
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
3
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
4
  }) : x)(function(x) {
@@ -14,22 +9,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
14
9
  var __commonJS = (cb, mod) => function __require2() {
15
10
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
11
  };
17
- var __copyProps = (to, from, except, desc) => {
18
- if (from && typeof from === "object" || typeof from === "function") {
19
- for (let key of __getOwnPropNames(from))
20
- if (!__hasOwnProp.call(to, key) && key !== except)
21
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
- }
23
- return to;
24
- };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
- // If the importer is in node compatibility mode or this is not an ESM
27
- // file that has been converted to a CommonJS file using a Babel-
28
- // compatible transform (i.e. "__esModule" has not been set), then set
29
- // "default" to the CommonJS "module.exports" for node compatibility.
30
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
- mod
32
- ));
33
12
 
34
13
  // node_modules/mime-db/db.json
35
14
  var require_db = __commonJS({
@@ -8585,9 +8564,9 @@ var require_mime_types = __commonJS({
8585
8564
  return false;
8586
8565
  }
8587
8566
  var match = EXTRACT_TYPE_REGEXP.exec(type);
8588
- var mime2 = match && db[match[1].toLowerCase()];
8589
- if (mime2 && mime2.charset) {
8590
- return mime2.charset;
8567
+ var mime = match && db[match[1].toLowerCase()];
8568
+ if (mime && mime.charset) {
8569
+ return mime.charset;
8591
8570
  }
8592
8571
  if (match && TEXT_TYPE_REGEXP.test(match[1])) {
8593
8572
  return "UTF-8";
@@ -8598,16 +8577,16 @@ var require_mime_types = __commonJS({
8598
8577
  if (!str || typeof str !== "string") {
8599
8578
  return false;
8600
8579
  }
8601
- var mime2 = str.indexOf("/") === -1 ? exports.lookup(str) : str;
8602
- if (!mime2) {
8580
+ var mime = str.indexOf("/") === -1 ? exports.lookup(str) : str;
8581
+ if (!mime) {
8603
8582
  return false;
8604
8583
  }
8605
- if (mime2.indexOf("charset") === -1) {
8606
- var charset2 = exports.charset(mime2);
8584
+ if (mime.indexOf("charset") === -1) {
8585
+ var charset2 = exports.charset(mime);
8607
8586
  if (charset2)
8608
- mime2 += "; charset=" + charset2.toLowerCase();
8587
+ mime += "; charset=" + charset2.toLowerCase();
8609
8588
  }
8610
- return mime2;
8589
+ return mime;
8611
8590
  }
8612
8591
  function extension(type) {
8613
8592
  if (!type || typeof type !== "string") {
@@ -8633,8 +8612,8 @@ var require_mime_types = __commonJS({
8633
8612
  function populateMaps(extensions, types) {
8634
8613
  var preference = ["nginx", "apache", void 0, "iana"];
8635
8614
  Object.keys(db).forEach(function forEachMimeType(type) {
8636
- var mime2 = db[type];
8637
- var exts = mime2.extensions;
8615
+ var mime = db[type];
8616
+ var exts = mime.extensions;
8638
8617
  if (!exts || !exts.length) {
8639
8618
  return;
8640
8619
  }
@@ -8643,7 +8622,7 @@ var require_mime_types = __commonJS({
8643
8622
  var extension2 = exts[i];
8644
8623
  if (types[extension2]) {
8645
8624
  var from = preference.indexOf(db[types[extension2]].source);
8646
- var to = preference.indexOf(mime2.source);
8625
+ var to = preference.indexOf(mime.source);
8647
8626
  if (types[extension2] !== "application/octet-stream" && (from > to || from === to && types[extension2].substr(0, 12) === "application/")) {
8648
8627
  continue;
8649
8628
  }
@@ -8655,6 +8634,17 @@ var require_mime_types = __commonJS({
8655
8634
  }
8656
8635
  });
8657
8636
 
8637
+ // src/types.ts
8638
+ var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8639
+ EndpointState2["Idle"] = "Idle";
8640
+ EndpointState2["Busy"] = "Busy";
8641
+ EndpointState2["Authenticating"] = "Authenticating";
8642
+ EndpointState2["FetchConfig"] = "FetchConfig";
8643
+ EndpointState2["StartingPop"] = "StartingPop";
8644
+ EndpointState2["Error"] = "Error";
8645
+ return EndpointState2;
8646
+ })(EndpointState || {});
8647
+
8658
8648
  // src/streaming.ts
8659
8649
  var Stream = class _Stream {
8660
8650
  constructor(iterator, controller) {
@@ -8907,16 +8897,13 @@ function readableStreamAsyncIterable(stream) {
8907
8897
  }
8908
8898
 
8909
8899
  // src/jobs.ts
8910
- var _AbstractJob = class _AbstractJob {
8911
- constructor(baseUrl, pipelineId, authorizationHeader, dispatcher, requestLogger) {
8900
+ var AbstractJob = class {
8901
+ constructor(getSession, client, requestLogger) {
8912
8902
  this._responseStream = null;
8913
8903
  this._controller = new AbortController();
8914
- this._baseUrl = baseUrl.replace(/\/+$/, "");
8915
- this._pipelineId = pipelineId;
8916
- this._authorizationHeader = authorizationHeader;
8917
- this._dispatcher = dispatcher;
8904
+ this._getSession = getSession;
8905
+ this._client = client;
8918
8906
  this._requestLogger = requestLogger;
8919
- this.i = _AbstractJob.n++;
8920
8907
  }
8921
8908
  [Symbol.asyncIterator]() {
8922
8909
  if (!this._responseStream) {
@@ -8924,25 +8911,58 @@ var _AbstractJob = class _AbstractJob {
8924
8911
  }
8925
8912
  return Stream.iterFromReadableStream(this._responseStream, this._controller);
8926
8913
  }
8927
- async _fetch() {
8914
+ async startJob() {
8928
8915
  return Promise.reject("abstract class");
8929
8916
  }
8930
- start(done) {
8931
- this._responseStream = new Promise((resolve, reject) => {
8932
- const request = this._fetch();
8933
- request.then((response) => {
8934
- if (response.status != 200) {
8935
- reject(`upload error ${response.status}: ${response.text()}`);
8936
- } else if (!response.body) {
8937
- reject(`response body is empty`);
8938
- } else {
8939
- resolve(response.body);
8917
+ start(done, status) {
8918
+ this._responseStream = new Promise(async (resolve2, reject) => {
8919
+ try {
8920
+ let retries = 1;
8921
+ while (true) {
8922
+ try {
8923
+ const response = await this.startJob();
8924
+ if (response.status == 401) {
8925
+ status(response.status);
8926
+ if (retries--) {
8927
+ this._requestLogger.info("received 401 response, attempt to reauthorize once");
8928
+ } else {
8929
+ reject(`response ${response.status}: ${response.statusText}`);
8930
+ break;
8931
+ }
8932
+ } else if (response.status == 404) {
8933
+ status(response.status);
8934
+ if (retries--) {
8935
+ this._requestLogger.info("received 404 response, attempt to restart pop");
8936
+ } else {
8937
+ reject(`response ${response.status}: ${response.statusText}`);
8938
+ break;
8939
+ }
8940
+ } else if (response.status != 200) {
8941
+ reject(`upload error ${response.status}: ${await response.text()}`);
8942
+ break;
8943
+ } else if (!response.body) {
8944
+ reject(`response body is empty`);
8945
+ break;
8946
+ } else {
8947
+ resolve2(response.body);
8948
+ break;
8949
+ }
8950
+ } catch (error) {
8951
+ if (retries--) {
8952
+ this._requestLogger.info("unknown, attempt to restart pop (as if we received 404)", error);
8953
+ status(404);
8954
+ } else {
8955
+ status(500);
8956
+ reject(error);
8957
+ break;
8958
+ }
8959
+ }
8940
8960
  }
8941
- }).catch((reason) => {
8961
+ } catch (reason) {
8942
8962
  reject(reason);
8943
- }).finally(() => {
8963
+ } finally {
8944
8964
  done();
8945
- });
8965
+ }
8946
8966
  });
8947
8967
  return this;
8948
8968
  }
@@ -8950,26 +8970,26 @@ var _AbstractJob = class _AbstractJob {
8950
8970
  this._controller.abort("cancelled");
8951
8971
  }
8952
8972
  };
8953
- _AbstractJob.n = 0;
8954
- var AbstractJob = _AbstractJob;
8973
+ AbstractJob.n = 0;
8955
8974
  var UploadJob = class extends AbstractJob {
8956
8975
  get [Symbol.toStringTag]() {
8957
8976
  return "uploadJob";
8958
8977
  }
8959
- constructor(stream, mimeType, baseUrl, pipelineId, authorizationHeader, dispatcher, requestLogger) {
8960
- super(baseUrl, pipelineId, authorizationHeader, dispatcher, requestLogger);
8978
+ constructor(stream, mimeType, getSession, client, requestLogger) {
8979
+ super(getSession, client, requestLogger);
8961
8980
  this._uploadStream = stream;
8962
8981
  this._mimeType = mimeType;
8963
8982
  }
8964
- async _fetch() {
8983
+ async startJob() {
8984
+ const session = await this._getSession();
8965
8985
  const headers = {
8966
- "Authorization": this._authorizationHeader,
8986
+ "Authorization": `Bearer ${session.accessToken}`,
8967
8987
  "Accept": "application/jsonl",
8968
8988
  "Content-Type": this._mimeType
8969
8989
  };
8970
- const postUrl = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=queue&processing=sync`;
8990
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
8971
8991
  this._requestLogger.debug("before POST %s with stream as body");
8972
- const response = await fetch(postUrl, {
8992
+ const response = await this._client.fetch(postUrl, {
8973
8993
  headers,
8974
8994
  method: "POST",
8975
8995
  body: this._uploadStream,
@@ -8983,26 +9003,27 @@ var UploadJob = class extends AbstractJob {
8983
9003
  }
8984
9004
  };
8985
9005
  var LoadFromJob = class extends AbstractJob {
8986
- constructor(location, baseUrl, pipelineId, authorizationHeader, dispatcher, requestLogger) {
8987
- super(baseUrl, pipelineId, authorizationHeader, dispatcher, requestLogger);
9006
+ constructor(location, getSession, client, requestLogger) {
9007
+ super(getSession, client, requestLogger);
8988
9008
  this._location = location;
8989
9009
  }
8990
9010
  get [Symbol.toStringTag]() {
8991
9011
  return "loadFromJob";
8992
9012
  }
8993
- async _fetch() {
8994
- const headers = {
8995
- "Authorization": this._authorizationHeader,
8996
- "Accept": "application/jsonl",
8997
- "Content-Type": "application/json"
8998
- };
9013
+ async startJob() {
8999
9014
  const body = {
9000
9015
  "sourceType": "URL",
9001
9016
  "url": this._location
9002
9017
  };
9003
- const postUrl = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=queue&processing=sync`;
9004
- this._requestLogger.debug("before PATCH %s with url %s as source", postUrl, this._location);
9005
- const response = await fetch(postUrl, {
9018
+ const session = await this._getSession();
9019
+ const headers = {
9020
+ "Authorization": `Bearer ${session.accessToken}`,
9021
+ "Accept": "application/jsonl",
9022
+ "Content-Type": "application/json"
9023
+ };
9024
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9025
+ this._requestLogger.debug("before PATCH %s with url %s as source", patchUrl, this._location);
9026
+ const response = await this._client.fetch(patchUrl, {
9006
9027
  headers,
9007
9028
  method: "PATCH",
9008
9029
  body: JSON.stringify(body),
@@ -9010,26 +9031,234 @@ var LoadFromJob = class extends AbstractJob {
9010
9031
  // @ts-ignore
9011
9032
  dispatcher: this._dispatcher
9012
9033
  });
9013
- this._requestLogger.debug("after PATCH %s with url %s as source", postUrl, this._location);
9034
+ this._requestLogger.debug("after PATCH %s with url %s as source", patchUrl, this._location);
9014
9035
  return response;
9015
9036
  }
9016
9037
  };
9017
9038
 
9039
+ // src/shims/http_client.ts
9040
+ var createHttpClient;
9041
+ if ("document" in globalThis && "implementation" in globalThis.document) {
9042
+ class HttpClient2 {
9043
+ constructor() {
9044
+ }
9045
+ async fetch(input, init) {
9046
+ return fetch(input, init);
9047
+ }
9048
+ async close() {
9049
+ }
9050
+ }
9051
+ createHttpClient = async () => {
9052
+ return new HttpClient2();
9053
+ };
9054
+ } else {
9055
+ class HttpClient2 {
9056
+ constructor(agent) {
9057
+ this.agent = agent;
9058
+ }
9059
+ async fetch(input, init) {
9060
+ init || (init = {});
9061
+ init.dispatcher = this.agent;
9062
+ return fetch(input, init);
9063
+ }
9064
+ async close() {
9065
+ return this.agent.close();
9066
+ }
9067
+ }
9068
+ createHttpClient = async () => {
9069
+ const undici = __require("undici");
9070
+ const agent = new undici.Agent({
9071
+ keepAliveTimeout: 1e4,
9072
+ connections: 5
9073
+ });
9074
+ return new HttpClient2(agent);
9075
+ };
9076
+ }
9077
+
9078
+ // src/semaphore.ts
9079
+ var Semaphore = class {
9080
+ /**
9081
+ * Creates a semaphore.
9082
+ * @param permits The number of permits, i.e. strands of execution being allowed
9083
+ * to run in parallel.
9084
+ * This number can be initialized with a negative integer.
9085
+ */
9086
+ constructor(permits) {
9087
+ this.promiseResolverQueue = [];
9088
+ this.permits = permits;
9089
+ }
9090
+ /**
9091
+ * Returns the number of available permits.
9092
+ * @returns The number of available permits.
9093
+ */
9094
+ getPermits() {
9095
+ return this.permits;
9096
+ }
9097
+ /**
9098
+ * Returns a promise used to wait for a permit to become available. This method should be awaited on.
9099
+ * @returns A promise that gets resolved when execution is allowed to proceed.
9100
+ */
9101
+ async wait() {
9102
+ if (this.permits > 0) {
9103
+ this.permits -= 1;
9104
+ return Promise.resolve(true);
9105
+ }
9106
+ return new Promise((resolver) => this.promiseResolverQueue.push(resolver));
9107
+ }
9108
+ /**
9109
+ * Alias for {@linkcode Semaphore.wait}.
9110
+ * @returns A promise that gets resolved when execution is allowed to proceed.
9111
+ */
9112
+ async acquire() {
9113
+ return this.wait();
9114
+ }
9115
+ /**
9116
+ * Same as {@linkcode Semaphore.wait} except the promise returned gets resolved with false if no
9117
+ * permit becomes available in time.
9118
+ * @param milliseconds The time spent waiting before the wait is aborted. This is a lower bound,
9119
+ * you shouldn't rely on it being precise.
9120
+ * @returns A promise that gets resolved to true when execution is allowed to proceed or
9121
+ * false if the time given elapses before a permit becomes available.
9122
+ */
9123
+ async waitFor(milliseconds) {
9124
+ if (this.permits > 0) {
9125
+ this.permits -= 1;
9126
+ return Promise.resolve(true);
9127
+ }
9128
+ let resolver = (b) => void 0;
9129
+ const promise = new Promise((r) => {
9130
+ resolver = r;
9131
+ });
9132
+ this.promiseResolverQueue.push(resolver);
9133
+ setTimeout(() => {
9134
+ const index = this.promiseResolverQueue.indexOf(resolver);
9135
+ if (index !== -1) {
9136
+ this.promiseResolverQueue.splice(index, 1);
9137
+ } else {
9138
+ console.warn(`Semaphore.waitFor couldn't find its promise resolver in the queue`);
9139
+ }
9140
+ resolver(false);
9141
+ }, milliseconds);
9142
+ return promise;
9143
+ }
9144
+ /**
9145
+ * Synchronous function that tries to acquire a permit and returns true if successful, false otherwise.
9146
+ * @returns Whether a permit could be acquired.
9147
+ */
9148
+ tryAcquire() {
9149
+ if (this.permits > 0) {
9150
+ this.permits -= 1;
9151
+ return true;
9152
+ }
9153
+ return false;
9154
+ }
9155
+ /**
9156
+ * Acquires all permits that are currently available and returns the number of acquired permits.
9157
+ * @returns Number of acquired permits.
9158
+ */
9159
+ drainPermits() {
9160
+ if (this.permits > 0) {
9161
+ const permitCount = this.permits;
9162
+ this.permits = 0;
9163
+ return permitCount;
9164
+ }
9165
+ return 0;
9166
+ }
9167
+ /**
9168
+ * Increases the number of permits by one. If there are other functions waiting, one of them will
9169
+ * continue to execute in a future iteration of the event loop.
9170
+ */
9171
+ signal() {
9172
+ this.permits += 1;
9173
+ if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
9174
+ console.warn("Semaphore.permits should never be > 0 when there is someone waiting.");
9175
+ } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
9176
+ this.permits -= 1;
9177
+ const nextResolver = this.promiseResolverQueue.shift();
9178
+ if (nextResolver) {
9179
+ nextResolver(true);
9180
+ }
9181
+ }
9182
+ }
9183
+ /**
9184
+ * Alias for {@linkcode Semaphore.signal}.
9185
+ */
9186
+ release() {
9187
+ this.signal();
9188
+ }
9189
+ /**
9190
+ * Schedules func to be called once a permit becomes available.
9191
+ * Returns a promise that resolves to the return value of func.
9192
+ * @typeparam T The return type of func.
9193
+ * @param func The function to be executed.
9194
+ * @return A promise that gets resolved with the return value of the function.
9195
+ */
9196
+ async execute(func) {
9197
+ await this.wait();
9198
+ try {
9199
+ return await func();
9200
+ } finally {
9201
+ this.signal();
9202
+ }
9203
+ }
9204
+ };
9205
+
9206
+ // src/shims/local_file.ts
9207
+ var resolve;
9208
+ if ("document" in globalThis && "implementation" in globalThis.document) {
9209
+ const isFileLike = (value) => value != null && typeof value === "object" && typeof value.name === "string" && typeof value.lastModified === "number";
9210
+ resolve = async (params) => {
9211
+ if (params.file) {
9212
+ if (isFileLike(params.file)) {
9213
+ params = {
9214
+ mimeType: params.file.type,
9215
+ file: params.file
9216
+ };
9217
+ return params;
9218
+ }
9219
+ }
9220
+ throw new DOMException("resolving a path to a file is not supported in browser");
9221
+ };
9222
+ } else {
9223
+ resolve = async (params) => {
9224
+ if (params.file) {
9225
+ return params;
9226
+ }
9227
+ const mime = require_mime_types();
9228
+ const fs = __require("fs");
9229
+ let mimeType;
9230
+ let stream;
9231
+ if (params.filePath) {
9232
+ if (params.mimeType) {
9233
+ mimeType = params.mimeType;
9234
+ } else {
9235
+ mimeType = mime.lookup(params.filePath) || void 0;
9236
+ }
9237
+ stream = fs.createReadStream(params.filePath);
9238
+ }
9239
+ params = {
9240
+ filePath: params.filePath,
9241
+ mimeType,
9242
+ file: stream
9243
+ };
9244
+ return params;
9245
+ };
9246
+ }
9247
+
9018
9248
  // src/endpoint.ts
9019
- var import_mime_types = __toESM(require_mime_types());
9020
- import * as fs from "fs";
9021
- import { Agent } from "undici";
9022
- import { Semaphore } from "await-semaphore";
9023
9249
  import { pino } from "pino";
9024
- var _Endpoint = class _Endpoint {
9250
+ var Endpoint = class {
9025
9251
  constructor(options) {
9026
9252
  this._options = options;
9027
9253
  this._baseUrl = null;
9028
9254
  this._pipelineId = null;
9255
+ this._popName = null;
9029
9256
  this._token = null;
9030
9257
  this._expire_token_time = null;
9031
- this._dispatcher = null;
9032
- this._limit = null;
9258
+ this._client = null;
9259
+ this._state = "Idle" /* Idle */;
9260
+ this._stateChangeHandler = null;
9261
+ this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9033
9262
  let rootLogger;
9034
9263
  if (options.logger) {
9035
9264
  rootLogger = options.logger;
@@ -9039,56 +9268,79 @@ var _Endpoint = class _Endpoint {
9039
9268
  this._logger = rootLogger.child({ module: "eyepop" });
9040
9269
  this._requestLogger = this._logger.child({ module: "requests" });
9041
9270
  }
9042
- async upload(params) {
9043
- if (!this._baseUrl || !this._pipelineId || !this._dispatcher || !this._limit) {
9044
- return Promise.reject("endpoint not connected, use open() before upload()");
9271
+ onStateChanged(handler) {
9272
+ this._stateChangeHandler = handler;
9273
+ return this;
9274
+ }
9275
+ state() {
9276
+ return this._state;
9277
+ }
9278
+ pendingJobs() {
9279
+ return (this._options.jobQueueLength ?? 1024) - this._limit.getPermits();
9280
+ }
9281
+ popName() {
9282
+ return this._popName;
9283
+ }
9284
+ popId() {
9285
+ return this._options.popId || null;
9286
+ }
9287
+ async session() {
9288
+ await this.currentAccessToken();
9289
+ if (this._token == null || this._expire_token_time == null) {
9290
+ return Promise.reject("endpoint not connected");
9045
9291
  }
9046
- let mimeType = null;
9047
- let stream = null;
9048
- if (params.filePath) {
9049
- if (params.mimeType) {
9050
- mimeType = params.mimeType;
9051
- } else {
9052
- mimeType = import_mime_types.default.lookup(params.filePath);
9292
+ if (this._baseUrl == null || this._pipelineId == null) {
9293
+ await this.reconnect();
9294
+ if (this._baseUrl == null || this._pipelineId == null) {
9295
+ return Promise.reject("endpoint not connected");
9053
9296
  }
9054
- stream = fs.createReadStream(params.filePath);
9055
- } else if (params.stream) {
9056
- stream = params.stream;
9057
- mimeType = params.mimeType;
9058
9297
  }
9059
- if (!mimeType) {
9298
+ return {
9299
+ eyepopUrl: this._options.eyepopUrl,
9300
+ popId: this._options.popId,
9301
+ accessToken: this._token,
9302
+ validUntil: this._expire_token_time * 1e3,
9303
+ baseUrl: this._baseUrl,
9304
+ pipelineId: this._pipelineId
9305
+ };
9306
+ }
9307
+ async upload(params) {
9308
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
9309
+ return Promise.reject("endpoint not connected, use open() before upload()");
9310
+ }
9311
+ params = await resolve(params);
9312
+ if (!params.mimeType) {
9060
9313
  return Promise.reject("upload for streams requires a mimeType");
9061
9314
  }
9062
- let release = await this._limit.acquire();
9063
- const job = new UploadJob(
9064
- stream,
9065
- mimeType,
9066
- this._baseUrl,
9067
- this._pipelineId,
9068
- await this.authorizationHeader(),
9069
- this._dispatcher,
9070
- this._requestLogger
9071
- );
9072
- return job.start(release);
9315
+ await this._limit.acquire();
9316
+ this.updateState();
9317
+ const job = new UploadJob(params.file, params.mimeType, async () => {
9318
+ return this.session();
9319
+ }, this._client, this._requestLogger);
9320
+ return job.start(() => {
9321
+ this.jobDone(job);
9322
+ }, (statusCode) => {
9323
+ this.jobStatus(job, statusCode);
9324
+ });
9073
9325
  }
9074
9326
  async loadFrom(location) {
9075
- if (!this._baseUrl || !this._pipelineId || !this._dispatcher || !this._limit) {
9327
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
9076
9328
  throw new Error("endpoint not connected, use open() before loadFrom()");
9077
9329
  }
9078
- const release = await this._limit.acquire();
9079
- const job = new LoadFromJob(
9080
- location,
9081
- this._baseUrl,
9082
- this._pipelineId,
9083
- await this.authorizationHeader(),
9084
- this._dispatcher,
9085
- this._requestLogger
9086
- );
9087
- return job.start(release);
9330
+ await this._limit.acquire();
9331
+ this.updateState();
9332
+ const job = new LoadFromJob(location, async () => {
9333
+ return this.session();
9334
+ }, this._client, this._requestLogger);
9335
+ return job.start(() => {
9336
+ this.jobDone(job);
9337
+ }, (statusCode) => {
9338
+ this.jobStatus(job, statusCode);
9339
+ });
9088
9340
  }
9089
9341
  // noinspection TypeScriptValidateTypes
9090
9342
  async connect() {
9091
- if (this._dispatcher) {
9343
+ if (this._client) {
9092
9344
  console.info("endpoint already connected");
9093
9345
  return this;
9094
9346
  }
@@ -9098,95 +9350,153 @@ var _Endpoint = class _Endpoint {
9098
9350
  if (!this._options.popId) {
9099
9351
  return Promise.reject("option popId or environment variable EYEPOP_POP_ID is required");
9100
9352
  }
9101
- if (!this._options.secretKey) {
9102
- return Promise.reject("option secretKey or environment variable EYEPOP_SECRET_KEY is required");
9353
+ if (this._options.auth && this._options.auth.session) {
9354
+ this._token = this._options.auth.session.accessToken;
9355
+ this._expire_token_time = this._options.auth.session.validUntil / 1e3;
9356
+ } else {
9357
+ if (!this._options.auth || !this._options.auth.secretKey) {
9358
+ return Promise.reject("option secretKey or environment variable EYEPOP_SECRET_KEY is required");
9359
+ }
9103
9360
  }
9104
- this._dispatcher = new Agent({
9105
- keepAliveTimeout: 1e4,
9106
- pipelining: 100,
9107
- maxConcurrentStreams: 100,
9108
- maxCachedSessions: 100,
9109
- connections: _Endpoint.MAX_CONNECTIONS
9110
- });
9111
- this._limit = new Semaphore(this._options.jobQueueLength);
9112
- const config_url = `${this.eyepopUrl()}/pops/${this._options.popId}/config?auto_start=${this._options.autoStart}`;
9361
+ this._client = await createHttpClient();
9362
+ let result = await this.reconnect();
9363
+ if (this._baseUrl && this._options.stopJobs) {
9364
+ const stop_url = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=preempt&processing=sync`;
9365
+ const body = { "sourceType": "NONE" };
9366
+ const headers = {
9367
+ "Authorization": await this.authorizationHeader(),
9368
+ "Content-Type": "application/json"
9369
+ };
9370
+ this._requestLogger.debug("before PATCH %s", stop_url);
9371
+ let response = await this._client.fetch(stop_url, { method: "PATCH", headers, body: JSON.stringify(body) });
9372
+ if (response.status >= 300) {
9373
+ const message = await response.text();
9374
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9375
+ }
9376
+ this._requestLogger.debug("after PATCH %s", stop_url);
9377
+ }
9378
+ return result;
9379
+ }
9380
+ jobDone(job) {
9381
+ this._limit?.release();
9382
+ this.updateState();
9383
+ }
9384
+ updateState(newState = void 0) {
9385
+ const fromState = this._state;
9386
+ if (newState == void 0) {
9387
+ if (this._limit.getPermits() == this._options.jobQueueLength) {
9388
+ newState = "Idle" /* Idle */;
9389
+ } else {
9390
+ newState = "Busy" /* Busy */;
9391
+ }
9392
+ }
9393
+ this._state = newState;
9394
+ if (this._stateChangeHandler) {
9395
+ this._stateChangeHandler(fromState, this._state);
9396
+ }
9397
+ }
9398
+ jobStatus(job, statusCode) {
9399
+ if (statusCode == 404) {
9400
+ this._pipelineId = null;
9401
+ this._baseUrl = null;
9402
+ } else if (statusCode == 401) {
9403
+ this._token = null;
9404
+ this._expire_token_time = null;
9405
+ }
9406
+ }
9407
+ async reconnect() {
9408
+ if (!this._client) {
9409
+ return Promise.reject("endpoint not initialized");
9410
+ }
9411
+ const config_url = `${this.eyepopUrl()}/pops/${this._options.popId}/config?auto_start=false`;
9113
9412
  const headers = {
9114
9413
  "Authorization": await this.authorizationHeader()
9115
9414
  };
9415
+ this.updateState("FetchConfig" /* FetchConfig */);
9116
9416
  this._requestLogger.debug("before GET %s", config_url);
9117
- let response = await fetch(config_url, { headers });
9417
+ let response = await this._client.fetch(config_url, { headers });
9118
9418
  if (response.status == 401) {
9119
9419
  this._requestLogger.debug("after GET %s: 401, about to retry with fresh access token", config_url);
9120
9420
  this._token = null;
9121
9421
  const headers2 = {
9122
9422
  "Authorization": await this.authorizationHeader()
9123
9423
  };
9124
- response = await fetch(config_url, {
9125
- headers: headers2,
9424
+ this.updateState("FetchConfig" /* FetchConfig */);
9425
+ response = await this._client.fetch(config_url, {
9426
+ headers: headers2
9126
9427
  // @ts-ignore
9127
- dispatcher: this._dispatcher
9128
9428
  });
9129
9429
  }
9130
9430
  if (response.status != 200) {
9431
+ this.updateState("Error" /* Error */);
9131
9432
  const message = await response.text();
9132
9433
  return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9133
- }
9134
- const data = await response.json();
9135
- const config = data;
9136
- this._baseUrl = config.base_url;
9137
- this._pipelineId = config.pipeline_id;
9138
- this._requestLogger.debug("after GET %s: %s / %s", config_url, this._baseUrl, this._pipelineId);
9139
- if (!this._pipelineId || !this._baseUrl) {
9140
- return Promise.reject(`Pop not started`);
9141
- }
9142
- this._baseUrl = this._baseUrl.replace(/\/+$/, "");
9143
- if (this._options.stopJobs) {
9144
- const stop_url = `${this._baseUrl}/pipelines/${this._pipelineId}/source?mode=preempt&processing=sync`;
9145
- const body = { "sourceType": "NONE" };
9146
- const headers2 = {
9147
- "Authorization": await this.authorizationHeader(),
9148
- "Content-Type": "application/json"
9149
- };
9150
- this._requestLogger.debug("before PATCH %s", stop_url);
9151
- let response2 = await fetch(stop_url, { method: "PATCH", headers: headers2, body: JSON.stringify(body) });
9152
- if (response2.status >= 300) {
9153
- const message = await response2.text();
9154
- return Promise.reject(`Unexpected status ${response2.status}: ${message}`);
9434
+ } else {
9435
+ let config = await response.json();
9436
+ if (!config.base_url && this._options.autoStart) {
9437
+ const auto_start_config_url = `${this.eyepopUrl()}/pops/${this._options.popId}/config?auto_start=true`;
9438
+ this._requestLogger.debug("pop was not running, trying to autostart with: %s", auto_start_config_url);
9439
+ const headers2 = {
9440
+ "Authorization": await this.authorizationHeader()
9441
+ };
9442
+ this.updateState("StartingPop" /* StartingPop */);
9443
+ response = await this._client.fetch(auto_start_config_url, {
9444
+ headers: headers2
9445
+ // @ts-ignore
9446
+ });
9447
+ if (response.status != 200) {
9448
+ this.updateState("Error" /* Error */);
9449
+ const message = await response.text();
9450
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9451
+ }
9452
+ config = await response.json();
9155
9453
  }
9156
- this._requestLogger.debug("after PATCH %s", stop_url);
9454
+ this._baseUrl = config.base_url;
9455
+ this._pipelineId = config.pipeline_id;
9456
+ this._popName = config.name;
9457
+ this._requestLogger.debug("after GET %s: %s / %s", config_url, this._baseUrl, this._pipelineId);
9458
+ if (!this._pipelineId || !this._baseUrl) {
9459
+ return Promise.reject(`Pop not started`);
9460
+ }
9461
+ this._baseUrl = this._baseUrl.replace(/\/+$/, "");
9462
+ this.updateState();
9463
+ return Promise.resolve(this);
9157
9464
  }
9158
- return this;
9159
9465
  }
9160
9466
  async disconnect(wait = true) {
9161
9467
  if (wait && this._limit) {
9162
- for (let i = 0; i < this._options.jobQueueLength; i++) {
9468
+ for (let j = 0; j < this._options.jobQueueLength; j++) {
9163
9469
  await this._limit.acquire();
9164
9470
  }
9165
9471
  }
9166
- if (this._dispatcher) {
9167
- await this._dispatcher.close();
9168
- this._dispatcher = null;
9472
+ this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9473
+ if (this._client) {
9474
+ await this._client.close();
9475
+ this._client = null;
9169
9476
  }
9170
9477
  this._baseUrl = null;
9171
9478
  this._pipelineId = null;
9172
- this._limit = null;
9173
9479
  }
9174
9480
  async authorizationHeader() {
9175
- return `Bearer ${await this.accessToken()}`;
9481
+ return `Bearer ${await this.currentAccessToken()}`;
9176
9482
  }
9177
- async accessToken() {
9483
+ async currentAccessToken() {
9178
9484
  const now = Date.now() / 1e3;
9179
9485
  if (!this._token || this._expire_token_time < now) {
9180
- const body = { "secret_key": this._options.secretKey };
9486
+ this.updateState("Authenticating" /* Authenticating */);
9487
+ if (!this._client) {
9488
+ return Promise.reject("endpoint not connected");
9489
+ } else if (!this._options.auth || !this._options.auth.secretKey) {
9490
+ return Promise.reject("temporary access token expired");
9491
+ }
9492
+ const body = { "secret_key": this._options.auth.secretKey };
9181
9493
  const headers = { "Content-Type": "application/json" };
9182
9494
  const post_url = `${this.eyepopUrl()}/authentication/token`;
9183
9495
  this._requestLogger.debug("before POST %s", post_url);
9184
- const response = await fetch(post_url, {
9496
+ const response = await this._client.fetch(post_url, {
9185
9497
  method: "POST",
9186
9498
  headers,
9187
- body: JSON.stringify(body),
9188
- // @ts-ignore
9189
- dispatcher: this._dispatcher
9499
+ body: JSON.stringify(body)
9190
9500
  });
9191
9501
  if (response.status != 200) {
9192
9502
  const message = await response.text();
@@ -9197,6 +9507,7 @@ var _Endpoint = class _Endpoint {
9197
9507
  const token = data;
9198
9508
  this._token = token.access_token;
9199
9509
  this._expire_token_time = now + token.expires_in - 60;
9510
+ this.updateState();
9200
9511
  }
9201
9512
  this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
9202
9513
  return this._token;
@@ -9209,8 +9520,6 @@ var _Endpoint = class _Endpoint {
9209
9520
  }
9210
9521
  }
9211
9522
  };
9212
- _Endpoint.MAX_CONNECTIONS = 5;
9213
- var Endpoint = _Endpoint;
9214
9523
 
9215
9524
  // src/visualize.ts
9216
9525
  var _EyePopPlot = class _EyePopPlot {
@@ -9221,17 +9530,19 @@ var _EyePopPlot = class _EyePopPlot {
9221
9530
  }
9222
9531
  prediction(p) {
9223
9532
  if (p && p.objects) {
9533
+ const x_scale = this._context.canvas.width / p.source_width;
9534
+ const y_scale = this._context.canvas.height / p.source_height;
9224
9535
  for (let i = 0; i < p.objects.length; i++) {
9225
- this.object(p.objects[i]);
9536
+ this.object(p.objects[i], x_scale, y_scale);
9226
9537
  }
9227
9538
  }
9228
9539
  }
9229
- object(obj) {
9540
+ object(obj, x_scale, y_scale) {
9230
9541
  const context = this._context;
9231
- const x = obj.x;
9232
- const y = obj.y;
9233
- const w = obj.width;
9234
- const h = obj.height;
9542
+ const x = obj.x * x_scale;
9543
+ const y = obj.y * y_scale;
9544
+ const w = obj.width * x_scale;
9545
+ const h = obj.height * y_scale;
9235
9546
  context.beginPath();
9236
9547
  context.rect(obj.x, obj.y, obj.width, obj.height);
9237
9548
  context.lineWidth = 1;
@@ -9323,10 +9634,50 @@ _EyePopPlot.colors = {
9323
9634
  };
9324
9635
  var EyePopPlot = _EyePopPlot;
9325
9636
 
9637
+ // src/shims/browser_session.ts
9638
+ var authenticateBrowserSession;
9639
+ if ("document" in globalThis && "implementation" in globalThis.document) {
9640
+ authenticateBrowserSession = (oauthUrl, eyepopUrl, popId) => {
9641
+ const queryString = window.location.search;
9642
+ const urlParams = new URLSearchParams(queryString);
9643
+ let token = urlParams.get("pop_token");
9644
+ if (token) {
9645
+ urlParams.delete("pop_token");
9646
+ const url = `${window.location.pathname}?${urlParams}`;
9647
+ window.history.replaceState({}, document.title, url);
9648
+ return {
9649
+ eyepopUrl,
9650
+ popId,
9651
+ accessToken: token,
9652
+ validUntil: Date.now() + 60 * 60 * 1e3
9653
+ };
9654
+ } else {
9655
+ const url = `${oauthUrl}?url=${encodeURIComponent(window.location.href)}`;
9656
+ window.location.replace(url);
9657
+ return {
9658
+ eyepopUrl,
9659
+ popId,
9660
+ accessToken: "in-progress",
9661
+ validUntil: 0
9662
+ };
9663
+ }
9664
+ };
9665
+ } else {
9666
+ authenticateBrowserSession = (oauthUrl, eyepopUrl, popId) => {
9667
+ return null;
9668
+ };
9669
+ }
9670
+
9326
9671
  // src/index.ts
9672
+ var readEnv = (env) => {
9673
+ if (typeof process !== "undefined") {
9674
+ return process.env?.[env] ?? void 0;
9675
+ }
9676
+ return void 0;
9677
+ };
9327
9678
  var EyePopSdk = class {
9328
9679
  static endpoint({
9329
- secretKey = readEnv("EYEPOP_SECRET_KEY"),
9680
+ auth = this.defaultAuth,
9330
9681
  popId = readEnv("EYEPOP_POP_ID"),
9331
9682
  eyepopUrl = readEnv("EYEPOP_URL"),
9332
9683
  autoStart = true,
@@ -9335,10 +9686,17 @@ var EyePopSdk = class {
9335
9686
  logger,
9336
9687
  ...opts
9337
9688
  } = {}) {
9689
+ if (auth.oAuth && popId) {
9690
+ let oauthUrl = "https://dashboard.eyepop.ai/sdkauth";
9691
+ if (typeof auth.oAuth == "string") {
9692
+ oauthUrl = auth.oAuth;
9693
+ }
9694
+ auth.session = authenticateBrowserSession(oauthUrl, eyepopUrl ?? "https://api.eyepop.ai", popId) ?? void 0;
9695
+ }
9338
9696
  const options = {
9339
- secretKey,
9340
- popId,
9341
- eyepopUrl: eyepopUrl ?? "https://api.eyepop.ai",
9697
+ auth,
9698
+ popId: auth.session ? auth.session.popId : popId,
9699
+ eyepopUrl: auth.session ? auth.session.eyepopUrl : eyepopUrl ?? "https://api.eyepop.ai",
9342
9700
  autoStart,
9343
9701
  stopJobs,
9344
9702
  jobQueueLength,
@@ -9351,14 +9709,14 @@ var EyePopSdk = class {
9351
9709
  return new EyePopPlot(context);
9352
9710
  }
9353
9711
  };
9354
- var readEnv = (env) => {
9355
- if (typeof process !== "undefined") {
9356
- return process.env?.[env] ?? void 0;
9357
- }
9358
- return void 0;
9712
+ EyePopSdk.defaultAuth = {
9713
+ secretKey: readEnv("EYEPOP_SECRET_KEY")
9359
9714
  };
9715
+ var src_default = EyePopSdk;
9360
9716
  export {
9361
- EyePopSdk
9717
+ EndpointState,
9718
+ EyePopSdk,
9719
+ src_default as default
9362
9720
  };
9363
9721
  /*! Bundled license information:
9364
9722