@explorins/pers-sdk-react-native 1.5.26 → 1.5.28
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 +84 -73
- package/dist/hooks/useAnalytics.d.ts +1 -65
- package/dist/hooks/useAnalytics.d.ts.map +1 -1
- package/dist/hooks/useTransactions.d.ts +2 -3
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +741 -161
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +11 -0
- package/dist/providers/react-native-auth-provider.d.ts.map +1 -1
- package/dist/providers/react-native-auth-provider.js +2 -2
- package/dist/providers/rn-dpop-provider.d.ts +19 -0
- package/dist/providers/rn-dpop-provider.d.ts.map +1 -0
- package/dist/providers/rn-dpop-provider.js +93 -0
- package/dist/storage/rn-secure-storage.d.ts +18 -0
- package/dist/storage/rn-secure-storage.d.ts.map +1 -0
- package/dist/storage/rn-secure-storage.js +112 -0
- package/package.json +6 -5
- package/src/hooks/useAnalytics.ts +4 -76
- package/src/hooks/useTransactions.ts +3 -3
- package/src/index.ts +24 -0
- package/src/providers/PersSDKProvider.tsx +13 -1
- package/src/providers/react-native-auth-provider.ts +2 -1
- package/src/providers/rn-dpop-provider.ts +104 -0
- package/src/storage/rn-secure-storage.ts +120 -0
package/dist/index.js
CHANGED
|
@@ -2631,6 +2631,7 @@ exports.ApiKeyType = void 0;
|
|
|
2631
2631
|
ApiKeyType["USER_JWT_REFRESH_TOKEN"] = "USER_JWT_REFRESH_TOKEN";
|
|
2632
2632
|
ApiKeyType["BLOCKCHAIN_WRITER_JWT"] = "BLOCKCHAIN_WRITER_JWT";
|
|
2633
2633
|
ApiKeyType["BLOCKCHAIN_READER_JWT"] = "BLOCKCHAIN_READER_JWT";
|
|
2634
|
+
ApiKeyType["TRANSACTION_JWT_ACCESS_TOKEN"] = "TRANSACTION_JWT_ACCESS_TOKEN";
|
|
2634
2635
|
// TODO: this needs to be removed. However, there is still dependency
|
|
2635
2636
|
ApiKeyType["TENANT_LEGACY_ADMIN"] = "TENANT_ADMIN_JWT";
|
|
2636
2637
|
// TENANT_SYSTEM_API_SECRET = 'TENANT_SYSTEM_API_SECRET',
|
|
@@ -2656,23 +2657,6 @@ exports.Web3ContractTypes = void 0;
|
|
|
2656
2657
|
Web3ContractTypes["TOKEN_CONTRACT"] = "TOKEN_CONTRACT";
|
|
2657
2658
|
})(exports.Web3ContractTypes || (exports.Web3ContractTypes = {}));
|
|
2658
2659
|
|
|
2659
|
-
exports.CampaignConditionType = void 0;
|
|
2660
|
-
(function (CampaignConditionType) {
|
|
2661
|
-
CampaignConditionType["EQUALS"] = "EQUALS";
|
|
2662
|
-
CampaignConditionType["NOT_EQUALS"] = "NOT_EQUALS";
|
|
2663
|
-
CampaignConditionType["GREATER_THAN"] = "GREATER_THAN";
|
|
2664
|
-
CampaignConditionType["LESS_THAN"] = "LESS_THAN";
|
|
2665
|
-
CampaignConditionType["CONTAINS"] = "CONTAINS";
|
|
2666
|
-
CampaignConditionType["IS_PART_OF"] = "IS_PART_OF";
|
|
2667
|
-
})(exports.CampaignConditionType || (exports.CampaignConditionType = {}));
|
|
2668
|
-
|
|
2669
|
-
exports.CampaignTriggerType = void 0;
|
|
2670
|
-
(function (CampaignTriggerType) {
|
|
2671
|
-
CampaignTriggerType["CLAIM_BY_USER"] = "CLAIM_BY_USER";
|
|
2672
|
-
CampaignTriggerType["CLAIM_BY_SYSTEM"] = "CLAIM_BY_SYSTEM";
|
|
2673
|
-
CampaignTriggerType["CLAIM_BY_BUSINESS"] = "CLAIM_BY_BUSINESS";
|
|
2674
|
-
})(exports.CampaignTriggerType || (exports.CampaignTriggerType = {}));
|
|
2675
|
-
|
|
2676
2660
|
/**
|
|
2677
2661
|
* Entity ownership types - defines what types of entities can own resources
|
|
2678
2662
|
*
|
|
@@ -2817,6 +2801,45 @@ exports.RedemptionRedeemStatus = void 0;
|
|
|
2817
2801
|
RedemptionRedeemStatus["FAILED"] = "FAILED"; // Processing failed
|
|
2818
2802
|
})(exports.RedemptionRedeemStatus || (exports.RedemptionRedeemStatus = {}));
|
|
2819
2803
|
|
|
2804
|
+
/**
|
|
2805
|
+
* Trigger Source Types
|
|
2806
|
+
* Defines the different types of triggers that can activate a campaign flow
|
|
2807
|
+
*/
|
|
2808
|
+
exports.TriggerSourceType = void 0;
|
|
2809
|
+
(function (TriggerSourceType) {
|
|
2810
|
+
TriggerSourceType["QR_CODE"] = "QR_CODE";
|
|
2811
|
+
// NFC_TAG = 'NFC_TAG',
|
|
2812
|
+
TriggerSourceType["API_WEBHOOK"] = "API_WEBHOOK";
|
|
2813
|
+
// TRANSACTION = 'TRANSACTION',
|
|
2814
|
+
})(exports.TriggerSourceType || (exports.TriggerSourceType = {}));
|
|
2815
|
+
/**
|
|
2816
|
+
* Source Logic Types
|
|
2817
|
+
* Defines how multiple trigger sources combine to activate a flow
|
|
2818
|
+
* Currently only ANY (OR logic) is implemented
|
|
2819
|
+
*/
|
|
2820
|
+
exports.SourceLogicType = void 0;
|
|
2821
|
+
(function (SourceLogicType) {
|
|
2822
|
+
SourceLogicType["ANY"] = "any";
|
|
2823
|
+
// Future: ALL = 'all', SEQUENCE = 'sequence', WEIGHTED = 'weighted'
|
|
2824
|
+
})(exports.SourceLogicType || (exports.SourceLogicType = {}));
|
|
2825
|
+
|
|
2826
|
+
exports.CampaignConditionType = void 0;
|
|
2827
|
+
(function (CampaignConditionType) {
|
|
2828
|
+
CampaignConditionType["EQUALS"] = "EQUALS";
|
|
2829
|
+
CampaignConditionType["NOT_EQUALS"] = "NOT_EQUALS";
|
|
2830
|
+
CampaignConditionType["GREATER_THAN"] = "GREATER_THAN";
|
|
2831
|
+
CampaignConditionType["LESS_THAN"] = "LESS_THAN";
|
|
2832
|
+
CampaignConditionType["CONTAINS"] = "CONTAINS";
|
|
2833
|
+
CampaignConditionType["IS_PART_OF"] = "IS_PART_OF";
|
|
2834
|
+
})(exports.CampaignConditionType || (exports.CampaignConditionType = {}));
|
|
2835
|
+
|
|
2836
|
+
exports.CampaignTriggerType = void 0;
|
|
2837
|
+
(function (CampaignTriggerType) {
|
|
2838
|
+
CampaignTriggerType["CLAIM_BY_USER"] = "CLAIM_BY_USER";
|
|
2839
|
+
CampaignTriggerType["CLAIM_BY_SYSTEM"] = "CLAIM_BY_SYSTEM";
|
|
2840
|
+
CampaignTriggerType["CLAIM_BY_BUSINESS"] = "CLAIM_BY_BUSINESS";
|
|
2841
|
+
})(exports.CampaignTriggerType || (exports.CampaignTriggerType = {}));
|
|
2842
|
+
|
|
2820
2843
|
const apiPublicKeyTestPrefix = 'pk_test_';
|
|
2821
2844
|
const testnetPrefix = 'testnet-';
|
|
2822
2845
|
const testnetShortPrefix = 'tn-';
|
|
@@ -2937,8 +2960,6 @@ const NativeTokenTypes = {
|
|
|
2937
2960
|
ERC721: 'ERC721'
|
|
2938
2961
|
};
|
|
2939
2962
|
|
|
2940
|
-
/* // ✅ EXPRESSION ALIASES: Results from CASE WHEN expressions and other computed fields
|
|
2941
|
-
& { [key: string]: string | number | Date | undefined }; */
|
|
2942
2963
|
const ANALYTICS_METRIC_TYPES = ['count', 'sum', 'avg', 'min', 'max'];
|
|
2943
2964
|
const ANALYTICS_GROUP_BY_TYPES = ['month', 'week', 'day', 'year', 'quarter'];
|
|
2944
2965
|
|
|
@@ -23836,12 +23857,12 @@ class CampaignApi {
|
|
|
23836
23857
|
params.push(`tag=${encodeURIComponent(options.tag)}`);
|
|
23837
23858
|
if (options.limit)
|
|
23838
23859
|
params.push(`limit=${options.limit}`);
|
|
23839
|
-
if (options.
|
|
23840
|
-
params.push(`
|
|
23841
|
-
if (options.
|
|
23842
|
-
params.push(`
|
|
23843
|
-
if (options.
|
|
23844
|
-
params.push(`
|
|
23860
|
+
if (options.page)
|
|
23861
|
+
params.push(`page=${options.page}`);
|
|
23862
|
+
if (options.sortBy)
|
|
23863
|
+
params.push(`sortBy=${options.sortBy}`);
|
|
23864
|
+
if (options.sortOrder)
|
|
23865
|
+
params.push(`sortOrder=${options.sortOrder}`);
|
|
23845
23866
|
}
|
|
23846
23867
|
if (params.length > 0) {
|
|
23847
23868
|
url += `?${params.join('&')}`;
|
|
@@ -23950,10 +23971,25 @@ class CampaignApi {
|
|
|
23950
23971
|
// ==========================================
|
|
23951
23972
|
/**
|
|
23952
23973
|
* PUBLIC: Get campaign triggers catalog
|
|
23953
|
-
* NEW: GET /
|
|
23974
|
+
* NEW: GET /trigger-sources
|
|
23954
23975
|
*/
|
|
23955
|
-
async getCampaignTriggers() {
|
|
23956
|
-
|
|
23976
|
+
async getCampaignTriggers(options) {
|
|
23977
|
+
let url = '/trigger-sources';
|
|
23978
|
+
const params = [];
|
|
23979
|
+
if (options) {
|
|
23980
|
+
if (options.limit)
|
|
23981
|
+
params.push(`limit=${options.limit}`);
|
|
23982
|
+
if (options.page)
|
|
23983
|
+
params.push(`page=${options.page}`);
|
|
23984
|
+
if (options.sortBy)
|
|
23985
|
+
params.push(`sortBy=${options.sortBy}`);
|
|
23986
|
+
if (options.sortOrder)
|
|
23987
|
+
params.push(`sortOrder=${options.sortOrder}`);
|
|
23988
|
+
}
|
|
23989
|
+
if (params.length > 0) {
|
|
23990
|
+
url += `?${params.join('&')}`;
|
|
23991
|
+
}
|
|
23992
|
+
return this.apiClient.get(url);
|
|
23957
23993
|
}
|
|
23958
23994
|
/**
|
|
23959
23995
|
* ADMIN: Create campaign trigger
|
|
@@ -23983,26 +24019,12 @@ class CampaignApi {
|
|
|
23983
24019
|
async setCampaignTrigger(campaignId, triggerId) {
|
|
23984
24020
|
return this.apiClient.put(`/campaign-triggers/${triggerId}/assign/${campaignId}`, {});
|
|
23985
24021
|
}
|
|
23986
|
-
/**
|
|
23987
|
-
* ADMIN: Create trigger condition
|
|
23988
|
-
* NEW: POST /campaign-triggers/conditions
|
|
23989
|
-
*/
|
|
23990
|
-
async createTriggerCondition(condition) {
|
|
23991
|
-
return this.apiClient.post('/campaign-triggers/conditions', condition);
|
|
23992
|
-
}
|
|
23993
|
-
/**
|
|
23994
|
-
* ADMIN: Update trigger condition
|
|
23995
|
-
* NEW: PUT /campaign-triggers/conditions/{id}
|
|
23996
|
-
*/
|
|
23997
|
-
async updateTriggerCondition(conditionId, condition) {
|
|
23998
|
-
return this.apiClient.put(`/campaign-triggers/conditions/${conditionId}`, condition);
|
|
23999
|
-
}
|
|
24000
24022
|
/**
|
|
24001
24023
|
* ADMIN: Add/Remove condition to trigger
|
|
24002
24024
|
* NEW: PUT /campaign-triggers/{triggerId}/conditions/{conditionId}
|
|
24003
24025
|
*/
|
|
24004
|
-
async addOrRemoveConditionToTrigger(triggerId,
|
|
24005
|
-
return this.apiClient.put(`/campaign-triggers/${triggerId}/conditions
|
|
24026
|
+
async addOrRemoveConditionToTrigger(triggerId, condition) {
|
|
24027
|
+
return this.apiClient.put(`/campaign-triggers/${triggerId}/conditions`, condition);
|
|
24006
24028
|
}
|
|
24007
24029
|
// ==========================================
|
|
24008
24030
|
// BUSINESS ENGAGEMENTS (/campaign-engagements)
|
|
@@ -24589,26 +24611,11 @@ class TransactionApi {
|
|
|
24589
24611
|
/**
|
|
24590
24612
|
* AUTH: Submit client-side signed transaction
|
|
24591
24613
|
*
|
|
24592
|
-
* NEW ENDPOINT: POST /transactions/
|
|
24614
|
+
* NEW ENDPOINT: POST /transactions/submit
|
|
24593
24615
|
*/
|
|
24594
24616
|
async submitSignedTransaction(signedTxData) {
|
|
24595
24617
|
return this.apiClient.post(`${this.basePath}/submit`, signedTxData);
|
|
24596
24618
|
}
|
|
24597
|
-
/**
|
|
24598
|
-
* AUTH: Burn user tokens
|
|
24599
|
-
*
|
|
24600
|
-
* UPDATED: Uses new user transaction endpoint with burn-specific parameters
|
|
24601
|
-
* Note: This might need backend confirmation on burn functionality implementation
|
|
24602
|
-
*/
|
|
24603
|
-
/* async burnUserTokens(request: UserBurnTokenRequestDTO): Promise<TransactionRequestResponseDTO> {
|
|
24604
|
-
// Map burn request to TransactionRequestDTO format for new endpoint
|
|
24605
|
-
const transactionRequest: TransactionRequestDTO = {
|
|
24606
|
-
...request,
|
|
24607
|
-
// Add any specific burn transaction parameters here
|
|
24608
|
-
} as any;
|
|
24609
|
-
|
|
24610
|
-
return this.apiClient.post<TransactionRequestResponseDTO>(`${this.basePath}`, transactionRequest);
|
|
24611
|
-
} */
|
|
24612
24619
|
// ==========================================
|
|
24613
24620
|
// BUSINESS OPERATIONS
|
|
24614
24621
|
// ==========================================
|
|
@@ -24633,9 +24640,9 @@ class TransactionApi {
|
|
|
24633
24640
|
return this.apiClient.post<TransactionDTO>(`${this.basePath}/system`, request);
|
|
24634
24641
|
} */
|
|
24635
24642
|
/**
|
|
24636
|
-
* AUTH: Prepare client signed transaction
|
|
24643
|
+
* AUTH: Prepare client signed transaction (Create new transaction for signing)
|
|
24637
24644
|
*
|
|
24638
|
-
* UPDATED: /transaction/auth/prepare-signing → /transactions
|
|
24645
|
+
* UPDATED: /transaction/auth/prepare-signing → /transactions (POST)
|
|
24639
24646
|
*/
|
|
24640
24647
|
async prepareClientSignedTransaction(request) {
|
|
24641
24648
|
return this.apiClient.post(`${this.basePath}`, request);
|
|
@@ -24659,45 +24666,7 @@ class TransactionApi {
|
|
|
24659
24666
|
* UPDATED: /transaction/admin → /transactions (same endpoint, better structure)
|
|
24660
24667
|
*/
|
|
24661
24668
|
async getPaginatedTransactions(params) {
|
|
24662
|
-
|
|
24663
|
-
const queryParams = {
|
|
24664
|
-
page: params.page.toString(),
|
|
24665
|
-
limit: params.limit.toString()
|
|
24666
|
-
};
|
|
24667
|
-
// Add sorting parameters if provided
|
|
24668
|
-
if (params.sortBy) {
|
|
24669
|
-
queryParams['sortBy'] = params.sortBy;
|
|
24670
|
-
}
|
|
24671
|
-
if (params.sortOrder) {
|
|
24672
|
-
queryParams['sortOrder'] = params.sortOrder;
|
|
24673
|
-
}
|
|
24674
|
-
// Add user-specific filtering if provided
|
|
24675
|
-
if (params.participantId) {
|
|
24676
|
-
queryParams['participantId'] = params.participantId;
|
|
24677
|
-
}
|
|
24678
|
-
// Add status filtering if provided
|
|
24679
|
-
if (params.status) {
|
|
24680
|
-
queryParams['status'] = params.status;
|
|
24681
|
-
}
|
|
24682
|
-
// Add additional filters if provided
|
|
24683
|
-
if (params.filters) {
|
|
24684
|
-
if (params.filters.startDate) {
|
|
24685
|
-
queryParams['startDate'] = params.filters.startDate;
|
|
24686
|
-
}
|
|
24687
|
-
if (params.filters.endDate) {
|
|
24688
|
-
queryParams['endDate'] = params.filters.endDate;
|
|
24689
|
-
}
|
|
24690
|
-
if (params.filters.type && params.filters.type.length > 0) {
|
|
24691
|
-
queryParams['type'] = params.filters.type.join(',');
|
|
24692
|
-
}
|
|
24693
|
-
if (params.filters.tokenType && params.filters.tokenType.length > 0) {
|
|
24694
|
-
queryParams['tokenType'] = params.filters.tokenType.join(',');
|
|
24695
|
-
}
|
|
24696
|
-
}
|
|
24697
|
-
// Build query string
|
|
24698
|
-
const queryString = Object.entries(queryParams)
|
|
24699
|
-
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
24700
|
-
.join('&');
|
|
24669
|
+
const queryString = this.buildQueryParams(params).toString();
|
|
24701
24670
|
return this.apiClient.get(`${this.basePath}?${queryString}`);
|
|
24702
24671
|
}
|
|
24703
24672
|
/**
|
|
@@ -24750,6 +24719,43 @@ class TransactionApi {
|
|
|
24750
24719
|
async getTransactionAnalytics(analyticsRequest) {
|
|
24751
24720
|
return this.apiClient.post(`${this.basePath}/analytics`, analyticsRequest);
|
|
24752
24721
|
}
|
|
24722
|
+
/**
|
|
24723
|
+
* Helper to convert DTO object to URLSearchParams
|
|
24724
|
+
* Handles nested 'filters' object and arrays correctly.
|
|
24725
|
+
*/
|
|
24726
|
+
buildQueryParams(params) {
|
|
24727
|
+
const query = new URLSearchParams();
|
|
24728
|
+
// 1. Handle Root Pagination Fields
|
|
24729
|
+
if (params.page !== undefined)
|
|
24730
|
+
query.append('page', params.page.toString());
|
|
24731
|
+
if (params.limit !== undefined)
|
|
24732
|
+
query.append('limit', params.limit.toString());
|
|
24733
|
+
if (params.sortBy)
|
|
24734
|
+
query.append('sortBy', params.sortBy);
|
|
24735
|
+
if (params.sortOrder)
|
|
24736
|
+
query.append('sortOrder', params.sortOrder);
|
|
24737
|
+
// 2. Handle Nested Filters
|
|
24738
|
+
if (params.filters) {
|
|
24739
|
+
Object.entries(params.filters).forEach(([key, value]) => {
|
|
24740
|
+
// Skip undefined/null values
|
|
24741
|
+
if (value === undefined || value === null || value === '')
|
|
24742
|
+
return;
|
|
24743
|
+
if (Array.isArray(value)) {
|
|
24744
|
+
// Handle Arrays: NestJS expects 'status=A&status=B'
|
|
24745
|
+
value.forEach((item) => query.append(key, String(item)));
|
|
24746
|
+
}
|
|
24747
|
+
else {
|
|
24748
|
+
// Handle Single Values: 'search=0x123'
|
|
24749
|
+
// NOTE: Backend Controller expects flat query params for filters
|
|
24750
|
+
// e.g. ?search=...&status=... NOT ?filters[search]=...
|
|
24751
|
+
// This mapping flattens 'filters.search' -> 'search' to match @Query('search') in Controller
|
|
24752
|
+
const stringValue = (value instanceof Date) ? value.toISOString() : String(value);
|
|
24753
|
+
query.append(key, stringValue);
|
|
24754
|
+
}
|
|
24755
|
+
});
|
|
24756
|
+
}
|
|
24757
|
+
return query;
|
|
24758
|
+
}
|
|
24753
24759
|
}
|
|
24754
24760
|
|
|
24755
24761
|
/**
|
|
@@ -24792,17 +24798,17 @@ class TransactionService {
|
|
|
24792
24798
|
return this.transactionApi.getUserTransactionHistory(role, limit);
|
|
24793
24799
|
}
|
|
24794
24800
|
/**
|
|
24795
|
-
* AUTH: Prepare client
|
|
24801
|
+
* AUTH: Prepare existing transaction for client-side signing
|
|
24796
24802
|
*/
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
}
|
|
24803
|
+
async prepareExistingTransaction(transactionId) {
|
|
24804
|
+
return this.transactionApi.prepareExistingTransaction(transactionId);
|
|
24805
|
+
}
|
|
24800
24806
|
/**
|
|
24801
|
-
* AUTH:
|
|
24807
|
+
* AUTH: Prepare client signed transaction
|
|
24802
24808
|
*/
|
|
24803
|
-
|
|
24804
|
-
|
|
24805
|
-
}
|
|
24809
|
+
async prepareClientSignedTransaction(request) {
|
|
24810
|
+
return this.transactionApi.prepareClientSignedTransaction(request);
|
|
24811
|
+
}
|
|
24806
24812
|
// ==========================================
|
|
24807
24813
|
// BUSINESS OPERATIONS
|
|
24808
24814
|
// ==========================================
|
|
@@ -24839,6 +24845,27 @@ class TransactionService {
|
|
|
24839
24845
|
async exportTransactionsCSV() {
|
|
24840
24846
|
return this.transactionApi.exportTransactionsCSV();
|
|
24841
24847
|
}
|
|
24848
|
+
// ==========================================
|
|
24849
|
+
// QUERY & ANALYTICS OPERATIONS
|
|
24850
|
+
// ==========================================
|
|
24851
|
+
/**
|
|
24852
|
+
* Query transactions by sender
|
|
24853
|
+
*/
|
|
24854
|
+
async queryTransactionsBySender(accountSelector) {
|
|
24855
|
+
return this.transactionApi.queryTransactionsBySender(accountSelector);
|
|
24856
|
+
}
|
|
24857
|
+
/**
|
|
24858
|
+
* Query transactions by recipient
|
|
24859
|
+
*/
|
|
24860
|
+
async queryTransactionsByRecipient(accountSelector) {
|
|
24861
|
+
return this.transactionApi.queryTransactionsByRecipient(accountSelector);
|
|
24862
|
+
}
|
|
24863
|
+
/**
|
|
24864
|
+
* ADMIN: Get transaction analytics
|
|
24865
|
+
*/
|
|
24866
|
+
async getTransactionAnalytics(analyticsRequest) {
|
|
24867
|
+
return this.transactionApi.getTransactionAnalytics(analyticsRequest);
|
|
24868
|
+
}
|
|
24842
24869
|
}
|
|
24843
24870
|
|
|
24844
24871
|
/**
|
|
@@ -26172,14 +26199,19 @@ const AUTH_STORAGE_KEYS = {
|
|
|
26172
26199
|
};
|
|
26173
26200
|
/**
|
|
26174
26201
|
* LocalStorage implementation
|
|
26202
|
+
* Note: Forces string conversion for compatibility
|
|
26175
26203
|
*/
|
|
26176
26204
|
class LocalStorageTokenStorage {
|
|
26205
|
+
constructor() {
|
|
26206
|
+
this.supportsObjects = false;
|
|
26207
|
+
}
|
|
26177
26208
|
async get(key) {
|
|
26178
26209
|
return typeof localStorage !== 'undefined' ? localStorage.getItem(key) : null;
|
|
26179
26210
|
}
|
|
26180
26211
|
async set(key, value) {
|
|
26181
26212
|
if (typeof localStorage !== 'undefined') {
|
|
26182
|
-
|
|
26213
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
26214
|
+
localStorage.setItem(key, stringValue);
|
|
26183
26215
|
}
|
|
26184
26216
|
}
|
|
26185
26217
|
async remove(key) {
|
|
@@ -26200,6 +26232,7 @@ class LocalStorageTokenStorage {
|
|
|
26200
26232
|
*/
|
|
26201
26233
|
class MemoryTokenStorage {
|
|
26202
26234
|
constructor() {
|
|
26235
|
+
this.supportsObjects = true;
|
|
26203
26236
|
this.store = new Map();
|
|
26204
26237
|
}
|
|
26205
26238
|
async get(key) {
|
|
@@ -26223,26 +26256,30 @@ class AuthTokenManager {
|
|
|
26223
26256
|
this.storage = storage;
|
|
26224
26257
|
}
|
|
26225
26258
|
async getAccessToken() {
|
|
26226
|
-
|
|
26259
|
+
const val = await this.storage.get(AUTH_STORAGE_KEYS.ACCESS_TOKEN);
|
|
26260
|
+
// Ensure we return string for tokens
|
|
26261
|
+
return typeof val === 'string' ? val : null;
|
|
26227
26262
|
}
|
|
26228
26263
|
async setAccessToken(token) {
|
|
26229
26264
|
await this.storage.set(AUTH_STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
26230
26265
|
}
|
|
26231
26266
|
async getRefreshToken() {
|
|
26232
|
-
|
|
26267
|
+
const val = await this.storage.get(AUTH_STORAGE_KEYS.REFRESH_TOKEN);
|
|
26268
|
+
return typeof val === 'string' ? val : null;
|
|
26233
26269
|
}
|
|
26234
26270
|
async setRefreshToken(token) {
|
|
26235
26271
|
await this.storage.set(AUTH_STORAGE_KEYS.REFRESH_TOKEN, token);
|
|
26236
26272
|
}
|
|
26237
26273
|
async getProviderToken() {
|
|
26238
|
-
|
|
26274
|
+
const val = await this.storage.get(AUTH_STORAGE_KEYS.PROVIDER_TOKEN);
|
|
26275
|
+
return typeof val === 'string' ? val : null;
|
|
26239
26276
|
}
|
|
26240
26277
|
async setProviderToken(token) {
|
|
26241
26278
|
await this.storage.set(AUTH_STORAGE_KEYS.PROVIDER_TOKEN, token);
|
|
26242
26279
|
}
|
|
26243
26280
|
async getAuthType() {
|
|
26244
26281
|
const authType = await this.storage.get(AUTH_STORAGE_KEYS.AUTH_TYPE);
|
|
26245
|
-
return authType;
|
|
26282
|
+
return typeof authType === 'string' ? authType : null;
|
|
26246
26283
|
}
|
|
26247
26284
|
async setAuthType(authType) {
|
|
26248
26285
|
await this.storage.set(AUTH_STORAGE_KEYS.AUTH_TYPE, authType);
|
|
@@ -26267,6 +26304,218 @@ class AuthTokenManager {
|
|
|
26267
26304
|
}
|
|
26268
26305
|
}
|
|
26269
26306
|
|
|
26307
|
+
class WebDPoPCryptoProvider {
|
|
26308
|
+
get crypto() {
|
|
26309
|
+
// Basic environment check
|
|
26310
|
+
if (typeof crypto !== 'undefined')
|
|
26311
|
+
return crypto;
|
|
26312
|
+
if (typeof window !== 'undefined' && window.crypto)
|
|
26313
|
+
return window.crypto;
|
|
26314
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto)
|
|
26315
|
+
return globalThis.crypto;
|
|
26316
|
+
throw new Error('WebCrypto API not found. Please polyfill crypto.subtle or use a different DPoPCryptoProvider.');
|
|
26317
|
+
}
|
|
26318
|
+
async generateKeyPair(options) {
|
|
26319
|
+
const extractable = options?.extractable ?? true;
|
|
26320
|
+
const keyPair = await this.crypto.subtle.generateKey({
|
|
26321
|
+
name: 'ECDSA',
|
|
26322
|
+
namedCurve: 'P-256',
|
|
26323
|
+
}, extractable, // Configurable extractability
|
|
26324
|
+
['sign', 'verify']);
|
|
26325
|
+
const publicKey = await this.crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
|
26326
|
+
// If extractable, export private key as JWK (plain object)
|
|
26327
|
+
// If NOT extractable, keep it as CryptoKey (native handle)
|
|
26328
|
+
// However, DPoPKeyPair interface currently expects JsonWebKey | CryptoKey
|
|
26329
|
+
// We need to check DPoPKeyPair type definition.
|
|
26330
|
+
// Wait, DPoPKeyPair definition in dpop.types.ts says:
|
|
26331
|
+
// publicKey: JsonWebKey; privateKey: JsonWebKey;
|
|
26332
|
+
// I need to change that to support CryptoKey.
|
|
26333
|
+
// Let's re-read dpop.types.ts
|
|
26334
|
+
// I assumed it might be compatible but let's verify.
|
|
26335
|
+
// If I change privateKey to unknown or JsonWebKey | CryptoKey, it will be safer.
|
|
26336
|
+
let privateKey;
|
|
26337
|
+
if (extractable) {
|
|
26338
|
+
privateKey = await this.crypto.subtle.exportKey('jwk', keyPair.privateKey);
|
|
26339
|
+
}
|
|
26340
|
+
else {
|
|
26341
|
+
privateKey = keyPair.privateKey;
|
|
26342
|
+
}
|
|
26343
|
+
return { publicKey, privateKey };
|
|
26344
|
+
}
|
|
26345
|
+
async signProof(payload, keyPair) {
|
|
26346
|
+
// 1. Prepare Header
|
|
26347
|
+
const header = {
|
|
26348
|
+
typ: 'dpop+jwt',
|
|
26349
|
+
alg: 'ES256',
|
|
26350
|
+
jwk: keyPair.publicKey
|
|
26351
|
+
};
|
|
26352
|
+
// 2. Prepare Payload
|
|
26353
|
+
// Ensure jti and iat if not present (though Manager usually provides them)
|
|
26354
|
+
const finalPayload = {
|
|
26355
|
+
...payload,
|
|
26356
|
+
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
26357
|
+
jti: payload.jti || this.generateUUID()
|
|
26358
|
+
};
|
|
26359
|
+
// 3. Encode segments
|
|
26360
|
+
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
26361
|
+
const encodedPayload = this.base64UrlEncode(JSON.stringify(finalPayload));
|
|
26362
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
26363
|
+
// 4. Import private key for signing
|
|
26364
|
+
let privateKey;
|
|
26365
|
+
// Check if it's already a CryptoKey (non-extractable handle)
|
|
26366
|
+
// We check for 'algorithm' property which signals a CryptoKey object
|
|
26367
|
+
if (keyPair.privateKey.algorithm) {
|
|
26368
|
+
privateKey = keyPair.privateKey;
|
|
26369
|
+
}
|
|
26370
|
+
else {
|
|
26371
|
+
// Otherwise import from JWK
|
|
26372
|
+
privateKey = await this.crypto.subtle.importKey('jwk', keyPair.privateKey, {
|
|
26373
|
+
name: 'ECDSA',
|
|
26374
|
+
namedCurve: 'P-256',
|
|
26375
|
+
}, false, ['sign']);
|
|
26376
|
+
}
|
|
26377
|
+
// 5. Sign
|
|
26378
|
+
const signatureBuffer = await this.crypto.subtle.sign({
|
|
26379
|
+
name: 'ECDSA',
|
|
26380
|
+
hash: { name: 'SHA-256' },
|
|
26381
|
+
}, privateKey, new TextEncoder().encode(signingInput));
|
|
26382
|
+
const encodedSignature = this.base64UrlEncodeBuffer(signatureBuffer);
|
|
26383
|
+
return `${signingInput}.${encodedSignature}`;
|
|
26384
|
+
}
|
|
26385
|
+
async hashAccessToken(accessToken) {
|
|
26386
|
+
const encoder = new TextEncoder();
|
|
26387
|
+
const data = encoder.encode(accessToken);
|
|
26388
|
+
const hashBuffer = await this.crypto.subtle.digest('SHA-256', data);
|
|
26389
|
+
return this.base64UrlEncodeBuffer(hashBuffer);
|
|
26390
|
+
}
|
|
26391
|
+
// --- Helpers ---
|
|
26392
|
+
generateUUID() {
|
|
26393
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
26394
|
+
return crypto.randomUUID();
|
|
26395
|
+
}
|
|
26396
|
+
// Fallback for older envs
|
|
26397
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
26398
|
+
const r = (Math.random() * 16) | 0;
|
|
26399
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
26400
|
+
return v.toString(16);
|
|
26401
|
+
});
|
|
26402
|
+
}
|
|
26403
|
+
base64UrlEncode(str) {
|
|
26404
|
+
// Use TextEncoder to handle UTF-8 properly
|
|
26405
|
+
const encoder = new TextEncoder();
|
|
26406
|
+
return this.base64UrlEncodeBuffer(encoder.encode(str));
|
|
26407
|
+
}
|
|
26408
|
+
base64UrlEncodeBuffer(buffer) {
|
|
26409
|
+
const bytes = new Uint8Array(buffer);
|
|
26410
|
+
let binary = '';
|
|
26411
|
+
const len = bytes.byteLength;
|
|
26412
|
+
// Chunk processing for large buffers if needed, but proofs are small
|
|
26413
|
+
for (let i = 0; i < len; i++) {
|
|
26414
|
+
binary += String.fromCharCode(bytes[i]);
|
|
26415
|
+
}
|
|
26416
|
+
// btoa is widely available in browsers.
|
|
26417
|
+
// In Node (non-globals), we might need Buffer.
|
|
26418
|
+
// For universal 'platform agnostic' aiming at standard web-like envs:
|
|
26419
|
+
let base64 = '';
|
|
26420
|
+
if (typeof btoa === 'function') {
|
|
26421
|
+
base64 = btoa(binary);
|
|
26422
|
+
}
|
|
26423
|
+
else if (typeof Buffer !== 'undefined') {
|
|
26424
|
+
base64 = Buffer.from(binary, 'binary').toString('base64');
|
|
26425
|
+
}
|
|
26426
|
+
else {
|
|
26427
|
+
throw new Error('Base64 encoding not supported in this environment');
|
|
26428
|
+
}
|
|
26429
|
+
return base64
|
|
26430
|
+
.replace(/\+/g, '-')
|
|
26431
|
+
.replace(/\//g, '_')
|
|
26432
|
+
.replace(/=+$/, '');
|
|
26433
|
+
}
|
|
26434
|
+
}
|
|
26435
|
+
|
|
26436
|
+
const DPOP_STORAGE_KEYS = {
|
|
26437
|
+
PUBLIC: 'pers_dpop_public_key',
|
|
26438
|
+
PRIVATE: 'pers_dpop_private_key'
|
|
26439
|
+
};
|
|
26440
|
+
class DPoPManager {
|
|
26441
|
+
constructor(storage, cryptoProvider) {
|
|
26442
|
+
this.storage = storage;
|
|
26443
|
+
this.memoryKeyPair = null;
|
|
26444
|
+
this.cryptoProvider = cryptoProvider || new WebDPoPCryptoProvider();
|
|
26445
|
+
}
|
|
26446
|
+
/**
|
|
26447
|
+
* Ensures a DPoP key pair exists, loading from storage or generating new one.
|
|
26448
|
+
*/
|
|
26449
|
+
async ensureKeyPair() {
|
|
26450
|
+
if (this.memoryKeyPair)
|
|
26451
|
+
return this.memoryKeyPair;
|
|
26452
|
+
const storedPublic = await this.storage.get(DPOP_STORAGE_KEYS.PUBLIC);
|
|
26453
|
+
const storedPrivate = await this.storage.get(DPOP_STORAGE_KEYS.PRIVATE);
|
|
26454
|
+
if (storedPublic && storedPrivate) {
|
|
26455
|
+
try {
|
|
26456
|
+
// Handle polymorphic storage: String (JSON) or Object (CryptoKey)
|
|
26457
|
+
const publicKey = typeof storedPublic === 'string' ? JSON.parse(storedPublic) : storedPublic;
|
|
26458
|
+
const privateKey = typeof storedPrivate === 'string' ? JSON.parse(storedPrivate) : storedPrivate;
|
|
26459
|
+
this.memoryKeyPair = { publicKey, privateKey };
|
|
26460
|
+
return this.memoryKeyPair;
|
|
26461
|
+
}
|
|
26462
|
+
catch (e) {
|
|
26463
|
+
console.warn('Corrupted DPoP keys in storage, regenerating.');
|
|
26464
|
+
}
|
|
26465
|
+
}
|
|
26466
|
+
// Generate new key pair
|
|
26467
|
+
// Adaptation: If storage supports raw objects (like IndexedDB or Native Keychain),
|
|
26468
|
+
// we can generate Non-Extractable keys for maximum security.
|
|
26469
|
+
// If storage is text-only (LocalStorage), we must use Extractable keys to serialize them.
|
|
26470
|
+
const useHighSecurity = !!this.storage.supportsObjects;
|
|
26471
|
+
const keyPair = await this.cryptoProvider.generateKeyPair({
|
|
26472
|
+
extractable: !useHighSecurity
|
|
26473
|
+
});
|
|
26474
|
+
// Save to storage
|
|
26475
|
+
await this.storage.set(DPOP_STORAGE_KEYS.PUBLIC, keyPair.publicKey);
|
|
26476
|
+
await this.storage.set(DPOP_STORAGE_KEYS.PRIVATE, keyPair.privateKey);
|
|
26477
|
+
this.memoryKeyPair = keyPair;
|
|
26478
|
+
return keyPair;
|
|
26479
|
+
}
|
|
26480
|
+
/**
|
|
26481
|
+
* Generates the DPoP proof header value.
|
|
26482
|
+
* @param method HTTP method
|
|
26483
|
+
* @param url Request URL
|
|
26484
|
+
* @param accessToken Optional access token to bind the proof to (ath claim)
|
|
26485
|
+
*/
|
|
26486
|
+
async generateProofHeader(method, url, accessToken) {
|
|
26487
|
+
const keyPair = await this.ensureKeyPair();
|
|
26488
|
+
const payload = {
|
|
26489
|
+
htm: method,
|
|
26490
|
+
htu: url,
|
|
26491
|
+
};
|
|
26492
|
+
if (accessToken) {
|
|
26493
|
+
payload.ath = await this.cryptoProvider.hashAccessToken(accessToken);
|
|
26494
|
+
}
|
|
26495
|
+
return this.cryptoProvider.signProof(payload, keyPair);
|
|
26496
|
+
}
|
|
26497
|
+
/**
|
|
26498
|
+
* Clears DPoP keys (e.g. on logout)
|
|
26499
|
+
*/
|
|
26500
|
+
async clearKeys() {
|
|
26501
|
+
this.memoryKeyPair = null;
|
|
26502
|
+
await this.storage.remove(DPOP_STORAGE_KEYS.PUBLIC);
|
|
26503
|
+
await this.storage.remove(DPOP_STORAGE_KEYS.PRIVATE);
|
|
26504
|
+
}
|
|
26505
|
+
/**
|
|
26506
|
+
* Gets the public key (for debugging/inspection)
|
|
26507
|
+
*/
|
|
26508
|
+
async getPublicKey() {
|
|
26509
|
+
try {
|
|
26510
|
+
const kp = await this.ensureKeyPair();
|
|
26511
|
+
return kp.publicKey;
|
|
26512
|
+
}
|
|
26513
|
+
catch {
|
|
26514
|
+
return null;
|
|
26515
|
+
}
|
|
26516
|
+
}
|
|
26517
|
+
}
|
|
26518
|
+
|
|
26270
26519
|
/**
|
|
26271
26520
|
* Default Authentication Provider
|
|
26272
26521
|
* Simple implementation of PERS authentication with token lifecycle management
|
|
@@ -26280,6 +26529,9 @@ class DefaultAuthProvider {
|
|
|
26280
26529
|
this.authType = config.authType || 'user';
|
|
26281
26530
|
const storage = config.storage || this.createStorage();
|
|
26282
26531
|
this.tokenManager = new AuthTokenManager(storage);
|
|
26532
|
+
if (config.dpop?.enabled) {
|
|
26533
|
+
this.dpopManager = new DPoPManager(storage, config.dpop.cryptoProvider);
|
|
26534
|
+
}
|
|
26283
26535
|
}
|
|
26284
26536
|
createStorage() {
|
|
26285
26537
|
return typeof window === 'undefined' || typeof localStorage === 'undefined'
|
|
@@ -26289,6 +26541,26 @@ class DefaultAuthProvider {
|
|
|
26289
26541
|
getToken() {
|
|
26290
26542
|
return this.tokenManager.getAccessToken();
|
|
26291
26543
|
}
|
|
26544
|
+
async getAuthHeaders(token, method, url) {
|
|
26545
|
+
const headers = {};
|
|
26546
|
+
if (this.dpopManager && method && url) {
|
|
26547
|
+
// Generate DPoP Proof
|
|
26548
|
+
// Note: For 'bypassAuth' requests (token is null), we still generate proof (bound to public key, no ath)
|
|
26549
|
+
// This covers Token Requests (Login) which need DPoP header to bind the issued token.
|
|
26550
|
+
const proof = await this.dpopManager.generateProofHeader(method, url, token || undefined);
|
|
26551
|
+
headers['DPoP'] = proof;
|
|
26552
|
+
if (token) {
|
|
26553
|
+
headers['Authorization'] = `DPoP ${token}`;
|
|
26554
|
+
}
|
|
26555
|
+
}
|
|
26556
|
+
else {
|
|
26557
|
+
// Standard Bearer
|
|
26558
|
+
if (token) {
|
|
26559
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
26560
|
+
}
|
|
26561
|
+
}
|
|
26562
|
+
return headers;
|
|
26563
|
+
}
|
|
26292
26564
|
async getProjectKey() {
|
|
26293
26565
|
return this.config.projectKey || null;
|
|
26294
26566
|
}
|
|
@@ -26323,6 +26595,9 @@ class DefaultAuthProvider {
|
|
|
26323
26595
|
}
|
|
26324
26596
|
async clearTokens() {
|
|
26325
26597
|
await this.tokenManager.clearAllTokens();
|
|
26598
|
+
if (this.dpopManager) {
|
|
26599
|
+
await this.dpopManager.clearKeys();
|
|
26600
|
+
}
|
|
26326
26601
|
}
|
|
26327
26602
|
async setTokens(accessToken, refreshToken, providerToken) {
|
|
26328
26603
|
await this.tokenManager.setTokens(accessToken, refreshToken, providerToken);
|
|
@@ -26371,7 +26646,11 @@ class PersApiClient {
|
|
|
26371
26646
|
this.mergedConfig.authProvider = new DefaultAuthProvider({
|
|
26372
26647
|
authType: this.mergedConfig.authType || 'user',
|
|
26373
26648
|
projectKey: this.mergedConfig.apiProjectKey,
|
|
26374
|
-
storage: this.mergedConfig.authStorage // Support custom storage
|
|
26649
|
+
storage: this.mergedConfig.authStorage, // Support custom storage
|
|
26650
|
+
dpop: {
|
|
26651
|
+
enabled: this.mergedConfig.dpop?.enabled ?? true, // Enable DPoP by default
|
|
26652
|
+
cryptoProvider: this.mergedConfig.dpop?.cryptoProvider
|
|
26653
|
+
}
|
|
26375
26654
|
});
|
|
26376
26655
|
}
|
|
26377
26656
|
this.authService = new AuthService(this.authApi, this.mergedConfig.authProvider);
|
|
@@ -26434,7 +26713,7 @@ class PersApiClient {
|
|
|
26434
26713
|
});
|
|
26435
26714
|
}
|
|
26436
26715
|
const requestOptions = {
|
|
26437
|
-
headers: await this.getHeaders(!bypassAuth),
|
|
26716
|
+
headers: await this.getHeaders(!bypassAuth, method, url),
|
|
26438
26717
|
timeout: this.mergedConfig.timeout,
|
|
26439
26718
|
responseType
|
|
26440
26719
|
};
|
|
@@ -26529,26 +26808,45 @@ class PersApiClient {
|
|
|
26529
26808
|
* Get request headers with optional auth token and project key
|
|
26530
26809
|
*
|
|
26531
26810
|
* @param includeAuth - Whether to include the Authorization header (default: true)
|
|
26811
|
+
* @param method - HTTP Method (required for DPoP)
|
|
26812
|
+
* @param url - Full Request URL (required for DPoP)
|
|
26532
26813
|
*/
|
|
26533
|
-
async getHeaders(includeAuth = true) {
|
|
26814
|
+
async getHeaders(includeAuth = true, method, url) {
|
|
26534
26815
|
const headers = {
|
|
26535
26816
|
'Content-Type': 'application/json',
|
|
26536
26817
|
};
|
|
26537
|
-
if (
|
|
26538
|
-
|
|
26539
|
-
if (
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26818
|
+
if (this.mergedConfig.authProvider) {
|
|
26819
|
+
let token = null;
|
|
26820
|
+
if (includeAuth) {
|
|
26821
|
+
token = await this.mergedConfig.authProvider.getToken();
|
|
26822
|
+
}
|
|
26823
|
+
// Handle Authentication Headers (Bearer or DPoP)
|
|
26824
|
+
if (this.mergedConfig.authProvider.getAuthHeaders) {
|
|
26825
|
+
// Provider supports advanced headers (DPoP)
|
|
26826
|
+
// We pass token (null if includeAuth=false) so it can generate DPoP proofs for login requests too
|
|
26827
|
+
try {
|
|
26828
|
+
const authHeaders = await this.mergedConfig.authProvider.getAuthHeaders(token, method, url);
|
|
26829
|
+
Object.assign(headers, authHeaders);
|
|
26543
26830
|
}
|
|
26544
|
-
|
|
26545
|
-
console.warn('[PersApiClient]
|
|
26831
|
+
catch (e) {
|
|
26832
|
+
console.warn('[PersApiClient] Failed to generate auth headers:', e);
|
|
26833
|
+
// Fallback if token exists but DPoP generation failed?
|
|
26834
|
+
// Better to fail or let standardized fallback below handle it?
|
|
26835
|
+
// If DPoP fails, falling back to Bearer might be insecure if server enforces DPoP.
|
|
26836
|
+
// For now, valid token -> Bearer fallback logic mostly for non-DPoP legacy providers.
|
|
26546
26837
|
}
|
|
26547
26838
|
}
|
|
26548
|
-
|
|
26549
|
-
|
|
26839
|
+
// Fallback: If provider didn't give headers but we have token and NO Authorization header yet
|
|
26840
|
+
if (includeAuth && token && !headers['Authorization']) {
|
|
26841
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
26842
|
+
}
|
|
26843
|
+
else if (includeAuth && !token) {
|
|
26844
|
+
console.warn('[PersApiClient] No token available from auth provider');
|
|
26550
26845
|
}
|
|
26551
26846
|
}
|
|
26847
|
+
else if (includeAuth) {
|
|
26848
|
+
console.warn('[PersApiClient] No auth provider configured');
|
|
26849
|
+
}
|
|
26552
26850
|
// Add project key
|
|
26553
26851
|
if (this.mergedConfig.authProvider) {
|
|
26554
26852
|
const projectKey = await this.mergedConfig.authProvider.getProjectKey();
|
|
@@ -29567,6 +29865,75 @@ class TransactionManager {
|
|
|
29567
29865
|
async exportTransactionsCSV() {
|
|
29568
29866
|
return this.transactionService.exportTransactionsCSV();
|
|
29569
29867
|
}
|
|
29868
|
+
/**
|
|
29869
|
+
* Prepare existing transaction for client-side signing
|
|
29870
|
+
*
|
|
29871
|
+
* Retrieves the necessary data to sign an existing transaction on the client side.
|
|
29872
|
+
* This is typically used when a transaction has been created but requires user signature.
|
|
29873
|
+
*
|
|
29874
|
+
* @param transactionId - The ID of the transaction to prepare
|
|
29875
|
+
* @returns Promise resolving to the transaction preparation data
|
|
29876
|
+
*/
|
|
29877
|
+
async prepareExistingTransaction(transactionId) {
|
|
29878
|
+
return this.transactionService.prepareExistingTransaction(transactionId);
|
|
29879
|
+
}
|
|
29880
|
+
/**
|
|
29881
|
+
* Prepare a new transaction for client-side signing
|
|
29882
|
+
*
|
|
29883
|
+
* Creates a new transaction and immediately returns the data needed for client-side signing.
|
|
29884
|
+
* This combines creation and preparation into a single flow for client-signed operations.
|
|
29885
|
+
*
|
|
29886
|
+
* @param request - The transaction request details
|
|
29887
|
+
* @returns Promise resolving to the transaction preparation data
|
|
29888
|
+
*/
|
|
29889
|
+
async prepareClientSignedTransaction(request) {
|
|
29890
|
+
return this.transactionService.prepareClientSignedTransaction(request);
|
|
29891
|
+
}
|
|
29892
|
+
/**
|
|
29893
|
+
* Submit a signed transaction
|
|
29894
|
+
*
|
|
29895
|
+
* Submits a transaction that has been signed on the client side to the backend for processing.
|
|
29896
|
+
*
|
|
29897
|
+
* @param signedTxData - The signed transaction data
|
|
29898
|
+
* @returns Promise resolving to the submission result
|
|
29899
|
+
*/
|
|
29900
|
+
async submitSignedTransaction(signedTxData) {
|
|
29901
|
+
return this.transactionService.submitSignedTransaction(signedTxData);
|
|
29902
|
+
}
|
|
29903
|
+
/**
|
|
29904
|
+
* Query transactions by sender
|
|
29905
|
+
*
|
|
29906
|
+
* Retrieves transactions where the specified account is the sender.
|
|
29907
|
+
*
|
|
29908
|
+
* @param accountSelector - Selector for the sender account
|
|
29909
|
+
* @returns Promise resolving to paginated transactions
|
|
29910
|
+
*/
|
|
29911
|
+
async queryTransactionsBySender(accountSelector) {
|
|
29912
|
+
return this.transactionService.queryTransactionsBySender(accountSelector);
|
|
29913
|
+
}
|
|
29914
|
+
/**
|
|
29915
|
+
* Query transactions by recipient
|
|
29916
|
+
*
|
|
29917
|
+
* Retrieves transactions where the specified account is the recipient.
|
|
29918
|
+
*
|
|
29919
|
+
* @param accountSelector - Selector for the recipient account
|
|
29920
|
+
* @returns Promise resolving to paginated transactions
|
|
29921
|
+
*/
|
|
29922
|
+
async queryTransactionsByRecipient(accountSelector) {
|
|
29923
|
+
return this.transactionService.queryTransactionsByRecipient(accountSelector);
|
|
29924
|
+
}
|
|
29925
|
+
/**
|
|
29926
|
+
* Get transaction analytics
|
|
29927
|
+
*
|
|
29928
|
+
* Retrieves analytics data for transactions based on the provided request criteria.
|
|
29929
|
+
* Useful for generating reports and dashboards.
|
|
29930
|
+
*
|
|
29931
|
+
* @param analyticsRequest - The analytics query parameters
|
|
29932
|
+
* @returns Promise resolving to the analytics data
|
|
29933
|
+
*/
|
|
29934
|
+
async getTransactionAnalytics(analyticsRequest) {
|
|
29935
|
+
return this.transactionService.getTransactionAnalytics(analyticsRequest);
|
|
29936
|
+
}
|
|
29570
29937
|
/**
|
|
29571
29938
|
* Get the full transaction service for advanced operations
|
|
29572
29939
|
*
|
|
@@ -31677,6 +32044,153 @@ class PersSDK {
|
|
|
31677
32044
|
}
|
|
31678
32045
|
}
|
|
31679
32046
|
|
|
32047
|
+
// Conditionally require Keychain to avoid Web bundler errors
|
|
32048
|
+
let Keychain;
|
|
32049
|
+
if (reactNative.Platform.OS !== 'web') {
|
|
32050
|
+
try {
|
|
32051
|
+
Keychain = require('react-native-keychain');
|
|
32052
|
+
}
|
|
32053
|
+
catch (e) {
|
|
32054
|
+
console.warn('ReactNativeSecureStorage: Failed to load react-native-keychain', e);
|
|
32055
|
+
}
|
|
32056
|
+
}
|
|
32057
|
+
/**
|
|
32058
|
+
* Secure Storage implementation for React Native
|
|
32059
|
+
* Uses Keychain/Keystore for sensitive data and AsyncStorage for non-sensitive data.
|
|
32060
|
+
*/
|
|
32061
|
+
class ReactNativeSecureStorage {
|
|
32062
|
+
constructor(keyPrefix = 'pers_tokens_') {
|
|
32063
|
+
this.keyPrefix = keyPrefix;
|
|
32064
|
+
// Set to false because Keychain stores strings, so we need extractable (serializable) keys
|
|
32065
|
+
this.supportsObjects = false;
|
|
32066
|
+
// Define which keys MUST go to secure hardware storage
|
|
32067
|
+
this.SECURE_KEYS = new Set([
|
|
32068
|
+
DPOP_STORAGE_KEYS.PRIVATE,
|
|
32069
|
+
AUTH_STORAGE_KEYS.ACCESS_TOKEN,
|
|
32070
|
+
AUTH_STORAGE_KEYS.REFRESH_TOKEN,
|
|
32071
|
+
AUTH_STORAGE_KEYS.PROVIDER_TOKEN
|
|
32072
|
+
]);
|
|
32073
|
+
}
|
|
32074
|
+
async set(key, value) {
|
|
32075
|
+
const prefixedKey = this.getKeyName(key);
|
|
32076
|
+
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
32077
|
+
// Cast key to any to bypass strict literal type checking of the Set.has method
|
|
32078
|
+
// In runtime, 'key' is just a string, so this is safe.
|
|
32079
|
+
if (this.SECURE_KEYS.has(key)) {
|
|
32080
|
+
// Store in Keychain/Keystore
|
|
32081
|
+
// Service name acts as the key identifier in simple usage
|
|
32082
|
+
await Keychain.setGenericPassword(prefixedKey, stringValue, { service: prefixedKey });
|
|
32083
|
+
}
|
|
32084
|
+
else {
|
|
32085
|
+
// Store standard config/metadata in AsyncStorage
|
|
32086
|
+
await AsyncStorage.setItem(prefixedKey, stringValue);
|
|
32087
|
+
}
|
|
32088
|
+
}
|
|
32089
|
+
async get(key) {
|
|
32090
|
+
const prefixedKey = this.getKeyName(key);
|
|
32091
|
+
if (this.SECURE_KEYS.has(key)) {
|
|
32092
|
+
try {
|
|
32093
|
+
const credentials = await Keychain.getGenericPassword({ service: prefixedKey });
|
|
32094
|
+
if (credentials) {
|
|
32095
|
+
return this.tryParse(credentials.password);
|
|
32096
|
+
}
|
|
32097
|
+
return null;
|
|
32098
|
+
}
|
|
32099
|
+
catch (e) {
|
|
32100
|
+
console.warn(`[ReactNativeSecureStorage] Failed to access keychain for ${key}`, e);
|
|
32101
|
+
return null; // Key not found or access denied
|
|
32102
|
+
}
|
|
32103
|
+
}
|
|
32104
|
+
else {
|
|
32105
|
+
try {
|
|
32106
|
+
const val = await AsyncStorage.getItem(prefixedKey);
|
|
32107
|
+
return val ? this.tryParse(val) : null;
|
|
32108
|
+
}
|
|
32109
|
+
catch (e) {
|
|
32110
|
+
console.warn(`[ReactNativeSecureStorage] Failed to access AsyncStorage for ${key}`, e);
|
|
32111
|
+
return null;
|
|
32112
|
+
}
|
|
32113
|
+
}
|
|
32114
|
+
}
|
|
32115
|
+
async remove(key) {
|
|
32116
|
+
const prefixedKey = this.getKeyName(key);
|
|
32117
|
+
if (this.SECURE_KEYS.has(key)) {
|
|
32118
|
+
await Keychain.resetGenericPassword({ service: prefixedKey });
|
|
32119
|
+
}
|
|
32120
|
+
else {
|
|
32121
|
+
await AsyncStorage.removeItem(prefixedKey);
|
|
32122
|
+
}
|
|
32123
|
+
}
|
|
32124
|
+
async clear() {
|
|
32125
|
+
// Clear all known secure keys
|
|
32126
|
+
for (const key of this.SECURE_KEYS) {
|
|
32127
|
+
await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
|
|
32128
|
+
}
|
|
32129
|
+
// Clear AsyncStorage keys related to PERS
|
|
32130
|
+
try {
|
|
32131
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
32132
|
+
const ourKeys = allKeys.filter(k => k.startsWith(this.keyPrefix));
|
|
32133
|
+
if (ourKeys.length > 0) {
|
|
32134
|
+
await AsyncStorage.multiRemove(ourKeys);
|
|
32135
|
+
}
|
|
32136
|
+
}
|
|
32137
|
+
catch (e) {
|
|
32138
|
+
console.warn('[ReactNativeSecureStorage] Failed to clear AsyncStorage', e);
|
|
32139
|
+
}
|
|
32140
|
+
}
|
|
32141
|
+
getKeyName(key) {
|
|
32142
|
+
// For Keychain, we might want to avoid prefixes if we want to share across apps,
|
|
32143
|
+
// but for simple isolation, prefix is fine to avoid collisions if multiple instances exist.
|
|
32144
|
+
// However, Keychain services are global to the app bundle ID usually.
|
|
32145
|
+
return `${this.keyPrefix}${key}`;
|
|
32146
|
+
}
|
|
32147
|
+
tryParse(val) {
|
|
32148
|
+
try {
|
|
32149
|
+
return JSON.parse(val);
|
|
32150
|
+
}
|
|
32151
|
+
catch {
|
|
32152
|
+
return val;
|
|
32153
|
+
}
|
|
32154
|
+
}
|
|
32155
|
+
}
|
|
32156
|
+
|
|
32157
|
+
/**
|
|
32158
|
+
* React Native Unified Auth Provider
|
|
32159
|
+
*
|
|
32160
|
+
* Creates a platform-specific auth provider that automatically selects the appropriate storage:
|
|
32161
|
+
* - Web: Uses LocalStorageTokenStorage from core SDK
|
|
32162
|
+
* - Mobile: Uses AsyncStorage-based storage
|
|
32163
|
+
*/
|
|
32164
|
+
// Use React Native's built-in platform detection
|
|
32165
|
+
const isWebPlatform = reactNative.Platform.OS === 'web';
|
|
32166
|
+
/**
|
|
32167
|
+
* Create a React Native auth provider with platform-appropriate storage
|
|
32168
|
+
*
|
|
32169
|
+
* Automatically selects storage implementation:
|
|
32170
|
+
* - Web: Uses LocalStorageTokenStorage (from core SDK)
|
|
32171
|
+
* - Mobile: Uses AsyncStorageTokenStorage (React Native specific)
|
|
32172
|
+
*
|
|
32173
|
+
* @param projectKey - PERS project key
|
|
32174
|
+
* @param config - Configuration options
|
|
32175
|
+
* @returns DefaultAuthProvider configured for the current platform
|
|
32176
|
+
*/
|
|
32177
|
+
function createReactNativeAuthProvider(projectKey, config = {}) {
|
|
32178
|
+
if (!projectKey || typeof projectKey !== 'string') {
|
|
32179
|
+
throw new Error('createReactNativeAuthProvider: projectKey is required and must be a string');
|
|
32180
|
+
}
|
|
32181
|
+
const { keyPrefix = `pers_${projectKey.slice(0, 8)}_`, debug = false, customStorage, authType = 'user' } = config;
|
|
32182
|
+
// Platform-specific storage selection
|
|
32183
|
+
const tokenStorage = customStorage || (isWebPlatform
|
|
32184
|
+
? new LocalStorageTokenStorage()
|
|
32185
|
+
: new ReactNativeSecureStorage(keyPrefix));
|
|
32186
|
+
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
32187
|
+
return new DefaultAuthProvider({
|
|
32188
|
+
authType,
|
|
32189
|
+
projectKey,
|
|
32190
|
+
storage: tokenStorage
|
|
32191
|
+
});
|
|
32192
|
+
}
|
|
32193
|
+
|
|
31680
32194
|
/**
|
|
31681
32195
|
* AsyncStorage Token Storage for React Native Mobile Platforms
|
|
31682
32196
|
*
|
|
@@ -31790,41 +32304,96 @@ class AsyncStorageTokenStorage {
|
|
|
31790
32304
|
}
|
|
31791
32305
|
}
|
|
31792
32306
|
|
|
31793
|
-
|
|
31794
|
-
|
|
31795
|
-
|
|
31796
|
-
|
|
31797
|
-
|
|
31798
|
-
|
|
31799
|
-
|
|
31800
|
-
|
|
31801
|
-
|
|
31802
|
-
|
|
31803
|
-
|
|
31804
|
-
|
|
31805
|
-
|
|
31806
|
-
|
|
31807
|
-
|
|
31808
|
-
|
|
31809
|
-
|
|
31810
|
-
|
|
31811
|
-
|
|
31812
|
-
|
|
31813
|
-
|
|
31814
|
-
|
|
31815
|
-
|
|
32307
|
+
// Conditionally require quick-crypto for Native platforms only
|
|
32308
|
+
let crypto$1;
|
|
32309
|
+
if (reactNative.Platform.OS !== 'web') {
|
|
32310
|
+
try {
|
|
32311
|
+
crypto$1 = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
|
|
32312
|
+
}
|
|
32313
|
+
catch (e) {
|
|
32314
|
+
console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
|
|
32315
|
+
}
|
|
32316
|
+
}
|
|
32317
|
+
else {
|
|
32318
|
+
// on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
|
|
32319
|
+
// But to be safe, we can mock or throw
|
|
32320
|
+
crypto$1 = {
|
|
32321
|
+
generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
|
|
32322
|
+
};
|
|
32323
|
+
}
|
|
32324
|
+
class ReactNativeDPoPProvider {
|
|
32325
|
+
/**
|
|
32326
|
+
* Generates a new key pair (ES256 recommended)
|
|
32327
|
+
*
|
|
32328
|
+
* Note: options.extractable is ignored because for React Native Keychain storage,
|
|
32329
|
+
* we MUST export the keys as JWK/strings. We rely on the Keychain encryption
|
|
32330
|
+
* for security at rest (High Security), making the key "Extractable" in memory
|
|
32331
|
+
* but protected by hardware encryption when stored.
|
|
32332
|
+
*/
|
|
32333
|
+
async generateKeyPair(options) {
|
|
32334
|
+
// Generate P-256 Key Pair
|
|
32335
|
+
return new Promise((resolve, reject) => {
|
|
32336
|
+
crypto$1.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
|
|
32337
|
+
if (err)
|
|
32338
|
+
return reject(err);
|
|
32339
|
+
// Export to JWK for SDK Compatibility
|
|
32340
|
+
// We use 'export' because supportsObjects=false in our storage forces the SDK
|
|
32341
|
+
// to expect serializable keys.
|
|
32342
|
+
const pubJwk = publicKey.export({ format: 'jwk' });
|
|
32343
|
+
const privJwk = privateKey.export({ format: 'jwk' });
|
|
32344
|
+
resolve({
|
|
32345
|
+
publicKey: pubJwk,
|
|
32346
|
+
privateKey: privJwk
|
|
32347
|
+
});
|
|
32348
|
+
});
|
|
32349
|
+
});
|
|
32350
|
+
}
|
|
32351
|
+
async signProof(payload, keyPair) {
|
|
32352
|
+
// 1. Construct Header
|
|
32353
|
+
const header = {
|
|
32354
|
+
typ: 'dpop+jwt',
|
|
32355
|
+
alg: 'ES256',
|
|
32356
|
+
jwk: keyPair.publicKey
|
|
32357
|
+
};
|
|
32358
|
+
// 2. Add Claims (iat/jti)
|
|
32359
|
+
const finalPayload = {
|
|
32360
|
+
...payload,
|
|
32361
|
+
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
32362
|
+
jti: payload.jti || crypto$1.randomUUID()
|
|
32363
|
+
};
|
|
32364
|
+
// 3. Encode
|
|
32365
|
+
const b64Header = this.base64Url(JSON.stringify(header));
|
|
32366
|
+
const b64Payload = this.base64Url(JSON.stringify(finalPayload));
|
|
32367
|
+
const signingInput = `${b64Header}.${b64Payload}`;
|
|
32368
|
+
// 4. Sign
|
|
32369
|
+
const sign = crypto$1.createSign('SHA256');
|
|
32370
|
+
sign.update(signingInput);
|
|
32371
|
+
// sign.end() is not required/available in quick-crypto as it doesn't strictly follow stream interface
|
|
32372
|
+
// Import private key back from JWK to sign
|
|
32373
|
+
// The keyPair.privateKey is a JsonWebKey object because we exported it earlier.
|
|
32374
|
+
// quick-crypto createPrivateKey expects jwk object if format is jwk
|
|
32375
|
+
const privateKeyObj = crypto$1.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
|
|
32376
|
+
const signature = sign.sign(privateKeyObj);
|
|
32377
|
+
// signature is a Buffer in quick-crypto
|
|
32378
|
+
return `${signingInput}.${this.base64UrlBuffer(signature)}`;
|
|
32379
|
+
}
|
|
32380
|
+
async hashAccessToken(accessToken) {
|
|
32381
|
+
const hash = crypto$1.createHash('sha256').update(accessToken).digest();
|
|
32382
|
+
// digest returns Buffer in quick-crypto
|
|
32383
|
+
return this.base64UrlBuffer(hash);
|
|
32384
|
+
}
|
|
32385
|
+
// --- Helpers ---
|
|
32386
|
+
base64Url(str) {
|
|
32387
|
+
return this.base64UrlBuffer(buffer.Buffer.from(str));
|
|
32388
|
+
}
|
|
32389
|
+
base64UrlBuffer(buf) {
|
|
32390
|
+
// Ensure we have a Buffer
|
|
32391
|
+
const buffer$1 = buffer.Buffer.isBuffer(buf) ? buf : buffer.Buffer.from(buf);
|
|
32392
|
+
return buffer$1.toString('base64')
|
|
32393
|
+
.replace(/=/g, '')
|
|
32394
|
+
.replace(/\+/g, '-')
|
|
32395
|
+
.replace(/\//g, '_');
|
|
31816
32396
|
}
|
|
31817
|
-
const { keyPrefix = `pers_${projectKey.slice(0, 8)}_`, debug = false, customStorage, authType = 'user' } = config;
|
|
31818
|
-
// Platform-specific storage selection
|
|
31819
|
-
const tokenStorage = customStorage || (isWebPlatform
|
|
31820
|
-
? new LocalStorageTokenStorage()
|
|
31821
|
-
: new AsyncStorageTokenStorage(keyPrefix));
|
|
31822
|
-
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
31823
|
-
return new DefaultAuthProvider({
|
|
31824
|
-
authType,
|
|
31825
|
-
projectKey,
|
|
31826
|
-
storage: tokenStorage
|
|
31827
|
-
});
|
|
31828
32397
|
}
|
|
31829
32398
|
|
|
31830
32399
|
/**
|
|
@@ -31958,6 +32527,15 @@ const PersSDKProvider = ({ children }) => {
|
|
|
31958
32527
|
...config,
|
|
31959
32528
|
authProvider
|
|
31960
32529
|
};
|
|
32530
|
+
// Inject Native DPoP Provider (crypto-based) for mobile platforms
|
|
32531
|
+
// Web platforms use built-in WebCrypto provider by default
|
|
32532
|
+
if (reactNative.Platform.OS !== 'web' && (!config.dpop?.cryptoProvider)) {
|
|
32533
|
+
sdkConfig.dpop = {
|
|
32534
|
+
...(config.dpop || {}),
|
|
32535
|
+
enabled: config.dpop?.enabled ?? true,
|
|
32536
|
+
cryptoProvider: new ReactNativeDPoPProvider()
|
|
32537
|
+
};
|
|
32538
|
+
}
|
|
31961
32539
|
// Initialize PersSDK with platform-aware auth provider
|
|
31962
32540
|
const sdkInstance = new PersSDK(httpClient, sdkConfig);
|
|
31963
32541
|
setAuthProvider(authProvider);
|
|
@@ -34419,7 +34997,9 @@ exports.ANALYTICS_METRIC_TYPES = ANALYTICS_METRIC_TYPES;
|
|
|
34419
34997
|
exports.AsyncStorageTokenStorage = AsyncStorageTokenStorage;
|
|
34420
34998
|
exports.NativeTokenTypes = NativeTokenTypes;
|
|
34421
34999
|
exports.PersSDKProvider = PersSDKProvider;
|
|
35000
|
+
exports.ReactNativeDPoPProvider = ReactNativeDPoPProvider;
|
|
34422
35001
|
exports.ReactNativeHttpClient = ReactNativeHttpClient;
|
|
35002
|
+
exports.ReactNativeSecureStorage = ReactNativeSecureStorage;
|
|
34423
35003
|
exports.TRANSACTION_FORMATS = TRANSACTION_FORMATS;
|
|
34424
35004
|
exports.TRANSACTION_FORMAT_DESCRIPTIONS = TRANSACTION_FORMAT_DESCRIPTIONS;
|
|
34425
35005
|
exports.apiPublicKeyTestPrefix = apiPublicKeyTestPrefix;
|