@tronweb3/tronwallet-adapter-tokenpocket 1.1.0 → 1.2.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/README.md +2 -0
- package/lib/cjs/adapter.js +27 -7
- package/lib/cjs/adapter.js.map +1 -1
- package/lib/esm/adapter.js +27 -7
- package/lib/esm/adapter.js.map +1 -1
- package/lib/types/adapter.d.ts +1 -1
- package/lib/types/adapter.d.ts.map +1 -1
- package/lib/umd/index.js +359 -90
- package/lib/umd/index.min.js +1 -1
- package/package.json +51 -51
- package/src/adapter.ts +27 -7
package/lib/umd/index.js
CHANGED
|
@@ -519,13 +519,60 @@
|
|
|
519
519
|
function isPlainObject(value) {
|
|
520
520
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
521
521
|
}
|
|
522
|
+
/** Non-negative decimal integer, e.g. `1`, `728126428`. */
|
|
523
|
+
const DECIMAL_INTEGER = /^[0-9]+$/;
|
|
524
|
+
/** Non-negative hex integer, e.g. `0x2b6653dc`. `BigInt` accepts either prefix case. */
|
|
525
|
+
const HEX_INTEGER = /^0[xX][0-9a-fA-F]+$/;
|
|
526
|
+
/**
|
|
527
|
+
* Convert a `domain.chainId` to the `number` wallets expect, refusing anything
|
|
528
|
+
* that cannot survive the trip.
|
|
529
|
+
*
|
|
530
|
+
* EIP-712 declares `chainId` as `uint256`, but `Number()` maps that domain onto
|
|
531
|
+
* IEEE-754 doubles and quietly accepts values that are not integers at all:
|
|
532
|
+
* `Number('9007199254740993')` yields `9007199254740992`, and `'1.5'`, `'-1'`,
|
|
533
|
+
* `'1e3'` and `''` all pass `Number.isFinite()`. Each of those makes the wallet
|
|
534
|
+
* sign a domain that differs from what the caller supplied, so the signature no
|
|
535
|
+
* longer corresponds to the original payload. Parsing through `BigInt` keeps the
|
|
536
|
+
* value exact and lets the unrepresentable cases fail loudly instead.
|
|
537
|
+
*/
|
|
538
|
+
function toChainIdNumber(chainId) {
|
|
539
|
+
// Reject anything that is not an integer literal before converting. `Number()`
|
|
540
|
+
// happily turns `'1.5'`, `'-1'`, `'1e3'` and `''` into finite numbers, none of
|
|
541
|
+
// which is a uint256.
|
|
542
|
+
if (typeof chainId === 'string') {
|
|
543
|
+
const value = chainId.trim();
|
|
544
|
+
if (!DECIMAL_INTEGER.test(value) && !HEX_INTEGER.test(value)) {
|
|
545
|
+
throw new errors_js_1.WalletSignTypedDataError(`Invalid domain.chainId: ${chainId}. Expected a non-negative decimal or 0x-prefixed hex integer.`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const parsed = Number(chainId);
|
|
549
|
+
// Catches NaN, Infinity and fractions, which only a number/bigint input can
|
|
550
|
+
// still be at this point — the string form was already gated by the regexes.
|
|
551
|
+
if (!Number.isInteger(parsed)) {
|
|
552
|
+
throw new errors_js_1.WalletSignTypedDataError(`Invalid domain.chainId: ${chainId}. Expected a non-negative integer.`);
|
|
553
|
+
}
|
|
554
|
+
if (parsed < 0) {
|
|
555
|
+
throw new errors_js_1.WalletSignTypedDataError(`Invalid domain.chainId: ${chainId}. Expected a non-negative integer.`);
|
|
556
|
+
}
|
|
557
|
+
// With integrality established, `isSafeInteger` is an exact bound check rather
|
|
558
|
+
// than an approximation: every integer up to MAX_SAFE_INTEGER converts
|
|
559
|
+
// losslessly, and anything larger necessarily rounds to 2^53 or beyond, which is
|
|
560
|
+
// never "safe". So a value that survives this equals what the caller supplied.
|
|
561
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
562
|
+
throw new errors_js_1.WalletSignTypedDataError(`domain.chainId ${chainId} exceeds Number.MAX_SAFE_INTEGER (${Number.MAX_SAFE_INTEGER}) and cannot be represented as a number without losing precision.`);
|
|
563
|
+
}
|
|
564
|
+
return parsed;
|
|
565
|
+
}
|
|
522
566
|
/**
|
|
523
567
|
* Validate a TypedData payload and return a normalized copy safe to pass to wallets.
|
|
524
568
|
*
|
|
525
|
-
* Current normalization: convert `domain.chainId`
|
|
526
|
-
* e.g. TronLink, require a numeric chainId).
|
|
569
|
+
* Current normalization: convert `domain.chainId` to a number (some wallets,
|
|
570
|
+
* e.g. TronLink, require a numeric chainId). `null`/`undefined` are left alone
|
|
571
|
+
* because `chainId` is optional, as are values of other types — a `BigNumber`
|
|
572
|
+
* instance is a legitimate `BigNumberish` and is passed through untouched.
|
|
527
573
|
*
|
|
528
|
-
* Throws {@link WalletSignTypedDataError} if the payload is not a valid TypedData
|
|
574
|
+
* Throws {@link WalletSignTypedDataError} if the payload is not a valid TypedData
|
|
575
|
+
* object, or if `domain.chainId` cannot be represented exactly as a number.
|
|
529
576
|
*/
|
|
530
577
|
function normalizeAndValidateTypedData(typedData) {
|
|
531
578
|
if (!isPlainObject(typedData)) {
|
|
@@ -541,10 +588,10 @@
|
|
|
541
588
|
throw new errors_js_1.WalletSignTypedDataError('typedData.message must be an object.');
|
|
542
589
|
}
|
|
543
590
|
const { chainId } = typedData.domain;
|
|
544
|
-
if (typeof chainId === 'string') {
|
|
545
|
-
const parsed =
|
|
546
|
-
if (
|
|
547
|
-
|
|
591
|
+
if (typeof chainId === 'string' || typeof chainId === 'number' || typeof chainId === 'bigint') {
|
|
592
|
+
const parsed = toChainIdNumber(chainId);
|
|
593
|
+
if (parsed === chainId) {
|
|
594
|
+
return typedData;
|
|
548
595
|
}
|
|
549
596
|
return Object.assign(Object.assign({}, typedData), { domain: Object.assign(Object.assign({}, typedData.domain), { chainId: parsed }) });
|
|
550
597
|
}
|
|
@@ -685,58 +732,135 @@
|
|
|
685
732
|
function requireUtils$2 () {
|
|
686
733
|
if (hasRequiredUtils$2) return utils$2;
|
|
687
734
|
hasRequiredUtils$2 = 1;
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
735
|
+
(function (exports$1) {
|
|
736
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
737
|
+
exports$1.MAX_CHECK_TIMEOUT = void 0;
|
|
738
|
+
exports$1.validateCheckTimeout = validateCheckTimeout;
|
|
739
|
+
exports$1.omitUndefined = omitUndefined;
|
|
740
|
+
exports$1.assertConnectAddress = assertConnectAddress;
|
|
741
|
+
exports$1.isInBrowser = isInBrowser;
|
|
742
|
+
exports$1.checkAdapterState = checkAdapterState;
|
|
743
|
+
exports$1.isInMobileBrowser = isInMobileBrowser;
|
|
744
|
+
const errors_js_1 = requireErrors();
|
|
745
|
+
/**
|
|
746
|
+
* Upper bound for `config.checkTimeout`, in milliseconds.
|
|
747
|
+
*
|
|
748
|
+
* Wallet detection polls on a fixed interval and gives up after
|
|
749
|
+
* `checkTimeout / interval` ticks. An unbounded value makes that loop
|
|
750
|
+
* indistinguishable from one that never stops, so it is capped. Ten minutes is
|
|
751
|
+
* far beyond any realistic detection window.
|
|
752
|
+
*/
|
|
753
|
+
exports$1.MAX_CHECK_TIMEOUT = 10 * 60 * 1000;
|
|
754
|
+
/**
|
|
755
|
+
* Validate `config.checkTimeout` and throw when it cannot produce a terminating
|
|
756
|
+
* detection loop.
|
|
757
|
+
*
|
|
758
|
+
* `typeof value === 'number'` is not enough on its own: `NaN` and `Infinity` are
|
|
759
|
+
* numbers, and adapters derive their polling bound from
|
|
760
|
+
* `Math.floor(checkTimeout / interval)`. With `NaN` that bound is `NaN`, so
|
|
761
|
+
* `times > maxTimes` is never true — the interval is never cleared and the
|
|
762
|
+
* detection promise never settles, hanging `connect()` for the life of the page.
|
|
763
|
+
* TypeScript cannot prevent this because adapters are also constructed from
|
|
764
|
+
* plain JavaScript, JSON config and `as any`.
|
|
765
|
+
*
|
|
766
|
+
* @param value the raw value taken from the adapter config
|
|
767
|
+
* @param prefix prefix used in the thrown message, e.g. `[TronLinkAdapter]`
|
|
768
|
+
*/
|
|
769
|
+
function validateCheckTimeout(value, prefix = '[WalletAdapter]') {
|
|
770
|
+
if (typeof value !== 'number') {
|
|
771
|
+
throw new Error(`${prefix} config.checkTimeout should be a number`);
|
|
772
|
+
}
|
|
773
|
+
if (!Number.isFinite(value) || value < 0 || value > exports$1.MAX_CHECK_TIMEOUT) {
|
|
774
|
+
throw new Error(`${prefix} config.checkTimeout should be a finite number between 0 and ${exports$1.MAX_CHECK_TIMEOUT}, but got ${value}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Copy an object without the keys whose value is exactly `undefined`.
|
|
779
|
+
*
|
|
780
|
+
* Spreading a caller's config over a set of defaults is only safe when explicit
|
|
781
|
+
* `undefined`s have been dropped first: `{ ...defaults, ...{ checkTimeout: undefined } }`
|
|
782
|
+
* yields `checkTimeout: undefined`, which defeats the default and every validation
|
|
783
|
+
* that ran against it. Callers routinely produce such objects by spreading their own
|
|
784
|
+
* optional config, so adapters must sanitise before merging rather than trusting the
|
|
785
|
+
* `Required<...>` type on the resulting field.
|
|
786
|
+
*
|
|
787
|
+
* @param params the raw config object, possibly `undefined`
|
|
788
|
+
* @returns a shallow copy containing only the keys that were actually set
|
|
789
|
+
*/
|
|
790
|
+
function omitUndefined(params) {
|
|
791
|
+
return Object.fromEntries(Object.entries(params !== null && params !== void 0 ? params : {}).filter(([, value]) => value !== undefined));
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Guard the transition into `AdapterState.Connected`.
|
|
795
|
+
*
|
|
796
|
+
* A resolved account request is not the same thing as a connection. Wallets
|
|
797
|
+
* answer `tron_requestAccounts`/`eth_requestAccounts` with a success code while
|
|
798
|
+
* still returning an empty account list, and injected `tronWeb.defaultAddress`
|
|
799
|
+
* is frequently not populated yet at the moment the request resolves. Treating
|
|
800
|
+
* either as "connected" leaves the adapter reporting `connected === true` with
|
|
801
|
+
* no address, and emits `connect('')` to the dapp; every later signMessage or
|
|
802
|
+
* signTransaction then passes the state guard only to fail inside the provider.
|
|
803
|
+
*
|
|
804
|
+
* @param address the address the adapter is about to store. `false` is accepted
|
|
805
|
+
* because tronweb types `tronWeb.defaultAddress.base58` as `string | false`.
|
|
806
|
+
* @returns the same address, narrowed to a non-empty string
|
|
807
|
+
* @throws {WalletConnectionError} when no usable address was obtained
|
|
808
|
+
*/
|
|
809
|
+
function assertConnectAddress(address) {
|
|
810
|
+
if (!address) {
|
|
811
|
+
throw new errors_js_1.WalletConnectionError('Request connect error.');
|
|
812
|
+
}
|
|
813
|
+
return address;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* check simply if current environment is browser or not
|
|
817
|
+
* @returns boolean
|
|
818
|
+
*/
|
|
819
|
+
function isInBrowser() {
|
|
820
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined';
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
*
|
|
824
|
+
* @param {Function} check funcion to check if wallet is installed. return true if wallet is detected.
|
|
825
|
+
* @returns
|
|
826
|
+
*/
|
|
827
|
+
function checkAdapterState(check) {
|
|
828
|
+
if (!isInBrowser())
|
|
829
|
+
return;
|
|
830
|
+
const disposers = [];
|
|
831
|
+
function dispose() {
|
|
832
|
+
for (const dispose of disposers) {
|
|
833
|
+
dispose();
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function checkAndDispose() {
|
|
837
|
+
if (check()) {
|
|
838
|
+
dispose();
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
const interval = setInterval(checkAndDispose, 500);
|
|
842
|
+
disposers.push(() => clearInterval(interval));
|
|
843
|
+
if (document.readyState === 'loading') {
|
|
844
|
+
document.addEventListener('DOMContentLoaded', checkAndDispose, { once: true });
|
|
845
|
+
disposers.push(() => document.removeEventListener('DOMContentLoaded', checkAndDispose));
|
|
846
|
+
}
|
|
847
|
+
if (document.readyState !== 'complete') {
|
|
848
|
+
window.addEventListener('load', checkAndDispose, { once: true });
|
|
849
|
+
disposers.push(() => window.removeEventListener('load', checkAndDispose));
|
|
850
|
+
}
|
|
851
|
+
checkAndDispose();
|
|
852
|
+
// stop all task after 1min
|
|
853
|
+
setTimeout(dispose, 60 * 1000);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Simplily detect mobile device
|
|
857
|
+
*/
|
|
858
|
+
function isInMobileBrowser() {
|
|
859
|
+
return (typeof navigator !== 'undefined' &&
|
|
860
|
+
navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
} (utils$2));
|
|
740
864
|
return utils$2;
|
|
741
865
|
}
|
|
742
866
|
|
|
@@ -761,6 +885,7 @@
|
|
|
761
885
|
};
|
|
762
886
|
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
763
887
|
exports$1.defaultSecurityOptions = void 0;
|
|
888
|
+
exports$1.validateSecurityOptions = validateSecurityOptions;
|
|
764
889
|
exports$1.clearCache = clearCache;
|
|
765
890
|
exports$1.fetchJsonWithCache = fetchJsonWithCache;
|
|
766
891
|
exports$1.defaultSecurityOptions = {
|
|
@@ -773,6 +898,83 @@
|
|
|
773
898
|
retries: 0,
|
|
774
899
|
cacheTTL: 10 * 60 * 1000,
|
|
775
900
|
};
|
|
901
|
+
/** A config fetch that takes longer than this is never worth waiting for. */
|
|
902
|
+
const MAX_SECURITY_TIMEOUT = 60 * 1000;
|
|
903
|
+
/** `retries` multiplies the time `connect()` can block, so it is capped. */
|
|
904
|
+
const MAX_SECURITY_RETRIES = 10;
|
|
905
|
+
function assertFiniteNumber(value, field, min, max) {
|
|
906
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
|
|
907
|
+
throw new Error(`[WalletAdapter] config.securityOptions.${field} should be a finite number between ${min} and ${max}, but got ${String(value)}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function parseAbsoluteUrl(url) {
|
|
911
|
+
try {
|
|
912
|
+
return new URL(url);
|
|
913
|
+
}
|
|
914
|
+
catch (_a) {
|
|
915
|
+
// Not absolute — a relative path, which `fetch` resolves against the page.
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function validateConfigUrls(configUrls) {
|
|
920
|
+
if (!Array.isArray(configUrls)) {
|
|
921
|
+
throw new Error(`[WalletAdapter] config.securityOptions.configUrls should be an array of URLs, but got ${typeof configUrls}`);
|
|
922
|
+
}
|
|
923
|
+
for (const url of configUrls) {
|
|
924
|
+
if (typeof url !== 'string' || url.trim() === '') {
|
|
925
|
+
throw new Error(`[WalletAdapter] config.securityOptions.configUrls should only contain non-empty URL strings, but got ${String(url)}`);
|
|
926
|
+
}
|
|
927
|
+
// Relative paths are legitimate — they resolve against the page and `new URL`
|
|
928
|
+
// rejects them — so only values that parse as an absolute URL are checked,
|
|
929
|
+
// and then only for the scheme `fetch` can actually retrieve.
|
|
930
|
+
const parsed = parseAbsoluteUrl(url);
|
|
931
|
+
if (parsed && parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
932
|
+
throw new Error(`[WalletAdapter] config.securityOptions.configUrls only supports http(s) URLs, but got ${url}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Validate `config.securityOptions` at construction time.
|
|
938
|
+
*
|
|
939
|
+
* Every field here is consumed much later, inside `connect()` — a `configUrls`
|
|
940
|
+
* string instead of an array only fails when `fetchJsonWithCache` calls `.map`
|
|
941
|
+
* on it, and a `NaN` timeout or a negative `retries` silently changes the fetch
|
|
942
|
+
* behaviour rather than reporting anything. Failing fast in the constructor puts
|
|
943
|
+
* the error where the caller can act on it.
|
|
944
|
+
*/
|
|
945
|
+
function validateSecurityOptions(securityOptions) {
|
|
946
|
+
const { enabled, configUrls, timeout, retries, cacheTTL, onRiskDetected, onConfigFallback } = securityOptions;
|
|
947
|
+
// `enabled` is read as a plain truthy value in `checkSecurity()` and again just
|
|
948
|
+
// below, so a non-boolean silently resolves to the opposite of what it reads like:
|
|
949
|
+
// `'false'` is truthy and turns the check on. Check it before the `configUrls`
|
|
950
|
+
// requirement below, or `{ enabled: 'false' }` reports a missing `configUrls`
|
|
951
|
+
// "when enabled is true" — for a caller who wrote `false`.
|
|
952
|
+
if (enabled !== undefined && typeof enabled !== 'boolean') {
|
|
953
|
+
throw new Error(`[WalletAdapter] config.securityOptions.enabled should be a boolean, but got ${typeof enabled}`);
|
|
954
|
+
}
|
|
955
|
+
if (configUrls !== undefined) {
|
|
956
|
+
validateConfigUrls(configUrls);
|
|
957
|
+
}
|
|
958
|
+
if (enabled && (!configUrls || configUrls.length === 0)) {
|
|
959
|
+
throw new Error(`[WalletAdapter] config.securityOptions.configUrls is required when securityOptions.enabled is true`);
|
|
960
|
+
}
|
|
961
|
+
if (timeout !== undefined) {
|
|
962
|
+
assertFiniteNumber(timeout, 'timeout', 1, MAX_SECURITY_TIMEOUT);
|
|
963
|
+
}
|
|
964
|
+
if (cacheTTL !== undefined) {
|
|
965
|
+
// No upper bound: caching the risk config for a long time is a valid choice.
|
|
966
|
+
assertFiniteNumber(cacheTTL, 'cacheTTL', 0, Number.MAX_SAFE_INTEGER);
|
|
967
|
+
}
|
|
968
|
+
if (retries !== undefined && (!Number.isInteger(retries) || retries < 0 || retries > MAX_SECURITY_RETRIES)) {
|
|
969
|
+
throw new Error(`[WalletAdapter] config.securityOptions.retries should be an integer between 0 and ${MAX_SECURITY_RETRIES}, but got ${String(retries)}`);
|
|
970
|
+
}
|
|
971
|
+
if (onRiskDetected !== undefined && typeof onRiskDetected !== 'function') {
|
|
972
|
+
throw new Error(`[WalletAdapter] config.securityOptions.onRiskDetected should be a function, but got ${typeof onRiskDetected}`);
|
|
973
|
+
}
|
|
974
|
+
if (onConfigFallback !== undefined && typeof onConfigFallback !== 'function') {
|
|
975
|
+
throw new Error(`[WalletAdapter] config.securityOptions.onConfigFallback should be a function, but got ${typeof onConfigFallback}`);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
776
978
|
function isValidRisk(risk) {
|
|
777
979
|
return (!!risk &&
|
|
778
980
|
typeof risk === 'object' &&
|
|
@@ -944,18 +1146,16 @@
|
|
|
944
1146
|
openUrlWhenWalletNotFound: true,
|
|
945
1147
|
securityOptions: security_js_1.defaultSecurityOptions,
|
|
946
1148
|
};
|
|
1149
|
+
this._connectPromise = null;
|
|
947
1150
|
this._securityCheckCache = null;
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
if (enabled && (!configUrls || configUrls.length === 0)) {
|
|
957
|
-
throw new Error(`[WalletAdapter] config.securityOptions.configUrls is required when securityOptions.enabled is true`);
|
|
958
|
-
}
|
|
1151
|
+
// Drop explicit `undefined`s before merging: spreading them would overwrite
|
|
1152
|
+
// the defaults with `undefined` and make `Required<BaseAdapterConfig>` a lie,
|
|
1153
|
+
// which matters because callers routinely spread optional config objects.
|
|
1154
|
+
// Subclasses that re-merge their own config on top of `commonConfig` must
|
|
1155
|
+
// sanitise it the same way — see `omitUndefined`.
|
|
1156
|
+
this.commonConfig = Object.assign(Object.assign({}, this.commonConfig), (0, utils_js_1.omitUndefined)(params));
|
|
1157
|
+
(0, utils_js_1.validateCheckTimeout)(this.commonConfig.checkTimeout);
|
|
1158
|
+
(0, security_js_1.validateSecurityOptions)(this.commonConfig.securityOptions);
|
|
959
1159
|
}
|
|
960
1160
|
/**
|
|
961
1161
|
* Update the security options at runtime. The given options are merged into
|
|
@@ -964,10 +1164,41 @@
|
|
|
964
1164
|
*/
|
|
965
1165
|
updateSecurityOptions(securityOptions) {
|
|
966
1166
|
const merged = Object.assign(Object.assign({}, this.commonConfig.securityOptions), securityOptions);
|
|
967
|
-
|
|
1167
|
+
(0, security_js_1.validateSecurityOptions)(merged);
|
|
968
1168
|
this.commonConfig.securityOptions = merged;
|
|
969
1169
|
this._securityCheckCache = null;
|
|
970
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Serialise `connect()` so that concurrent callers cannot each reach the wallet.
|
|
1173
|
+
*
|
|
1174
|
+
* The `connecting` flag alone cannot do this. Subclasses only raise it *after*
|
|
1175
|
+
* `await this._beforeConnect()` resolves, and `_beforeConnect()` itself awaits
|
|
1176
|
+
* wallet discovery and the security check — so two calls made in the same tick
|
|
1177
|
+
* both pass the guard while it is still `false`, and both go on to call
|
|
1178
|
+
* `tron_requestAccounts`. That produces a second authorisation popup, a `4000`
|
|
1179
|
+
* "pending request" error from the provider, and duplicated connect events.
|
|
1180
|
+
*
|
|
1181
|
+
* Claiming the in-flight promise here is synchronous — there is no `await`
|
|
1182
|
+
* between the check and the assignment — so a second caller always observes it
|
|
1183
|
+
* and simply shares the first call's result. Because the whole subclass
|
|
1184
|
+
* `_connect()` runs inside it, discovery, the security check and the provider
|
|
1185
|
+
* request are all in the same critical section.
|
|
1186
|
+
*/
|
|
1187
|
+
connect(options) {
|
|
1188
|
+
if (this._connectPromise) {
|
|
1189
|
+
return this._connectPromise;
|
|
1190
|
+
}
|
|
1191
|
+
// Deliberately not `async`: that would wrap the shared promise in a fresh one
|
|
1192
|
+
// per caller. The IIFE still turns a synchronous throw from `_connect()` into
|
|
1193
|
+
// a rejection, so callers never see one thrown at them directly.
|
|
1194
|
+
const promise = (() => __awaiter(this, void 0, void 0, function* () { return this._connect(options); }))().finally(() => {
|
|
1195
|
+
if (this._connectPromise === promise) {
|
|
1196
|
+
this._connectPromise = null;
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
this._connectPromise = promise;
|
|
1200
|
+
return promise;
|
|
1201
|
+
}
|
|
971
1202
|
/**
|
|
972
1203
|
* Run the pre-connect pipeline (wallet discovery, deeplink/url fallback,
|
|
973
1204
|
* security check).
|
|
@@ -1238,8 +1469,8 @@
|
|
|
1238
1469
|
this._updateWallet = () => __awaiter(this, void 0, void 0, function* () {
|
|
1239
1470
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1240
1471
|
this._supportNewTronProtocol = false;
|
|
1241
|
-
let state
|
|
1242
|
-
let address
|
|
1472
|
+
let state;
|
|
1473
|
+
let address;
|
|
1243
1474
|
if ((0, tronwallet_abstract_adapter_1.isInMobileBrowser)()) {
|
|
1244
1475
|
if (window.tronLink) {
|
|
1245
1476
|
this._wallet = window.tronLink;
|
|
@@ -1278,8 +1509,6 @@
|
|
|
1278
1509
|
}
|
|
1279
1510
|
catch (e) {
|
|
1280
1511
|
console.error('Unknown error: ' + e, ' Please install TronLink extension wallet.');
|
|
1281
|
-
address = null;
|
|
1282
|
-
state = tronwallet_abstract_adapter_1.AdapterState.Disconnect;
|
|
1283
1512
|
this._readyState = tronwallet_abstract_adapter_1.WalletReadyState.NotFound;
|
|
1284
1513
|
this.emit('readyStateChanged', this.readyState);
|
|
1285
1514
|
return;
|
|
@@ -1384,7 +1613,7 @@
|
|
|
1384
1613
|
}
|
|
1385
1614
|
});
|
|
1386
1615
|
}
|
|
1387
|
-
|
|
1616
|
+
_connect() {
|
|
1388
1617
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1389
1618
|
var _a, _b;
|
|
1390
1619
|
try {
|
|
@@ -1398,7 +1627,7 @@
|
|
|
1398
1627
|
const wallet = this._wallet;
|
|
1399
1628
|
try {
|
|
1400
1629
|
const res = yield wallet.request({ method: 'eth_requestAccounts' });
|
|
1401
|
-
const address = res[0];
|
|
1630
|
+
const address = (0, tronwallet_abstract_adapter_1.assertConnectAddress)(res === null || res === void 0 ? void 0 : res[0]);
|
|
1402
1631
|
this.setAddress(address);
|
|
1403
1632
|
this.setState(tronwallet_abstract_adapter_1.AdapterState.Connected);
|
|
1404
1633
|
this._listenTronEvent();
|
|
@@ -1432,13 +1661,13 @@
|
|
|
1432
1661
|
if (res.code === 4001) {
|
|
1433
1662
|
throw new tronwallet_abstract_adapter_1.WalletConnectionError('The user rejected connection.');
|
|
1434
1663
|
}
|
|
1435
|
-
const address = ((_a = wallet.tronWeb.defaultAddress) === null || _a === void 0 ? void 0 : _a.base58)
|
|
1664
|
+
const address = (0, tronwallet_abstract_adapter_1.assertConnectAddress)((_a = wallet.tronWeb.defaultAddress) === null || _a === void 0 ? void 0 : _a.base58);
|
|
1436
1665
|
this.setAddress(address);
|
|
1437
1666
|
this.setState(tronwallet_abstract_adapter_1.AdapterState.Connected);
|
|
1438
1667
|
}
|
|
1439
1668
|
else if (window.tronWeb) {
|
|
1440
1669
|
const wallet = this._wallet;
|
|
1441
|
-
const address = ((_b = wallet.tronWeb.defaultAddress) === null || _b === void 0 ? void 0 : _b.base58)
|
|
1670
|
+
const address = (0, tronwallet_abstract_adapter_1.assertConnectAddress)((_b = wallet.tronWeb.defaultAddress) === null || _b === void 0 ? void 0 : _b.base58);
|
|
1442
1671
|
this.setAddress(address);
|
|
1443
1672
|
this.setState(tronwallet_abstract_adapter_1.AdapterState.Connected);
|
|
1444
1673
|
}
|
|
@@ -1615,7 +1844,7 @@
|
|
|
1615
1844
|
}
|
|
1616
1845
|
if ((0, tronwallet_abstract_adapter_1.isInBrowser)() && !(0, tronwallet_abstract_adapter_1.isInMobileBrowser)()) {
|
|
1617
1846
|
// Desktop: use TIP-6963 event-based discovery with fallback
|
|
1618
|
-
|
|
1847
|
+
const detection = new Promise((resolve) => {
|
|
1619
1848
|
let handled = false;
|
|
1620
1849
|
let timer = null;
|
|
1621
1850
|
let handler = null;
|
|
@@ -1682,13 +1911,23 @@
|
|
|
1682
1911
|
window.addEventListener(tronwallet_abstract_adapter_1.TIP6963AnnounceProviderEventName, handler);
|
|
1683
1912
|
window.dispatchEvent(new Event(tronwallet_abstract_adapter_1.TIP6963RequestProviderEventName));
|
|
1684
1913
|
});
|
|
1685
|
-
|
|
1914
|
+
this._checkPromise = detection;
|
|
1915
|
+
// Never cache a failed detection: the wallet may inject late or be enabled at
|
|
1916
|
+
// runtime. Unlike the polling adapters this keeps the full detection window on a
|
|
1917
|
+
// retry, because the TIP-6963 announce is asynchronous and a shortened window
|
|
1918
|
+
// would race it.
|
|
1919
|
+
void detection.then((found) => {
|
|
1920
|
+
if (!found && this._checkPromise === detection) {
|
|
1921
|
+
this._checkPromise = null;
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
return detection;
|
|
1686
1925
|
}
|
|
1687
1926
|
// Mobile: use legacy polling detection
|
|
1688
1927
|
const interval = 100;
|
|
1689
1928
|
const maxTimes = Math.floor(this.config.checkTimeout / interval);
|
|
1690
1929
|
let times = 0, timer;
|
|
1691
|
-
|
|
1930
|
+
const detection = new Promise((resolve) => {
|
|
1692
1931
|
const check = () => {
|
|
1693
1932
|
times++;
|
|
1694
1933
|
this._updateWallet();
|
|
@@ -1703,7 +1942,17 @@
|
|
|
1703
1942
|
timer = setInterval(check, interval);
|
|
1704
1943
|
check();
|
|
1705
1944
|
});
|
|
1706
|
-
|
|
1945
|
+
this._checkPromise = detection;
|
|
1946
|
+
// Never cache a failed detection: the wallet may inject late or be enabled at
|
|
1947
|
+
// runtime. Unlike the polling adapters this keeps the full detection window on a
|
|
1948
|
+
// retry, because the TIP-6963 announce is asynchronous and a shortened window
|
|
1949
|
+
// would race it.
|
|
1950
|
+
void detection.then((found) => {
|
|
1951
|
+
if (!found && this._checkPromise === detection) {
|
|
1952
|
+
this._checkPromise = null;
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
return detection;
|
|
1707
1956
|
}
|
|
1708
1957
|
checkForWalletReadyForApp() {
|
|
1709
1958
|
if (this.checkReadyInterval) {
|
|
@@ -1895,8 +2144,8 @@
|
|
|
1895
2144
|
this._checkPromise = null;
|
|
1896
2145
|
this._updateWallet = () => __awaiter(this, void 0, void 0, function* () {
|
|
1897
2146
|
var _a, _b, _c, _d, _e;
|
|
1898
|
-
let state
|
|
1899
|
-
let address
|
|
2147
|
+
let state;
|
|
2148
|
+
let address;
|
|
1900
2149
|
if ((0, utils_js_1.supportTokenPocket)()) {
|
|
1901
2150
|
try {
|
|
1902
2151
|
yield this.checkSecurity();
|
|
@@ -1993,7 +2242,7 @@
|
|
|
1993
2242
|
}
|
|
1994
2243
|
});
|
|
1995
2244
|
}
|
|
1996
|
-
|
|
2245
|
+
_connect() {
|
|
1997
2246
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1998
2247
|
try {
|
|
1999
2248
|
if (!(yield this._beforeConnect()))
|
|
@@ -2199,7 +2448,7 @@
|
|
|
2199
2448
|
return this._checkPromise;
|
|
2200
2449
|
}
|
|
2201
2450
|
if ((0, tronwallet_abstract_adapter_1.isInBrowser)() && !(0, tronwallet_abstract_adapter_1.isInMobileBrowser)()) {
|
|
2202
|
-
|
|
2451
|
+
const detection = new Promise((resolve) => {
|
|
2203
2452
|
const timer = setTimeout(() => {
|
|
2204
2453
|
window.removeEventListener(tronwallet_abstract_adapter_2.TIP6963AnnounceProviderEventName, handler);
|
|
2205
2454
|
this._updateWallet();
|
|
@@ -2253,13 +2502,23 @@
|
|
|
2253
2502
|
window.addEventListener(tronwallet_abstract_adapter_2.TIP6963AnnounceProviderEventName, handler);
|
|
2254
2503
|
window.dispatchEvent(new Event(tronwallet_abstract_adapter_2.TIP6963RequestProviderEventName));
|
|
2255
2504
|
});
|
|
2256
|
-
|
|
2505
|
+
this._checkPromise = detection;
|
|
2506
|
+
// Never cache a failed detection: the wallet may inject late or be enabled at
|
|
2507
|
+
// runtime. Unlike the polling adapters this keeps the full detection window on a
|
|
2508
|
+
// retry, because the TIP-6963 announce is asynchronous and a shortened window
|
|
2509
|
+
// would race it.
|
|
2510
|
+
void detection.then((found) => {
|
|
2511
|
+
if (!found && this._checkPromise === detection) {
|
|
2512
|
+
this._checkPromise = null;
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
return detection;
|
|
2257
2516
|
}
|
|
2258
2517
|
// Support TIP-6963 with wallet extension
|
|
2259
2518
|
const interval = 100;
|
|
2260
2519
|
const maxTimes = Math.floor(this.config.checkTimeout / interval);
|
|
2261
2520
|
let times = 0, timer;
|
|
2262
|
-
|
|
2521
|
+
const detection = new Promise((resolve) => {
|
|
2263
2522
|
const check = () => {
|
|
2264
2523
|
times++;
|
|
2265
2524
|
const isSupport = (0, utils_js_1.supportTokenPocket)();
|
|
@@ -2274,7 +2533,17 @@
|
|
|
2274
2533
|
timer = setInterval(check, interval);
|
|
2275
2534
|
check();
|
|
2276
2535
|
});
|
|
2277
|
-
|
|
2536
|
+
this._checkPromise = detection;
|
|
2537
|
+
// Never cache a failed detection: the wallet may inject late or be enabled at
|
|
2538
|
+
// runtime. Unlike the polling adapters this keeps the full detection window on a
|
|
2539
|
+
// retry, because the TIP-6963 announce is asynchronous and a shortened window
|
|
2540
|
+
// would race it.
|
|
2541
|
+
void detection.then((found) => {
|
|
2542
|
+
if (!found && this._checkPromise === detection) {
|
|
2543
|
+
this._checkPromise = null;
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2546
|
+
return detection;
|
|
2278
2547
|
}
|
|
2279
2548
|
setAddress(address) {
|
|
2280
2549
|
this._address = address;
|