@mediaviz/sdk 1.7.0 → 1.8.0-dev.100

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/sdk.esm.js CHANGED
@@ -459,6 +459,448 @@ function requireTypes () {
459
459
  return types;
460
460
  }
461
461
 
462
+ var src$1;
463
+ var hasRequiredSrc$1;
464
+
465
+ function requireSrc$1 () {
466
+ if (hasRequiredSrc$1) return src$1;
467
+ hasRequiredSrc$1 = 1;
468
+
469
+ const { OAuthClient } = requireClient();
470
+ const { OAuthError } = requireErrors();
471
+ const { OAuthErrorCode } = requireTypes();
472
+
473
+ src$1 = { OAuthClient, OAuthError, OAuthErrorCode };
474
+ return src$1;
475
+ }
476
+
477
+ var srcExports$1 = requireSrc$1();
478
+ var _oauth = /*@__PURE__*/getDefaultExportFromCjs(srcExports$1);
479
+
480
+ // Auto-generated — do not edit
481
+ const { OAuthClient, OAuthError, OAuthErrorCode } = _oauth;
482
+
483
+ var signing;
484
+ var hasRequiredSigning;
485
+
486
+ function requireSigning () {
487
+ if (hasRequiredSigning) return signing;
488
+ hasRequiredSigning = 1;
489
+
490
+ // HMAC scheme shared with the MediaViz producer: X-Signature = 'sha256=' +
491
+ // hex(HMAC-SHA256(secret, `${X-Timestamp}.` + rawBody)). The signing string is
492
+ // the timestamp joined to the *raw request body bytes* — never re-serialized
493
+ // JSON. Verification accepts the current and (during rotation) previous
494
+ // secret, rejects timestamps outside the skew window, and compares digests in
495
+ // constant time. Web Crypto only, so the same code runs in Node >= 18 and
496
+ // browsers.
497
+
498
+ const _encoder = new TextEncoder();
499
+
500
+ async function signWebhookPayload(secret, timestamp, rawBody) {
501
+ const key = await globalThis.crypto.subtle.importKey(
502
+ 'raw', _encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
503
+ const payload = _concat(_encoder.encode(`${timestamp}.`), toBytes(rawBody));
504
+ const digest = await globalThis.crypto.subtle.sign('HMAC', key, payload);
505
+ return 'sha256=' + _hex(new Uint8Array(digest));
506
+ }
507
+
508
+ async function verifyWebhookSignature(secretCurrent, secretPrevious, headers, rawBody, options = {}) {
509
+ const h = lowerHeaders(headers);
510
+ const timestamp = h['x-timestamp'];
511
+ const signature = h['x-signature'];
512
+ if (!timestamp || !signature) return false;
513
+ if (!/^-?\d+$/.test(String(timestamp))) return false;
514
+ const skewToleranceS = options.skewToleranceS ?? 300;
515
+ const now = options.now ?? Math.floor(Date.now() / 1000);
516
+ if (Math.abs(now - Number(timestamp)) > skewToleranceS) return false;
517
+ for (const secret of [secretCurrent, secretPrevious]) {
518
+ if (!secret) continue;
519
+ const expected = await signWebhookPayload(secret, String(timestamp), rawBody);
520
+ if (_constantTimeEqual(expected, String(signature))) return true;
521
+ }
522
+ return false;
523
+ }
524
+
525
+ // helpers
526
+ function toBytes(rawBody) {
527
+ if (typeof rawBody === 'string') return _encoder.encode(rawBody);
528
+ if (rawBody instanceof Uint8Array) return rawBody;
529
+ if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
530
+ throw new TypeError('rawBody must be a string, Uint8Array, or ArrayBuffer');
531
+ }
532
+
533
+ function lowerHeaders(headers) {
534
+ const out = {};
535
+ const entries = typeof headers?.entries === 'function' ? headers.entries() : Object.entries(headers ?? {});
536
+ for (const [k, v] of entries) out[String(k).toLowerCase()] = v;
537
+ return out;
538
+ }
539
+
540
+ function _concat(a, b) {
541
+ const out = new Uint8Array(a.length + b.length);
542
+ out.set(a, 0);
543
+ out.set(b, a.length);
544
+ return out;
545
+ }
546
+
547
+ function _hex(bytes) {
548
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
549
+ }
550
+
551
+ function _constantTimeEqual(a, b) {
552
+ if (a.length !== b.length) return false;
553
+ let diff = 0;
554
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
555
+ return diff === 0;
556
+ }
557
+
558
+ signing = { signWebhookPayload, verifyWebhookSignature, toBytes, lowerHeaders };
559
+ return signing;
560
+ }
561
+
562
+ var store;
563
+ var hasRequiredStore;
564
+
565
+ function requireStore () {
566
+ if (hasRequiredStore) return store;
567
+ hasRequiredStore = 1;
568
+
569
+ // Persistence seam for the webhook consumer: dedupe ledger, signing secrets,
570
+ // and pull cursors. The default in-memory store suits a single long-lived
571
+ // process (worker, dev server); multi-process consumers supply an object with
572
+ // the same shape backed by Redis or a database.
573
+
574
+ const MAX_SEEN_ENTRIES = 100000;
575
+
576
+ class InMemoryWebhookStore {
577
+ constructor() {
578
+ this._seen = new Map(); // eventId -> expiresAt (ms)
579
+ this._secrets = new Map(); // subscriptionId -> {current, previous, previousExpiresAt}
580
+ this._cursors = new Map(); // subscriptionId -> cursor
581
+ }
582
+
583
+ markSeen(eventId, ttlS) {
584
+ const now = Date.now();
585
+ const expiresAt = this._seen.get(eventId);
586
+ if (expiresAt !== undefined && expiresAt > now) return false;
587
+ this._seen.delete(eventId);
588
+ this._seen.set(eventId, now + ttlS * 1000);
589
+ this._prune(now);
590
+ return true;
591
+ }
592
+
593
+ unmarkSeen(eventId) {
594
+ this._seen.delete(eventId);
595
+ }
596
+
597
+ saveSecret(subscriptionId, secret) {
598
+ this._secrets.set(subscriptionId, { current: secret, previous: null, previousExpiresAt: 0 });
599
+ }
600
+
601
+ rotateSecret(subscriptionId, newSecret, windowS) {
602
+ const entry = this._secrets.get(subscriptionId);
603
+ this._secrets.set(subscriptionId, {
604
+ current: newSecret,
605
+ previous: entry?.current ?? null,
606
+ previousExpiresAt: Date.now() + windowS * 1000,
607
+ });
608
+ }
609
+
610
+ getSecrets(subscriptionId) {
611
+ const entry = this._secrets.get(subscriptionId);
612
+ if (!entry) return [null, null];
613
+ const previous = entry.previous && entry.previousExpiresAt > Date.now() ? entry.previous : null;
614
+ return [entry.current, previous];
615
+ }
616
+
617
+ getCursor(subscriptionId) {
618
+ return this._cursors.get(subscriptionId) ?? null;
619
+ }
620
+
621
+ setCursor(subscriptionId, cursor) {
622
+ this._cursors.set(subscriptionId, cursor);
623
+ }
624
+
625
+ _prune(now) {
626
+ if (this._seen.size <= MAX_SEEN_ENTRIES) return;
627
+ for (const [id, expiresAt] of this._seen) {
628
+ if (expiresAt <= now) this._seen.delete(id);
629
+ if (this._seen.size <= MAX_SEEN_ENTRIES) return;
630
+ }
631
+ // still over the cap: evict oldest insertions
632
+ for (const id of this._seen.keys()) {
633
+ this._seen.delete(id);
634
+ if (this._seen.size <= MAX_SEEN_ENTRIES) return;
635
+ }
636
+ }
637
+ }
638
+
639
+ store = { InMemoryWebhookStore };
640
+ return store;
641
+ }
642
+
643
+ var consumer;
644
+ var hasRequiredConsumer;
645
+
646
+ function requireConsumer () {
647
+ if (hasRequiredConsumer) return consumer;
648
+ hasRequiredConsumer = 1;
649
+
650
+ const { verifyWebhookSignature, toBytes, lowerHeaders } = requireSigning();
651
+ const { InMemoryWebhookStore } = requireStore();
652
+
653
+ // Photo columns each producer model writes — used to select a model's results
654
+ // out of a fetched PhotoDisplay. Mirrors the producer vocabulary; personhood is
655
+ // predicate-based and writes no columns.
656
+ const WEBHOOK_MODEL_COLUMNS = {
657
+ blur: ['blur_value'],
658
+ colors: ['main_color_palette', 'color_averages', 'hue_values', 'light_values', 'saturation_values'],
659
+ face_recognition: ['number_of_faces', 'bounding_boxes_from_faces_model'],
660
+ image_classification: ['labels_from_classifications_model', 'feature_extraction_output'],
661
+ image_comparison: ['average_hash'],
662
+ image_describe: ['topic'],
663
+ ocr: ['ocr_output'],
664
+ similarity: ['similar_photo_ids_high', 'similar_photo_ids_medium', 'similar_photo_ids_low'],
665
+ evidence: ['evidence_score', 'aesthetic_score', 'face_score', 'content_score', 'similarity_set_ranking'],
666
+ personhood: [],
667
+ };
668
+
669
+ const WEBHOOK_OUTCOME_MODELS = {
670
+ curation: ['blur', 'colors', 'face_recognition', 'image_classification', 'image_comparison', 'similarity', 'evidence'],
671
+ similarity: ['image_classification', 'image_comparison', 'similarity'],
672
+ persons: ['face_recognition', 'personhood'],
673
+ };
674
+
675
+ // Client-side counterpart of the MediaViz webhook producer, giving photo-by-photo
676
+ // progress as a project upload is analyzed.
677
+ //
678
+ // Receiver half (no auth needed): classify inbound POSTs, echo the subscription
679
+ // verification challenge, verify signed `target.completed` deliveries (HMAC +
680
+ // timestamp skew), dedupe on event_id, and dispatch each new event to a single
681
+ // onEvent callback. `handleRequest` is framework-agnostic glue — adapt it to
682
+ // express/lambda/etc. in a few lines by feeding it the raw body and headers.
683
+ //
684
+ // API half (requires an authenticated client): subscription lifecycle
685
+ // orchestration — register/confirm/rotate store the signing secret the moment
686
+ // the producer issues it — plus history pulls that share the push dedupe
687
+ // ledger, and out-of-band result fetches (webhooks carry a pointer, never the
688
+ // result itself).
689
+ class WebhookConsumer {
690
+ constructor(ctx, subscriptionApi, options = {}) {
691
+ this._ctx = ctx;
692
+ this._api = subscriptionApi;
693
+ this.store = options.store ?? new InMemoryWebhookStore();
694
+ this.skewToleranceS = options.skewToleranceS ?? 300;
695
+ this.rotationWindowS = options.rotationWindowS ?? 86400;
696
+ this.dedupeTtlS = options.dedupeTtlS ?? 604800;
697
+ this.pageLimit = options.pageLimit ?? 100;
698
+ this._onEvent = options.onEvent ?? null;
699
+ }
700
+
701
+ // ── receiver half ─────────────────────────────────────────────────────────
702
+
703
+ onEvent(fn) {
704
+ this._onEvent = fn;
705
+ return fn;
706
+ }
707
+
708
+ useStore(store) {
709
+ this.store = store;
710
+ }
711
+
712
+ classifyMessage(body) {
713
+ const type = body?.event_type;
714
+ if (type === 'subscription.verification') return 'verification';
715
+ if (type === 'target.completed') return 'delivery';
716
+ return 'unknown';
717
+ }
718
+
719
+ handleChallenge(body) {
720
+ return { challenge: body.challenge };
721
+ }
722
+
723
+ async handleDelivery(headers, rawBody) {
724
+ let event;
725
+ try {
726
+ event = this._parseDelivery(rawBody);
727
+ } catch {
728
+ return 'bad_signature';
729
+ }
730
+ const [current, previous] = this.store.getSecrets(String(event.subscription_id));
731
+ const ok = await verifyWebhookSignature(current, previous, headers, rawBody, { skewToleranceS: this.skewToleranceS });
732
+ if (!ok) return 'bad_signature';
733
+ return this._dispatch(event);
734
+ }
735
+
736
+ async handleRequest(headers, rawBody) {
737
+ let body;
738
+ try {
739
+ body = JSON.parse(_text(rawBody));
740
+ } catch {
741
+ return { status: 400, body: { detail: 'invalid JSON' } };
742
+ }
743
+ switch (this.classifyMessage(body)) {
744
+ case 'verification':
745
+ return { status: 200, body: this.handleChallenge(body) };
746
+ case 'delivery': {
747
+ const ack = await this.handleDelivery(lowerHeaders(headers), rawBody);
748
+ if (ack === 'bad_signature') return { status: 401, body: { detail: 'bad signature' } };
749
+ return { status: 200, body: { status: ack } };
750
+ }
751
+ default:
752
+ return { status: 400, body: { detail: 'unrecognized event_type' } };
753
+ }
754
+ }
755
+
756
+ // ── API half ──────────────────────────────────────────────────────────────
757
+
758
+ async register(projectTableName, callbackUrl, targets) {
759
+ const res = await this._api.createSubscription(projectTableName, callbackUrl, targets);
760
+ this._saveSecretFrom(res);
761
+ return res;
762
+ }
763
+
764
+ async confirm(subscriptionId) {
765
+ const res = await this._api.verifySubscription(subscriptionId);
766
+ this._saveSecretFrom(res);
767
+ return res;
768
+ }
769
+
770
+ async rotateSecret(subscriptionId) {
771
+ const res = await this._api.rotateSubscriptionSecret(subscriptionId);
772
+ this.store.rotateSecret(String(subscriptionId), res.signing_secret, this.rotationWindowS);
773
+ return res.signing_secret;
774
+ }
775
+
776
+ async updateCallback(subscriptionId, callbackUrl) {
777
+ // A callback change resets the subscription to pending_verification and
778
+ // pauses push until the new URL passes the challenge — confirm right away.
779
+ await this._api.updateSubscription(subscriptionId, { callbackUrl });
780
+ return this.confirm(subscriptionId);
781
+ }
782
+
783
+ async disable(subscriptionId) {
784
+ return this._api.deleteSubscription(subscriptionId);
785
+ }
786
+
787
+ async listSubscriptions() {
788
+ return this._api.listSubscriptions();
789
+ }
790
+
791
+ async pullEvents(subscriptionId, { since, limit } = {}) {
792
+ return this._api.listSubscriptionEvents(subscriptionId, { since, limit: limit ?? this.pageLimit });
793
+ }
794
+
795
+ async reconcile(subscriptionId, projectTableName) {
796
+ const id = String(subscriptionId);
797
+ let cursor = this.store.getCursor(id);
798
+ let pulled = 0;
799
+ let dispatched = 0;
800
+ for (;;) {
801
+ const page = await this._api.listSubscriptionEvents(id, { since: cursor ?? undefined, limit: this.pageLimit });
802
+ const events = page?.events ?? [];
803
+ pulled += events.length;
804
+ for (const ev of events) {
805
+ const ack = await this._dispatch({
806
+ event_id: ev.event_id,
807
+ event_type: 'target.completed',
808
+ schema_version: '1',
809
+ subscription_id: id,
810
+ project_table_name: projectTableName,
811
+ photo_id: ev.photo_id,
812
+ target: ev.target,
813
+ completed_at: ev.completed_at,
814
+ result_location: this._ctx.baseUrl + _photoPath(projectTableName, ev.photo_id),
815
+ });
816
+ if (ack === 'handled') dispatched += 1;
817
+ }
818
+ if (page?.next_cursor && page.next_cursor !== cursor) {
819
+ cursor = page.next_cursor;
820
+ this.store.setCursor(id, cursor);
821
+ }
822
+ if (!events.length || events.length < this.pageLimit) break;
823
+ }
824
+ return { pulled, dispatched };
825
+ }
826
+
827
+ async fetchResult(event) {
828
+ this._ctx.requireTokens();
829
+ const path = _photoPath(event.project_table_name, event.photo_id);
830
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
831
+ return { photo: data, selected: selectResultColumns(data, event.target) };
832
+ }
833
+
834
+ saveSecret(subscriptionId, secret) {
835
+ this.store.saveSecret(String(subscriptionId), secret);
836
+ }
837
+
838
+ // ── internal ──────────────────────────────────────────────────────────────
839
+
840
+ _saveSecretFrom(res) {
841
+ if (res?.signing_secret && res?.subscription_id != null) {
842
+ this.store.saveSecret(String(res.subscription_id), res.signing_secret);
843
+ }
844
+ }
845
+
846
+ _parseDelivery(rawBody) {
847
+ const event = JSON.parse(_text(rawBody));
848
+ for (const key of ['event_id', 'subscription_id', 'target']) {
849
+ if (typeof event?.[key] !== 'string' || !event[key]) throw new Error(`missing ${key}`);
850
+ }
851
+ if (!Number.isInteger(event.photo_id)) throw new Error('photo_id must be an integer');
852
+ return event;
853
+ }
854
+
855
+ async _dispatch(event) {
856
+ if (!this.store.markSeen(String(event.event_id), this.dedupeTtlS)) return 'duplicate';
857
+ if (this._onEvent) {
858
+ try {
859
+ await this._onEvent(event);
860
+ } catch (e) {
861
+ // release the dedupe claim so the producer's retry redelivers
862
+ this.store.unmarkSeen(String(event.event_id));
863
+ throw e;
864
+ }
865
+ }
866
+ return 'handled';
867
+ }
868
+ }
869
+
870
+ // helpers
871
+ function selectResultColumns(photo, target) {
872
+ if (target === 'all') return null;
873
+ const sep = String(target).indexOf(':');
874
+ const kind = sep === -1 ? String(target) : String(target).slice(0, sep);
875
+ const name = sep === -1 ? '' : String(target).slice(sep + 1);
876
+ let columns;
877
+ if (kind === 'model') {
878
+ columns = WEBHOOK_MODEL_COLUMNS[name];
879
+ if (!columns) throw new Error(`unknown model '${name}'`);
880
+ } else if (kind === 'outcome') {
881
+ const models = WEBHOOK_OUTCOME_MODELS[name];
882
+ if (!models) throw new Error(`unknown outcome '${name}'`);
883
+ columns = [...new Set(models.flatMap((m) => WEBHOOK_MODEL_COLUMNS[m]))];
884
+ } else {
885
+ throw new Error(`unknown target grammar '${target}' (expected 'model:<name>', 'outcome:<name>', or 'all')`);
886
+ }
887
+ const selected = {};
888
+ for (const c of columns) selected[c] = photo?.[c] ?? null;
889
+ return selected;
890
+ }
891
+
892
+ function _photoPath(projectTableName, photoId) {
893
+ return `/api/v1/photos/${encodeURIComponent(projectTableName)}/${encodeURIComponent(String(photoId))}`;
894
+ }
895
+
896
+ function _text(rawBody) {
897
+ return typeof rawBody === 'string' ? rawBody : new TextDecoder().decode(toBytes(rawBody));
898
+ }
899
+
900
+ consumer = { WebhookConsumer, selectResultColumns, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS };
901
+ return consumer;
902
+ }
903
+
462
904
  var src;
463
905
  var hasRequiredSrc;
464
906
 
@@ -466,19 +908,19 @@ function requireSrc () {
466
908
  if (hasRequiredSrc) return src;
467
909
  hasRequiredSrc = 1;
468
910
 
469
- const { OAuthClient } = requireClient();
470
- const { OAuthError } = requireErrors();
471
- const { OAuthErrorCode } = requireTypes();
911
+ const { WebhookConsumer, selectResultColumns, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS } = requireConsumer();
912
+ const { InMemoryWebhookStore } = requireStore();
913
+ const { signWebhookPayload, verifyWebhookSignature } = requireSigning();
472
914
 
473
- src = { OAuthClient, OAuthError, OAuthErrorCode };
915
+ src = { WebhookConsumer, InMemoryWebhookStore, signWebhookPayload, verifyWebhookSignature, selectResultColumns, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS };
474
916
  return src;
475
917
  }
476
918
 
477
919
  var srcExports = requireSrc();
478
- var _oauth = /*@__PURE__*/getDefaultExportFromCjs(srcExports);
920
+ var _webhooks = /*@__PURE__*/getDefaultExportFromCjs(srcExports);
479
921
 
480
922
  // Auto-generated — do not edit
481
- const { OAuthClient, OAuthError, OAuthErrorCode } = _oauth;
923
+ const { WebhookConsumer, InMemoryWebhookStore, signWebhookPayload, verifyWebhookSignature, selectResultColumns, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS } = _webhooks;
482
924
 
483
925
  class AiModelCredits {
484
926
  constructor(ctx) { this._ctx = ctx; }
@@ -525,7 +967,7 @@ class Company {
525
967
  }
526
968
  }
527
969
 
528
- function stripUndef$6(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
970
+ function stripUndef$7(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
529
971
 
530
972
  class CuratedAlbums {
531
973
  constructor(ctx) { this._ctx = ctx; }
@@ -533,7 +975,7 @@ class CuratedAlbums {
533
975
  async createCuratedAlbum(projectTableName, name, description = undefined, confidenceValue = undefined) {
534
976
  this._ctx.requireTokens();
535
977
  const path = `/api/v1/curated_album/project/${encodeURIComponent(projectTableName)}`;
536
- const body = stripUndef$6({
978
+ const body = stripUndef$7({
537
979
  name: name,
538
980
  description: description,
539
981
  confidence_value: confidenceValue,
@@ -587,7 +1029,7 @@ class CuratedAlbums {
587
1029
  async updateCuratedAlbum(albumId, { name, description, confidenceValue } = {}) {
588
1030
  this._ctx.requireTokens();
589
1031
  const path = `/api/v1/curated_album/${encodeURIComponent(albumId)}`;
590
- const body = stripUndef$6({
1032
+ const body = stripUndef$7({
591
1033
  name: name,
592
1034
  description: description,
593
1035
  confidence_value: confidenceValue,
@@ -611,7 +1053,7 @@ class CuratedAlbums {
611
1053
  }
612
1054
  }
613
1055
 
614
- function stripUndef$5(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1056
+ function stripUndef$6(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
615
1057
 
616
1058
  class CustomAlbums {
617
1059
  constructor(ctx) { this._ctx = ctx; }
@@ -659,7 +1101,7 @@ class CustomAlbums {
659
1101
  async createProjectCustomAlbum(projectTableName, { name, description, photoIdInclusionList, photoIdRemovalList } = {}) {
660
1102
  this._ctx.requireTokens();
661
1103
  const path = `/api/v1/custom_album/project/${encodeURIComponent(projectTableName)}`;
662
- const body = stripUndef$5({
1104
+ const body = stripUndef$6({
663
1105
  name: name,
664
1106
  description: description,
665
1107
  photo_id_inclusion_list: photoIdInclusionList,
@@ -672,7 +1114,7 @@ class CustomAlbums {
672
1114
  async updateCustomAlbum(albumId, { name, description, photoIdInclusionList, photoIdRemovalList } = {}) {
673
1115
  this._ctx.requireTokens();
674
1116
  const path = `/api/v1/custom_album/${encodeURIComponent(albumId)}`;
675
- const body = stripUndef$5({
1117
+ const body = stripUndef$6({
676
1118
  name: name,
677
1119
  description: description,
678
1120
  photo_id_inclusion_list: photoIdInclusionList,
@@ -772,7 +1214,7 @@ async function handleResponse(response) {
772
1214
  }
773
1215
  }
774
1216
 
775
- function stripUndef$4(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1217
+ function stripUndef$5(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
776
1218
 
777
1219
  class EmailTokens {
778
1220
  constructor(ctx) { this._ctx = ctx; }
@@ -803,7 +1245,7 @@ class EmailTokens {
803
1245
  }
804
1246
 
805
1247
  async validateToken(token) {
806
- const body = stripUndef$4({
1248
+ const body = stripUndef$5({
807
1249
  token: token,
808
1250
  });
809
1251
  const resp = await fetch(this._ctx.baseUrl + `/api/v1/validate-token`, {
@@ -815,7 +1257,7 @@ class EmailTokens {
815
1257
  }
816
1258
 
817
1259
  async resetPassword(token, newPassword) {
818
- const body = stripUndef$4({
1260
+ const body = stripUndef$5({
819
1261
  token: token,
820
1262
  new_password: newPassword,
821
1263
  });
@@ -854,7 +1296,7 @@ class Health {
854
1296
  }
855
1297
  }
856
1298
 
857
- function stripUndef$3(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1299
+ function stripUndef$4(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
858
1300
 
859
1301
  class Keywords {
860
1302
  constructor(ctx) { this._ctx = ctx; }
@@ -862,7 +1304,7 @@ class Keywords {
862
1304
  async createKeywordFilteringList(name, projectList = undefined) {
863
1305
  this._ctx.requireTokens();
864
1306
  const path = `/api/v1/keyword/`;
865
- const body = stripUndef$3({
1307
+ const body = stripUndef$4({
866
1308
  name: name,
867
1309
  project_list: projectList,
868
1310
  });
@@ -915,7 +1357,7 @@ class Keywords {
915
1357
  async updateKeywordFilteringListLabels(keywordListId, listKeywordsToInclude, listKeywordsToExclude) {
916
1358
  this._ctx.requireTokens();
917
1359
  const path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}`;
918
- const body = stripUndef$3({
1360
+ const body = stripUndef$4({
919
1361
  list_keywords_to_include: listKeywordsToInclude,
920
1362
  list_keywords_to_exclude: listKeywordsToExclude,
921
1363
  });
@@ -926,7 +1368,7 @@ class Keywords {
926
1368
  async updateKeywordFilteringListDetails(keywordListId, { name, projectList } = {}) {
927
1369
  this._ctx.requireTokens();
928
1370
  const path = `/api/v1/keyword/details/${encodeURIComponent(keywordListId)}`;
929
- const body = stripUndef$3({
1371
+ const body = stripUndef$4({
930
1372
  name: name,
931
1373
  project_list: projectList,
932
1374
  });
@@ -1125,6 +1567,13 @@ class Person {
1125
1567
  return data;
1126
1568
  }
1127
1569
 
1570
+ async removePersonName(projectTableName, personId) {
1571
+ this._ctx.requireTokens();
1572
+ const path = `/api/v1/person/${encodeURIComponent(projectTableName)}/remove_name/${encodeURIComponent(personId)}`;
1573
+ const { data } = await this._ctx.client.request(path, 'PUT', this._ctx.accessToken, this._ctx.refreshToken);
1574
+ return data;
1575
+ }
1576
+
1128
1577
  async combinePersons(projectTableName, destinationPersonId, oldPersonId) {
1129
1578
  this._ctx.requireTokens();
1130
1579
  const path = `/api/v1/person/${encodeURIComponent(projectTableName)}/combine/${encodeURIComponent(destinationPersonId)}/${encodeURIComponent(oldPersonId)}`;
@@ -1175,14 +1624,21 @@ class Person {
1175
1624
  const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1176
1625
  return data;
1177
1626
  }
1627
+
1628
+ async removePerson(projectTableName, personId) {
1629
+ this._ctx.requireTokens();
1630
+ const path = `/api/v1/person/${encodeURIComponent(projectTableName)}/${encodeURIComponent(personId)}`;
1631
+ const { data } = await this._ctx.client.request(path, 'DELETE', this._ctx.accessToken, this._ctx.refreshToken);
1632
+ return data;
1633
+ }
1178
1634
  }
1179
1635
 
1180
- function stripUndef$2(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1636
+ function stripUndef$3(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1181
1637
 
1182
1638
  class Projects {
1183
1639
  constructor(ctx) { this._ctx = ctx; }
1184
1640
 
1185
- async createProjectAndRun(name, private_ = undefined, type = undefined, description = undefined, directory = undefined, photoUploadVector = undefined, thumbnail = undefined, runName = undefined, { outcomes, models } = {}) {
1641
+ async createProjectAndRun(name, type, private_ = undefined, description = undefined, directory = undefined, photoUploadVector = undefined, thumbnail = undefined, runName = undefined, { outcomes, models } = {}) {
1186
1642
  this._ctx.requireTokens();
1187
1643
  let path = `/api/v1/project_outcome/`;
1188
1644
  const query = new URLSearchParams();
@@ -1190,7 +1646,7 @@ class Projects {
1190
1646
  if (models !== undefined) (Array.isArray(models) ? models : [models]).forEach(v => query.append('models', v));
1191
1647
  const qs = query.toString();
1192
1648
  if (qs) path += '?' + qs;
1193
- const body = stripUndef$2({
1649
+ const body = stripUndef$3({
1194
1650
  name: name,
1195
1651
  private: private_,
1196
1652
  type: type,
@@ -1260,7 +1716,7 @@ class Projects {
1260
1716
  async updateProject(projectId, { private: private_, type, description, directory, name, thumbnail } = {}) {
1261
1717
  this._ctx.requireTokens();
1262
1718
  const path = `/api/v1/projects/${encodeURIComponent(projectId)}`;
1263
- const body = stripUndef$2({
1719
+ const body = stripUndef$3({
1264
1720
  private: private_,
1265
1721
  type: type,
1266
1722
  description: description,
@@ -1339,7 +1795,7 @@ class Projects {
1339
1795
  async addProjectEvent(projectTableName, event, detail = undefined) {
1340
1796
  this._ctx.requireTokens();
1341
1797
  const path = `/api/v1/projects/${encodeURIComponent(projectTableName)}/event`;
1342
- const body = stripUndef$2({
1798
+ const body = stripUndef$3({
1343
1799
  event: event,
1344
1800
  detail: detail,
1345
1801
  });
@@ -1483,7 +1939,7 @@ class Photos {
1483
1939
  }
1484
1940
  }
1485
1941
 
1486
- function stripUndef$1(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1942
+ function stripUndef$2(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1487
1943
 
1488
1944
  class Search {
1489
1945
  constructor(ctx) { this._ctx = ctx; }
@@ -1565,7 +2021,7 @@ class Search {
1565
2021
  if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1566
2022
  const qs = query.toString();
1567
2023
  if (qs) path += '?' + qs;
1568
- const body = stripUndef$1({
2024
+ const body = stripUndef$2({
1569
2025
  search_text: searchText,
1570
2026
  size: size,
1571
2027
  });
@@ -1621,6 +2077,75 @@ class Search {
1621
2077
  }
1622
2078
  }
1623
2079
 
2080
+ function stripUndef$1(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
2081
+
2082
+ class Subscription {
2083
+ constructor(ctx) { this._ctx = ctx; }
2084
+
2085
+ async createSubscription(projectTableName, callbackUrl, targets) {
2086
+ this._ctx.requireTokens();
2087
+ const path = `/api/v1/subscriptions`;
2088
+ const body = stripUndef$1({
2089
+ project_table_name: projectTableName,
2090
+ callback_url: callbackUrl,
2091
+ targets: targets,
2092
+ });
2093
+ const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
2094
+ return data;
2095
+ }
2096
+
2097
+ async verifySubscription(subscriptionId) {
2098
+ this._ctx.requireTokens();
2099
+ const path = `/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/verify`;
2100
+ const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken);
2101
+ return data;
2102
+ }
2103
+
2104
+ async listSubscriptions() {
2105
+ this._ctx.requireTokens();
2106
+ const path = `/api/v1/subscriptions`;
2107
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
2108
+ return data;
2109
+ }
2110
+
2111
+ async listSubscriptionEvents(subscriptionId, { since, limit } = {}) {
2112
+ this._ctx.requireTokens();
2113
+ let path = `/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/events`;
2114
+ const query = new URLSearchParams();
2115
+ if (since !== undefined) (Array.isArray(since) ? since : [since]).forEach(v => query.append('since', v));
2116
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
2117
+ const qs = query.toString();
2118
+ if (qs) path += '?' + qs;
2119
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
2120
+ return data;
2121
+ }
2122
+
2123
+ async updateSubscription(subscriptionId, { callbackUrl, targets } = {}) {
2124
+ this._ctx.requireTokens();
2125
+ const path = `/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}`;
2126
+ const body = stripUndef$1({
2127
+ callback_url: callbackUrl,
2128
+ targets: targets,
2129
+ });
2130
+ const { data } = await this._ctx.client.request(path, 'PATCH', this._ctx.accessToken, this._ctx.refreshToken, body);
2131
+ return data;
2132
+ }
2133
+
2134
+ async deleteSubscription(subscriptionId) {
2135
+ this._ctx.requireTokens();
2136
+ const path = `/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}`;
2137
+ const { data } = await this._ctx.client.request(path, 'DELETE', this._ctx.accessToken, this._ctx.refreshToken);
2138
+ return data;
2139
+ }
2140
+
2141
+ async rotateSubscriptionSecret(subscriptionId) {
2142
+ this._ctx.requireTokens();
2143
+ const path = `/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/rotate_secret`;
2144
+ const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken);
2145
+ return data;
2146
+ }
2147
+ }
2148
+
1624
2149
  function stripUndef(o) { const r = {}; for (const k in o) if (o[k] !== undefined) r[k] = o[k]; return r; }
1625
2150
 
1626
2151
  class Users {
@@ -1804,7 +2329,9 @@ class MediaViz {
1804
2329
  this.photos = new Photos(_ctx);
1805
2330
  this.projects = new Projects(_ctx);
1806
2331
  this.search = new Search(_ctx);
2332
+ this.subscription = new Subscription(_ctx);
1807
2333
  this.users = new Users(_ctx);
2334
+ this.webhooks = new WebhookConsumer(_ctx, this.subscription);
1808
2335
  }
1809
2336
 
1810
2337
  async authenticate() {
@@ -1834,4 +2361,4 @@ class MediaViz {
1834
2361
  get refreshToken() { return this._refreshToken; }
1835
2362
  }
1836
2363
 
1837
- export { Admin, AiModelCredits, ApiError, Company, CuratedAlbums, CustomAlbums, EmailTokens, Health, Keywords, MediaViz, NotFoundError, OAuthClient, OAuthError, OAuthErrorCode, OauthAuthorization, OauthLogin, OauthToken, Person, Photos, Photoupload, Projects, RateLimitError, Search, ServerError, Users, ValidationError, handleResponse };
2364
+ export { Admin, AiModelCredits, ApiError, Company, CuratedAlbums, CustomAlbums, EmailTokens, Health, InMemoryWebhookStore, Keywords, MediaViz, NotFoundError, OAuthClient, OAuthError, OAuthErrorCode, OauthAuthorization, OauthLogin, OauthToken, Person, Photos, Photoupload, Projects, RateLimitError, Search, ServerError, Subscription, Users, ValidationError, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS, WebhookConsumer, handleResponse, selectResultColumns, signWebhookPayload, verifyWebhookSignature };