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