@atxp/client 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, getErrorRecoveryHint, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, getIsReactNative, createReactNativeSafeFetch, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount } from '@atxp/common';
1
+ import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, getErrorRecoveryHint, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, createReactNativeSafeFetch, getIsReactNative, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount } from '@atxp/common';
2
2
  export { ATXPAccount } from '@atxp/common';
3
3
  import * as oauth from 'oauth4webapi';
4
4
  import { BigNumber } from 'bignumber.js';
@@ -453,6 +453,7 @@ function atxpFetch(config) {
453
453
  }
454
454
  class ATXPFetcher {
455
455
  constructor(config) {
456
+ this.oauthClient = null;
456
457
  this.defaultPaymentFailureHandler = async (context) => {
457
458
  const { payment, error, attemptedNetworks, retryable } = context;
458
459
  const recoveryHint = getErrorRecoveryHint(error);
@@ -512,8 +513,9 @@ class ATXPFetcher {
512
513
  }
513
514
  // Create prospective payment for approval (using first destination for display)
514
515
  const firstDest = mappedDestinations[0];
516
+ const accountId = await this.account.getAccountId();
515
517
  const prospectivePayment = {
516
- accountId: this.account.accountId,
518
+ accountId,
517
519
  resourceUrl: paymentRequest.resource?.toString() ?? '',
518
520
  resourceName: paymentRequest.payeeName ?? '',
519
521
  currency: firstDest.currency,
@@ -547,7 +549,7 @@ class ATXPFetcher {
547
549
  network: result.chain
548
550
  });
549
551
  // Submit payment to the server
550
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
552
+ const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId });
551
553
  const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
552
554
  method: 'PUT',
553
555
  headers: {
@@ -675,7 +677,8 @@ class ATXPFetcher {
675
677
  const paymentMakerCount = this.account.paymentMakers.length;
676
678
  throw new Error(`Payment maker is missing generateJWT method. Available payment maker count: ${paymentMakerCount}. This indicates the payment maker object does not implement the PaymentMaker interface. If using TypeScript, ensure your payment maker properly implements the PaymentMaker interface.`);
677
679
  }
678
- const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId: this.account.accountId });
680
+ const accountId = await this.account.getAccountId();
681
+ const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId });
679
682
  // Make a fetch call to the authorization URL with the payment ID
680
683
  // redirect=false is a hack
681
684
  // The OAuth spec calls for the authorization url to return with a redirect, but fetch
@@ -729,42 +732,47 @@ class ATXPFetcher {
729
732
  if (paymentMaker) {
730
733
  // We can do the full OAuth flow - we'll generate a signed JWT and call /authorize on the
731
734
  // AS to get a code, then exchange the code for an access token
732
- const authorizationUrl = await this.oauthClient.makeAuthorizationUrl(error.url, error.resourceServerUrl);
735
+ const oauthClient = await this.getOAuthClient();
736
+ const authorizationUrl = await oauthClient.makeAuthorizationUrl(error.url, error.resourceServerUrl);
733
737
  if (!this.isAllowedAuthServer(authorizationUrl)) {
734
738
  throw new Error(`ATXP: resource server ${error.url} is requesting to use ${authorizationUrl} which is not in the allowed list of authorization servers ${this.allowedAuthorizationServers.join(', ')}`);
735
739
  }
736
740
  try {
737
741
  const redirectUrl = await this.makeAuthRequestWithPaymentMaker(authorizationUrl, paymentMaker);
738
742
  // Handle the OAuth callback
739
- await this.oauthClient.handleCallback(redirectUrl);
743
+ const oauthClient = await this.getOAuthClient();
744
+ await oauthClient.handleCallback(redirectUrl);
740
745
  // Call onAuthorize callback after successful authorization
746
+ const accountId = await this.account.getAccountId();
741
747
  await this.onAuthorize({
742
748
  authorizationServer: authorizationUrl.origin,
743
- userId: this.account.accountId
749
+ userId: accountId
744
750
  });
745
751
  }
746
752
  catch (authError) {
747
753
  // Call onAuthorizeFailure callback if authorization fails
754
+ const accountId = await this.account.getAccountId();
748
755
  await this.onAuthorizeFailure({
749
756
  authorizationServer: authorizationUrl.origin,
750
- userId: this.account.accountId,
757
+ userId: accountId,
751
758
  error: authError
752
759
  });
753
760
  throw authError;
754
761
  }
755
762
  }
756
763
  else {
757
- // Else, we'll see if we've already got an OAuth token from OUR caller (if any).
764
+ // Else, we'll see if we've already got an OAuth token from OUR caller (if any).
758
765
  // If we do, we'll use it to auth to the downstream resource
759
766
  // (In pass-through scenarios, the atxpServer() middleware stores the incoming
760
767
  // token in the DB under the '' resource URL).
761
- const existingToken = await this.db.getAccessToken(this.account.accountId, '');
768
+ const accountId = await this.account.getAccountId();
769
+ const existingToken = await this.db.getAccessToken(accountId, '');
762
770
  if (!existingToken) {
763
771
  this.logger.info(`ATXP: no token found for the current server - we can't exchange a token if we don't have one`);
764
772
  throw error;
765
773
  }
766
774
  const newToken = await this.exchangeToken(existingToken, error.resourceServerUrl);
767
- this.db.saveAccessToken(this.account.accountId, error.resourceServerUrl, newToken);
775
+ this.db.saveAccessToken(accountId, error.resourceServerUrl, newToken);
768
776
  }
769
777
  };
