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