@bsv/wallet-toolbox-client 1.7.17 → 1.7.19

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.
@@ -77,7 +77,8 @@ class WalletPermissionsManager {
77
77
  onBasketAccessRequested: [],
78
78
  onCertificateAccessRequested: [],
79
79
  onSpendingAuthorizationRequested: [],
80
- onGroupedPermissionRequested: []
80
+ onGroupedPermissionRequested: [],
81
+ onCounterpartyPermissionRequested: []
81
82
  };
82
83
  /**
83
84
  * We queue parallel requests for the same resource so that only one
@@ -93,6 +94,9 @@ class WalletPermissionsManager {
93
94
  /** Cache recently confirmed permissions to avoid repeated lookups. */
94
95
  this.permissionCache = new Map();
95
96
  this.recentGrants = new Map();
97
+ this.manifestCache = new Map();
98
+ this.manifestFetchInProgress = new Map();
99
+ this.pactEstablishedCache = new Map();
96
100
  this.underlying = underlyingWallet;
97
101
  this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
98
102
  // Default all config options to true unless specified
@@ -316,9 +320,8 @@ class WalletPermissionsManager {
316
320
  const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
317
321
  const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
318
322
  // --- Validation: Ensure granted permissions are a subset of what was requested ---
319
- if (params.granted.spendingAuthorization &&
320
- !deepEqual(params.granted.spendingAuthorization, requestedPermissions.spendingAuthorization)) {
321
- throw new Error('Granted spending authorization does not match the original request.');
323
+ if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
324
+ throw new Error('Granted spending authorization was not part of the original request.');
322
325
  }
323
326
  if ((_a = params.granted.protocolPermissions) === null || _a === void 0 ? void 0 : _a.some(g => { var _a; return !((_a = requestedPermissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
324
327
  throw new Error('Granted protocol permissions are not a subset of the original request.');
@@ -410,6 +413,74 @@ class WalletPermissionsManager {
410
413
  }
411
414
  this.activeRequests.delete(requestID);
412
415
  }
416
+ async dismissGroupedPermission(requestID) {
417
+ const matching = this.activeRequests.get(requestID);
418
+ if (!matching) {
419
+ throw new Error('Request ID not found.');
420
+ }
421
+ for (const p of matching.pending) {
422
+ p.resolve(true);
423
+ }
424
+ this.activeRequests.delete(requestID);
425
+ }
426
+ async grantCounterpartyPermission(params) {
427
+ var _a;
428
+ const matching = this.activeRequests.get(params.requestID);
429
+ if (!matching) {
430
+ throw new Error('Request ID not found.');
431
+ }
432
+ const originalRequest = matching.request;
433
+ const { originator, counterparty, permissions: requestedPermissions, displayOriginator } = originalRequest;
434
+ const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
435
+ if ((_a = params.granted.protocols) === null || _a === void 0 ? void 0 : _a.some(g => !requestedPermissions.protocols.find(r => deepEqual(r, g)))) {
436
+ throw new Error('Granted protocol permissions are not a subset of the original request.');
437
+ }
438
+ const expiry = params.expiry || 0;
439
+ for (const p of params.granted.protocols || []) {
440
+ const token = await this.findProtocolToken(originator, false, p.protocolID, counterparty, true, originLookupValues);
441
+ if (token) {
442
+ const request = {
443
+ type: 'protocol',
444
+ originator,
445
+ privileged: false,
446
+ protocolID: p.protocolID,
447
+ counterparty,
448
+ reason: p.description
449
+ };
450
+ await this.renewPermissionOnChain(token, request, expiry);
451
+ this.markRecentGrant(request);
452
+ }
453
+ else {
454
+ const request = {
455
+ type: 'protocol',
456
+ originator,
457
+ privileged: false,
458
+ protocolID: p.protocolID,
459
+ counterparty,
460
+ reason: p.description
461
+ };
462
+ await this.createPermissionOnChain(request, expiry);
463
+ this.markRecentGrant(request);
464
+ }
465
+ }
466
+ this.markPactEstablished(originator, counterparty);
467
+ for (const p of matching.pending) {
468
+ p.resolve(true);
469
+ }
470
+ this.activeRequests.delete(params.requestID);
471
+ }
472
+ async denyCounterpartyPermission(requestID) {
473
+ const matching = this.activeRequests.get(requestID);
474
+ if (!matching) {
475
+ throw new Error('Request ID not found.');
476
+ }
477
+ const err = new Error('The user has denied the request for permission.');
478
+ err.code = 'ERR_PERMISSION_DENIED';
479
+ for (const p of matching.pending) {
480
+ p.reject(err);
481
+ }
482
+ this.activeRequests.delete(requestID);
483
+ }
413
484
  /* ---------------------------------------------------------------------
414
485
  * 3) THE "ENSURE" METHODS: CHECK IF PERMISSION EXISTS, OTHERWISE PROMPT
415
486
  * --------------------------------------------------------------------- */
@@ -728,6 +799,469 @@ class WalletPermissionsManager {
728
799
  usageType: 'generic'
729
800
  });
730
801
  }
802
+ validateCounterpartyPermissions(raw) {
803
+ if (!raw || !Array.isArray(raw.protocols) || raw.protocols.length === 0)
804
+ return null;
805
+ const validProtocols = raw.protocols.filter((p) => {
806
+ return (Array.isArray(p === null || p === void 0 ? void 0 : p.protocolID) &&
807
+ p.protocolID[0] === 2 &&
808
+ typeof p.protocolID[1] === 'string' &&
809
+ typeof (p === null || p === void 0 ? void 0 : p.description) === 'string');
810
+ });
811
+ if (validProtocols.length === 0)
812
+ return null;
813
+ return {
814
+ description: typeof raw.description === 'string' ? raw.description : undefined,
815
+ protocols: validProtocols
816
+ };
817
+ }
818
+ async fetchManifestPermissions(originator) {
819
+ const cached = this.manifestCache.get(originator);
820
+ if (cached && Date.now() - cached.fetchedAt < WalletPermissionsManager.MANIFEST_CACHE_TTL_MS) {
821
+ return {
822
+ groupPermissions: cached.groupPermissions,
823
+ counterpartyPermissions: cached.counterpartyPermissions
824
+ };
825
+ }
826
+ const inProgress = this.manifestFetchInProgress.get(originator);
827
+ if (inProgress) {
828
+ return inProgress;
829
+ }
830
+ const fetchPromise = (async () => {
831
+ var _a, _b;
832
+ try {
833
+ const proto = originator.startsWith('localhost:') ? 'http' : 'https';
834
+ const response = await fetch(`${proto}://${originator}/manifest.json`);
835
+ if (response.ok) {
836
+ const manifest = await response.json();
837
+ const groupPermissions = ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) || null;
838
+ const counterpartyPermissions = this.validateCounterpartyPermissions((_b = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _b === void 0 ? void 0 : _b.counterpartyPermissions);
839
+ this.manifestCache.set(originator, { groupPermissions, counterpartyPermissions, fetchedAt: Date.now() });
840
+ return { groupPermissions, counterpartyPermissions };
841
+ }
842
+ }
843
+ catch (e) { }
844
+ const result = { groupPermissions: null, counterpartyPermissions: null };
845
+ this.manifestCache.set(originator, { ...result, fetchedAt: Date.now() });
846
+ return result;
847
+ })();
848
+ this.manifestFetchInProgress.set(originator, fetchPromise);
849
+ try {
850
+ return await fetchPromise;
851
+ }
852
+ finally {
853
+ this.manifestFetchInProgress.delete(originator);
854
+ }
855
+ }
856
+ async fetchManifestGroupPermissions(originator) {
857
+ const { groupPermissions } = await this.fetchManifestPermissions(originator);
858
+ return groupPermissions;
859
+ }
860
+ async filterAlreadyGrantedPermissions(originator, groupPermissions) {
861
+ const permissionsToRequest = {
862
+ description: groupPermissions.description,
863
+ protocolPermissions: [],
864
+ basketAccess: [],
865
+ certificateAccess: []
866
+ };
867
+ if (groupPermissions.spendingAuthorization) {
868
+ const hasAuth = await this.hasSpendingAuthorization({
869
+ originator,
870
+ satoshis: groupPermissions.spendingAuthorization.amount
871
+ });
872
+ if (!hasAuth) {
873
+ permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
874
+ }
875
+ }
876
+ for (const p of groupPermissions.protocolPermissions || []) {
877
+ const hasPerm = await this.hasProtocolPermission({
878
+ originator,
879
+ privileged: false,
880
+ protocolID: p.protocolID,
881
+ counterparty: p.counterparty || 'self'
882
+ });
883
+ if (!hasPerm) {
884
+ permissionsToRequest.protocolPermissions.push(p);
885
+ }
886
+ }
887
+ for (const b of groupPermissions.basketAccess || []) {
888
+ const hasAccess = await this.hasBasketAccess({
889
+ originator,
890
+ basket: b.basket
891
+ });
892
+ if (!hasAccess) {
893
+ permissionsToRequest.basketAccess.push(b);
894
+ }
895
+ }
896
+ for (const c of groupPermissions.certificateAccess || []) {
897
+ const hasAccess = await this.hasCertificateAccess({
898
+ originator,
899
+ privileged: false,
900
+ verifier: c.verifierPublicKey,
901
+ certType: c.type,
902
+ fields: c.fields
903
+ });
904
+ if (!hasAccess) {
905
+ permissionsToRequest.certificateAccess.push(c);
906
+ }
907
+ }
908
+ return permissionsToRequest;
909
+ }
910
+ hasAnyPermissionsToRequest(permissions) {
911
+ var _a, _b, _c, _d, _e, _f;
912
+ return !!(permissions.spendingAuthorization ||
913
+ ((_b = (_a = permissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 ||
914
+ ((_d = (_c = permissions.basketAccess) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 ||
915
+ ((_f = (_e = permissions.certificateAccess) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0);
916
+ }
917
+ hasGroupedPermissionRequestedHandlers() {
918
+ const handlers = this.callbacks.onGroupedPermissionRequested || [];
919
+ return handlers.some(h => typeof h === 'function');
920
+ }
921
+ hasCounterpartyPermissionRequestedHandlers() {
922
+ const handlers = this.callbacks.onCounterpartyPermissionRequested || [];
923
+ return handlers.some(h => typeof h === 'function');
924
+ }
925
+ async hasPactEstablished(originator, counterparty) {
926
+ var _a;
927
+ if (counterparty === 'self' || counterparty === 'anyone') {
928
+ return true;
929
+ }
930
+ const cacheKey = `${originator}:${counterparty}`;
931
+ if (this.pactEstablishedCache.has(cacheKey)) {
932
+ return true;
933
+ }
934
+ const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
935
+ if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
936
+ return true;
937
+ }
938
+ const firstProtocol = counterpartyPermissions.protocols[0];
939
+ const hasToken = await this.hasProtocolPermission({
940
+ originator,
941
+ privileged: false,
942
+ protocolID: firstProtocol.protocolID,
943
+ counterparty
944
+ });
945
+ if (hasToken) {
946
+ this.pactEstablishedCache.set(cacheKey, Date.now());
947
+ return true;
948
+ }
949
+ return false;
950
+ }
951
+ markPactEstablished(originator, counterparty) {
952
+ const cacheKey = `${originator}:${counterparty}`;
953
+ this.pactEstablishedCache.set(cacheKey, Date.now());
954
+ }
955
+ async maybeRequestPact(currentRequest) {
956
+ var _a;
957
+ if (!this.config.seekGroupedPermission) {
958
+ return null;
959
+ }
960
+ if (!this.hasCounterpartyPermissionRequestedHandlers()) {
961
+ return null;
962
+ }
963
+ if (currentRequest.type !== 'protocol') {
964
+ return null;
965
+ }
966
+ if (currentRequest.privileged) {
967
+ return null;
968
+ }
969
+ const [level] = currentRequest.protocolID;
970
+ if (level !== 2) {
971
+ return null;
972
+ }
973
+ const originator = currentRequest.originator;
974
+ const counterparty = currentRequest.counterparty;
975
+ if (!counterparty || counterparty === 'self' || counterparty === 'anyone') {
976
+ return null;
977
+ }
978
+ if (!/^[0-9a-fA-F]{66}$/.test(counterparty)) {
979
+ return null;
980
+ }
981
+ if (await this.hasPactEstablished(originator, counterparty)) {
982
+ return null;
983
+ }
984
+ const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
985
+ if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
986
+ return null;
987
+ }
988
+ const protocolsToRequest = [];
989
+ for (const p of counterpartyPermissions.protocols) {
990
+ const hasPerm = await this.hasProtocolPermission({
991
+ originator,
992
+ privileged: false,
993
+ protocolID: p.protocolID,
994
+ counterparty
995
+ });
996
+ if (!hasPerm) {
997
+ protocolsToRequest.push(p);
998
+ }
999
+ }
1000
+ if (protocolsToRequest.length === 0) {
1001
+ this.markPactEstablished(originator, counterparty);
1002
+ return null;
1003
+ }
1004
+ const permissionsToRequest = {
1005
+ description: counterpartyPermissions.description,
1006
+ protocols: protocolsToRequest
1007
+ };
1008
+ const key = `pact:${originator}:${counterparty}`;
1009
+ const existing = this.activeRequests.get(key);
1010
+ if (existing) {
1011
+ const existingRequest = existing.request;
1012
+ for (const p of permissionsToRequest.protocols) {
1013
+ if (!existingRequest.permissions.protocols.find(x => deepEqual(x, p))) {
1014
+ existingRequest.permissions.protocols.push(p);
1015
+ }
1016
+ }
1017
+ await new Promise((resolve, reject) => {
1018
+ existing.pending.push({ resolve, reject });
1019
+ });
1020
+ }
1021
+ else {
1022
+ await new Promise(async (resolve, reject) => {
1023
+ this.activeRequests.set(key, {
1024
+ request: {
1025
+ originator,
1026
+ counterparty,
1027
+ permissions: permissionsToRequest,
1028
+ displayOriginator: currentRequest.displayOriginator
1029
+ },
1030
+ pending: [{ resolve, reject }]
1031
+ });
1032
+ await this.callEvent('onCounterpartyPermissionRequested', {
1033
+ requestID: key,
1034
+ originator,
1035
+ counterparty,
1036
+ permissions: permissionsToRequest
1037
+ });
1038
+ });
1039
+ }
1040
+ this.markPactEstablished(originator, counterparty);
1041
+ const satisfied = await this.hasProtocolPermission({
1042
+ originator,
1043
+ privileged: false,
1044
+ protocolID: currentRequest.protocolID,
1045
+ counterparty
1046
+ });
1047
+ return satisfied ? true : null;
1048
+ }
1049
+ async maybeRequestPeerGroupedLevel2ProtocolPermissions(currentRequest) {
1050
+ var _a, _b;
1051
+ if (!this.config.seekGroupedPermission) {
1052
+ return null;
1053
+ }
1054
+ if (!this.hasGroupedPermissionRequestedHandlers()) {
1055
+ return null;
1056
+ }
1057
+ if (currentRequest.type !== 'protocol') {
1058
+ return null;
1059
+ }
1060
+ const [level] = currentRequest.protocolID;
1061
+ if (level !== 2) {
1062
+ return null;
1063
+ }
1064
+ const originator = currentRequest.originator;
1065
+ const privileged = (_a = currentRequest.privileged) !== null && _a !== void 0 ? _a : false;
1066
+ const counterparty = (_b = currentRequest.counterparty) !== null && _b !== void 0 ? _b : 'self';
1067
+ const groupPermissions = await this.fetchManifestGroupPermissions(originator);
1068
+ if (!groupPermissions) {
1069
+ return null;
1070
+ }
1071
+ const normalizeManifestCounterparty = (cp) => {
1072
+ if (cp === '')
1073
+ return counterparty;
1074
+ return cp !== null && cp !== void 0 ? cp : 'self';
1075
+ };
1076
+ const manifestLevel2ForThisPeer = (groupPermissions.protocolPermissions || [])
1077
+ .filter(p => { var _a, _b; return ((_b = (_a = p.protocolID) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 0) === 2; })
1078
+ .map(p => ({
1079
+ protocolID: p.protocolID,
1080
+ counterparty: normalizeManifestCounterparty(p.counterparty),
1081
+ description: p.description
1082
+ }))
1083
+ .filter(p => p.counterparty === counterparty);
1084
+ const isCurrentRequestInManifest = manifestLevel2ForThisPeer.some(p => deepEqual(p.protocolID, currentRequest.protocolID));
1085
+ if (!isCurrentRequestInManifest) {
1086
+ return null;
1087
+ }
1088
+ const permissionsToRequest = {
1089
+ protocolPermissions: []
1090
+ };
1091
+ for (const p of manifestLevel2ForThisPeer) {
1092
+ const hasPerm = await this.hasProtocolPermission({
1093
+ originator,
1094
+ privileged,
1095
+ protocolID: p.protocolID,
1096
+ counterparty: p.counterparty
1097
+ });
1098
+ if (!hasPerm) {
1099
+ permissionsToRequest.protocolPermissions.push({
1100
+ protocolID: p.protocolID,
1101
+ counterparty: p.counterparty,
1102
+ description: p.description
1103
+ });
1104
+ }
1105
+ }
1106
+ if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
1107
+ return null;
1108
+ }
1109
+ const key = `group-peer:${originator}:${privileged}:${counterparty}`;
1110
+ const existing = this.activeRequests.get(key);
1111
+ if (existing) {
1112
+ const existingRequest = existing.request;
1113
+ if (!existingRequest.permissions.protocolPermissions) {
1114
+ existingRequest.permissions.protocolPermissions = [];
1115
+ }
1116
+ for (const p of permissionsToRequest.protocolPermissions || []) {
1117
+ if (!existingRequest.permissions.protocolPermissions.find(x => deepEqual(x, p))) {
1118
+ existingRequest.permissions.protocolPermissions.push(p);
1119
+ }
1120
+ }
1121
+ await new Promise((resolve, reject) => {
1122
+ existing.pending.push({ resolve, reject });
1123
+ });
1124
+ }
1125
+ else {
1126
+ await new Promise(async (resolve, reject) => {
1127
+ const permissions = permissionsToRequest;
1128
+ this.activeRequests.set(key, {
1129
+ request: {
1130
+ originator,
1131
+ permissions,
1132
+ displayOriginator: currentRequest.displayOriginator
1133
+ },
1134
+ pending: [{ resolve, reject }]
1135
+ });
1136
+ await this.callEvent('onGroupedPermissionRequested', {
1137
+ requestID: key,
1138
+ originator,
1139
+ permissions
1140
+ });
1141
+ });
1142
+ }
1143
+ const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
1144
+ return satisfied ? true : null;
1145
+ }
1146
+ async checkSpecificPermissionAfterGroupFlow(request) {
1147
+ var _a, _b, _c;
1148
+ switch (request.type) {
1149
+ case 'protocol':
1150
+ return await this.hasProtocolPermission({
1151
+ originator: request.originator,
1152
+ privileged: (_a = request.privileged) !== null && _a !== void 0 ? _a : false,
1153
+ protocolID: request.protocolID,
1154
+ counterparty: (_b = request.counterparty) !== null && _b !== void 0 ? _b : 'self'
1155
+ });
1156
+ case 'basket':
1157
+ return await this.hasBasketAccess({
1158
+ originator: request.originator,
1159
+ basket: request.basket
1160
+ });
1161
+ case 'certificate':
1162
+ return await this.hasCertificateAccess({
1163
+ originator: request.originator,
1164
+ privileged: (_c = request.privileged) !== null && _c !== void 0 ? _c : false,
1165
+ verifier: request.certificate.verifier,
1166
+ certType: request.certificate.certType,
1167
+ fields: request.certificate.fields
1168
+ });
1169
+ case 'spending':
1170
+ return await this.hasSpendingAuthorization({
1171
+ originator: request.originator,
1172
+ satoshis: request.spending.satoshis
1173
+ });
1174
+ default:
1175
+ return false;
1176
+ }
1177
+ }
1178
+ isRequestIncludedInGroupPermissions(request, groupPermissions) {
1179
+ var _a, _b, _c, _d;
1180
+ switch (request.type) {
1181
+ case 'protocol': {
1182
+ if (request.privileged)
1183
+ return false;
1184
+ const pid = request.protocolID;
1185
+ if (!pid)
1186
+ return false;
1187
+ const cp = (_a = request.counterparty) !== null && _a !== void 0 ? _a : 'self';
1188
+ return !!((_b = groupPermissions.protocolPermissions) === null || _b === void 0 ? void 0 : _b.some(p => {
1189
+ var _a;
1190
+ const manifestCp = p.counterparty === '' ? cp : ((_a = p.counterparty) !== null && _a !== void 0 ? _a : 'self');
1191
+ return deepEqual(p.protocolID, pid) && manifestCp === cp;
1192
+ }));
1193
+ }
1194
+ case 'basket': {
1195
+ const basket = request.basket;
1196
+ if (!basket)
1197
+ return false;
1198
+ return !!((_c = groupPermissions.basketAccess) === null || _c === void 0 ? void 0 : _c.some(b => b.basket === basket));
1199
+ }
1200
+ case 'certificate': {
1201
+ if (request.privileged)
1202
+ return false;
1203
+ const cert = request.certificate;
1204
+ if (!cert)
1205
+ return false;
1206
+ return !!((_d = groupPermissions.certificateAccess) === null || _d === void 0 ? void 0 : _d.some(c => {
1207
+ const fieldsA = new Set(c.fields || []);
1208
+ const fieldsB = new Set(cert.fields || []);
1209
+ if (fieldsA.size !== fieldsB.size)
1210
+ return false;
1211
+ for (const f of fieldsA)
1212
+ if (!fieldsB.has(f))
1213
+ return false;
1214
+ return c.type === cert.certType && c.verifierPublicKey === cert.verifier;
1215
+ }));
1216
+ }
1217
+ case 'spending':
1218
+ return !!groupPermissions.spendingAuthorization;
1219
+ default:
1220
+ return false;
1221
+ }
1222
+ }
1223
+ async maybeRequestGroupedPermissions(currentRequest) {
1224
+ if (!this.config.seekGroupedPermission) {
1225
+ return null;
1226
+ }
1227
+ const originator = currentRequest.originator;
1228
+ const groupPermissions = await this.fetchManifestGroupPermissions(originator);
1229
+ if (!groupPermissions) {
1230
+ return null;
1231
+ }
1232
+ if (!this.isRequestIncludedInGroupPermissions(currentRequest, groupPermissions)) {
1233
+ return null;
1234
+ }
1235
+ const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
1236
+ if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
1237
+ return null;
1238
+ }
1239
+ const key = `group:${originator}`;
1240
+ if (this.activeRequests.has(key)) {
1241
+ await new Promise((resolve, reject) => {
1242
+ this.activeRequests.get(key).pending.push({ resolve, reject });
1243
+ });
1244
+ }
1245
+ else {
1246
+ await new Promise(async (resolve, reject) => {
1247
+ this.activeRequests.set(key, {
1248
+ request: {
1249
+ originator,
1250
+ permissions: permissionsToRequest,
1251
+ displayOriginator: currentRequest.displayOriginator
1252
+ },
1253
+ pending: [{ resolve, reject }]
1254
+ });
1255
+ await this.callEvent('onGroupedPermissionRequested', {
1256
+ requestID: key,
1257
+ originator,
1258
+ permissions: permissionsToRequest
1259
+ });
1260
+ });
1261
+ }
1262
+ const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
1263
+ return satisfied ? true : null;
1264
+ }
731
1265
  /**
732
1266
  * A central method that triggers the permission request flow.
733
1267
  * - It checks if there's already an active request for the same key
@@ -736,13 +1270,25 @@ class WalletPermissionsManager {
736
1270
  * and return a promise that resolves once permission is granted or rejects if denied.
737
1271
  */
738
1272
  async requestPermissionFlow(r) {
739
- var _a;
1273
+ var _a, _b, _c;
740
1274
  const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
741
1275
  const preparedRequest = {
742
1276
  ...r,
743
1277
  originator: normalizedOriginator,
744
- displayOriginator: (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : r.originator
1278
+ displayOriginator: (_c = (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : (_b = r.previousToken) === null || _b === void 0 ? void 0 : _b.rawOriginator) !== null && _c !== void 0 ? _c : r.originator
745
1279
  };
1280
+ const pactResult = await this.maybeRequestPact(preparedRequest);
1281
+ if (pactResult !== null) {
1282
+ return pactResult;
1283
+ }
1284
+ const peerGroupResult = await this.maybeRequestPeerGroupedLevel2ProtocolPermissions(preparedRequest);
1285
+ if (peerGroupResult !== null) {
1286
+ return peerGroupResult;
1287
+ }
1288
+ const groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
1289
+ if (groupResult !== null) {
1290
+ return groupResult;
1291
+ }
746
1292
  const key = this.buildRequestKey(preparedRequest);
747
1293
  // If there's already a queue for the same resource, we piggyback on it
748
1294
  const existingQueue = this.activeRequests.get(key);
@@ -2358,80 +2904,17 @@ class WalletPermissionsManager {
2358
2904
  return this.underlying.isAuthenticated(...args);
2359
2905
  }
2360
2906
  async waitForAuthentication(...args) {
2361
- var _a, _b, _c, _d, _e, _f, _g;
2362
2907
  let [_, originator] = args;
2363
2908
  if (this.config.seekGroupedPermission && originator) {
2364
2909
  const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
2365
2910
  originator = normalizedOriginator;
2366
2911
  // 1. Fetch manifest.json from the originator
2367
- let groupPermissions;
2368
- try {
2369
- const proto = originator.startsWith('localhost:') ? 'http' : 'https';
2370
- const response = await fetch(`${proto}://${originator}/manifest.json`);
2371
- if (response.ok) {
2372
- const manifest = await response.json();
2373
- if ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) {
2374
- groupPermissions = manifest.babbage.groupPermissions;
2375
- }
2376
- }
2377
- }
2378
- catch (e) {
2379
- // Ignore fetch/parse errors, just proceed without group permissions.
2380
- }
2912
+ const groupPermissions = await this.fetchManifestGroupPermissions(originator);
2381
2913
  if (groupPermissions) {
2382
2914
  // 2. Filter out already-granted permissions
2383
- const permissionsToRequest = {
2384
- protocolPermissions: [],
2385
- basketAccess: [],
2386
- certificateAccess: []
2387
- };
2388
- if (groupPermissions.spendingAuthorization) {
2389
- const hasAuth = await this.hasSpendingAuthorization({
2390
- originator,
2391
- satoshis: groupPermissions.spendingAuthorization.amount
2392
- });
2393
- if (!hasAuth) {
2394
- permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
2395
- }
2396
- }
2397
- for (const p of groupPermissions.protocolPermissions || []) {
2398
- const hasPerm = await this.hasProtocolPermission({
2399
- originator,
2400
- privileged: false, // Privilege is never allowed here
2401
- protocolID: p.protocolID,
2402
- counterparty: p.counterparty || 'self'
2403
- });
2404
- if (!hasPerm) {
2405
- permissionsToRequest.protocolPermissions.push(p);
2406
- }
2407
- }
2408
- for (const b of groupPermissions.basketAccess || []) {
2409
- const hasAccess = await this.hasBasketAccess({
2410
- originator,
2411
- basket: b.basket
2412
- });
2413
- if (!hasAccess) {
2414
- permissionsToRequest.basketAccess.push(b);
2415
- }
2416
- }
2417
- for (const c of groupPermissions.certificateAccess || []) {
2418
- const hasAccess = await this.hasCertificateAccess({
2419
- originator,
2420
- privileged: false, // Privilege is never allowed here for security
2421
- verifier: c.verifierPublicKey,
2422
- certType: c.type,
2423
- fields: c.fields
2424
- });
2425
- if (!hasAccess) {
2426
- permissionsToRequest.certificateAccess.push(c);
2427
- }
2428
- }
2915
+ const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
2429
2916
  // 3. If any permissions are left to request, start the flow
2430
- const hasRequests = permissionsToRequest.spendingAuthorization ||
2431
- ((_c = (_b = permissionsToRequest.protocolPermissions) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0 ||
2432
- ((_e = (_d = permissionsToRequest.basketAccess) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0) > 0 ||
2433
- ((_g = (_f = permissionsToRequest.certificateAccess) === null || _f === void 0 ? void 0 : _f.length) !== null && _g !== void 0 ? _g : 0) > 0;
2434
- if (hasRequests) {
2917
+ if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
2435
2918
  const key = `group:${originator}`;
2436
2919
  if (this.activeRequests.has(key)) {
2437
2920
  // Another call is already waiting, piggyback on it
@@ -2646,6 +3129,7 @@ class WalletPermissionsManager {
2646
3129
  }
2647
3130
  }
2648
3131
  exports.WalletPermissionsManager = WalletPermissionsManager;
3132
+ WalletPermissionsManager.MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
2649
3133
  /** How long a cached permission remains valid (5 minutes). */
2650
3134
  WalletPermissionsManager.CACHE_TTL_MS = 5 * 60 * 1000;
2651
3135
  /** Window during which freshly granted permissions are auto-allowed (except spending). */