770
778
  this.exchangeToken = async (myToken, newResourceUrl) => {
@@ -808,9 +816,10 @@ class ATXPFetcher {
808
816
  this.fetch = async (url, init) => {
809
817
  let response = null;
810
818
  let fetchError = null;
819
+ const oauthClient = await this.getOAuthClient();
811
820
  try {
812
821
  // Try to fetch the resource
813
- response = await this.oauthClient.fetch(url, init);
822
+ response = await oauthClient.fetch(url, init);
814
823
  await this.checkForATXPResponse(response);
815
824
  return response;
816
825
  }
@@ -822,7 +831,7 @@ class ATXPFetcher {
822
831
  await this.authToService(error);
823
832
  try {
824
833
  // Retry the request once - we should be auth'd now
825
- response = await this.oauthClient.fetch(url, init);
834
+ response = await oauthClient.fetch(url, init);
826
835
  await this.checkForATXPResponse(response);
827
836
  return response;
828
837
  }
@@ -838,7 +847,7 @@ class ATXPFetcher {
838
847
  this.logger.info(`Payment required - ATXP client starting payment flow ${mcpError?.data?.paymentRequestUrl}`);
839
848
  if (await this.handlePaymentRequestError(mcpError)) {
840
849
  // Retry the request once - we should be auth'd now
841
- response = await this.oauthClient.fetch(url, init);
850
+ response = await oauthClient.fetch(url, init);
842
851
  await this.checkForATXPResponse(response);
843
852
  }
844
853
  else {
@@ -857,26 +866,15 @@ class ATXPFetcher {
857
866
  };
858
867
  const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure, onPaymentAttemptFailed } = config;
859
868
  // Use React Native safe fetch if in React Native environment
860
- const safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
869
+ this.safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
861
870
  const safeSideChannelFetch = getIsReactNative() ? createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
862
- // ATXPClient should never actually use the callback url - instead of redirecting the user to
863
- // an authorization url which redirects back to the callback url, ATXPClient posts the payment
864
- // directly to the authorization server, then does the token exchange itself
865
- this.oauthClient = new OAuthClient({
866
- userId: account.accountId,
867
- db,
868
- callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
869
- isPublic: false,
870
- fetchFn: safeFetchFn,
871
- sideChannelFetch: safeSideChannelFetch,
872
- strict,
873
- allowInsecureRequests,
874
- logger: logger
875
- });
871
+ // OAuthClient is initialized lazily in getOAuthClient() since it needs async accountId
876
872
  this.account = account;
877
873
  this.destinationMakers = destinationMakers;
878
874
  this.sideChannelFetch = safeSideChannelFetch;
879
875
  this.db = db;
876
+ this.strict = strict;
877
+ this.allowInsecureRequests = allowInsecureRequests;
880
878
  this.allowedAuthorizationServers = allowedAuthorizationServers;
881
879
  this.approvePayment = approvePayment;
882
880
  this.logger = logger;
@@ -886,6 +884,27 @@ class ATXPFetcher {
886
884
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
887
885
  this.onPaymentAttemptFailed = onPaymentAttemptFailed;
888
886
  }
887
+ /**
888
+ * Get or create the OAuthClient (lazy initialization to support async accountId)
889
+ */
890
+ async getOAuthClient() {
891
+ if (this.oauthClient) {
892
+ return this.oauthClient;
893
+ }
894
+ const accountId = await this.account.getAccountId();
895
+ this.oauthClient = new OAuthClient({
896
+ userId: accountId,
897
+ db: this.db,
898
+ callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
899
+ isPublic: false,
900
+ fetchFn: this.safeFetchFn,
901
+ sideChannelFetch: this.sideChannelFetch,
902
+ strict: this.strict,
903
+ allowInsecureRequests: this.allowInsecureRequests,
904
+ logger: this.logger
905
+ });
906
+ return this.oauthClient;
907
+ }
889
908
  }
890
909
 
891
910
  var util$1;