@mediaviz/sdk 1.7.0 → 1.8.0-dev.101

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