opticedge-cloud-utils 1.1.14 → 1.1.16
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/.eslintignore +2 -2
- package/.prettierignore +4 -4
- package/dist/tw/wallet.d.ts +1 -1
- package/dist/tw/wallet.js +6 -7
- package/package.json +2 -2
- package/src/tw/wallet.ts +6 -8
- package/tests/tw/wallet.test.ts +35 -111
package/.eslintignore
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
# Ignore compiled output
|
|
2
|
-
dist/
|
|
1
|
+
# Ignore compiled output
|
|
2
|
+
dist/
|
package/.prettierignore
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
node_modules
|
|
2
|
-
dist
|
|
3
|
-
build
|
|
4
|
-
coverage
|
|
1
|
+
node_modules
|
|
2
|
+
dist
|
|
3
|
+
build
|
|
4
|
+
coverage
|
package/dist/tw/wallet.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function pregenerateInAppWallet(
|
|
1
|
+
export declare function pregenerateInAppWallet(twClientId: string, twSecretKey: string, email: string): Promise<string | null>;
|
package/dist/tw/wallet.js
CHANGED
|
@@ -4,21 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.pregenerateInAppWallet = pregenerateInAppWallet;
|
|
7
|
+
// src/tw/wallet.ts
|
|
7
8
|
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const secrets_1 = require("../secrets");
|
|
9
9
|
const retry_1 = require("../retry");
|
|
10
|
-
async function pregenerateInAppWallet(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.error('Missing TW secret key', { projectId, twSecretKeyName });
|
|
10
|
+
async function pregenerateInAppWallet(twClientId, twSecretKey, email) {
|
|
11
|
+
if (!twClientId || !twSecretKey || !email) {
|
|
12
|
+
console.error('Missing client id or secret or email');
|
|
14
13
|
return null;
|
|
15
14
|
}
|
|
16
15
|
const doRequest = async () => {
|
|
17
16
|
const res = await axios_1.default.post('https://in-app-wallet.thirdweb.com/api/v1/pregenerate', { strategy: 'email', email }, {
|
|
18
17
|
headers: {
|
|
19
|
-
'
|
|
18
|
+
'Content-Type': 'application/json',
|
|
20
19
|
'x-client-id': twClientId,
|
|
21
|
-
'
|
|
20
|
+
'x-secret-key': twSecretKey
|
|
22
21
|
},
|
|
23
22
|
timeout: 10000
|
|
24
23
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opticedge-cloud-utils",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Common utilities for cloud functions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@google-cloud/tasks": "^6.1.0",
|
|
21
21
|
"axios": "^1.10.0",
|
|
22
22
|
"google-auth-library": "^9.15.1",
|
|
23
|
-
"mongodb": "^
|
|
23
|
+
"mongodb": "^7.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@google-cloud/functions-framework": "^4.0.0",
|
package/src/tw/wallet.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
// src/tw/wallet.ts
|
|
1
2
|
import axios from 'axios'
|
|
2
|
-
import { getSecret } from '../secrets'
|
|
3
3
|
import { isRetryableAxios, retry, RetryOptions } from '../retry'
|
|
4
4
|
|
|
5
5
|
export async function pregenerateInAppWallet(
|
|
6
|
-
projectId: string,
|
|
7
6
|
twClientId: string,
|
|
8
|
-
|
|
7
|
+
twSecretKey: string,
|
|
9
8
|
email: string
|
|
10
9
|
): Promise<string | null> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.error('Missing TW secret key', { projectId, twSecretKeyName })
|
|
10
|
+
if (!twClientId || !twSecretKey || !email) {
|
|
11
|
+
console.error('Missing client id or secret or email')
|
|
14
12
|
return null
|
|
15
13
|
}
|
|
16
14
|
|
|
@@ -20,9 +18,9 @@ export async function pregenerateInAppWallet(
|
|
|
20
18
|
{ strategy: 'email', email },
|
|
21
19
|
{
|
|
22
20
|
headers: {
|
|
23
|
-
'
|
|
21
|
+
'Content-Type': 'application/json',
|
|
24
22
|
'x-client-id': twClientId,
|
|
25
|
-
'
|
|
23
|
+
'x-secret-key': twSecretKey
|
|
26
24
|
},
|
|
27
25
|
timeout: 10000
|
|
28
26
|
}
|
package/tests/tw/wallet.test.ts
CHANGED
|
@@ -1,42 +1,35 @@
|
|
|
1
|
+
// tests/tw/wallet.test.ts
|
|
1
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
// tests/wallet.test.ts (or append to your existing test file)
|
|
3
3
|
import axios from 'axios'
|
|
4
4
|
import { pregenerateInAppWallet } from '../../src/tw/wallet'
|
|
5
|
-
import * as secrets from '../../src/secrets'
|
|
6
5
|
|
|
7
6
|
jest.mock('axios')
|
|
8
|
-
jest.mock('../../src/secrets')
|
|
9
|
-
|
|
10
7
|
const mockedAxios = axios as jest.Mocked<typeof axios>
|
|
11
|
-
const mockedGetSecret = secrets.getSecret as jest.Mock
|
|
12
|
-
|
|
13
|
-
const makeSuccessResponse = (data: any) => ({ data, status: 200 })
|
|
14
8
|
|
|
15
|
-
// small helper to synthesize axios-like errors
|
|
16
9
|
function makeAxiosError(message: string, status?: number, noResponse = false) {
|
|
17
10
|
const err: any = new Error(message)
|
|
18
11
|
err.isAxiosError = true
|
|
19
12
|
if (!noResponse) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// no numeric status provided but simulate a response
|
|
24
|
-
err.response = { status: 500, data: { message: 'mock' } }
|
|
13
|
+
err.response = {
|
|
14
|
+
status: typeof status === 'number' ? status : 500,
|
|
15
|
+
data: { message: `mocked ${status ?? 500}` }
|
|
25
16
|
}
|
|
26
17
|
} else {
|
|
27
|
-
//
|
|
18
|
+
// simulate network error (no response)
|
|
28
19
|
delete err.response
|
|
29
20
|
}
|
|
30
21
|
return err
|
|
31
22
|
}
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
function makeSuccessResponse(data: any) {
|
|
25
|
+
return { data, status: 200 }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('pregenerateInAppWallet (current signature)', () => {
|
|
35
29
|
const mockClientId = 'test-client-id'
|
|
36
|
-
const
|
|
30
|
+
const mockSecretKey = 'test-secret-key'
|
|
37
31
|
const mockEmail = 'user@example.com'
|
|
38
32
|
const mockWalletAddress = '0xabc123'
|
|
39
|
-
const mockSecretValue = 'mock-secret-key'
|
|
40
33
|
|
|
41
34
|
const realMathRandom = Math.random
|
|
42
35
|
const realWarn = console.warn
|
|
@@ -44,162 +37,93 @@ describe('pregenerateInAppWallet with retry behavior', () => {
|
|
|
44
37
|
|
|
45
38
|
beforeEach(() => {
|
|
46
39
|
jest.clearAllMocks()
|
|
47
|
-
// make retry jitter deterministic (so
|
|
40
|
+
// make retry jitter deterministic (so delays don't interfere)
|
|
48
41
|
Math.random = jest.fn(() => 0)
|
|
49
42
|
|
|
43
|
+
// axios.isAxiosError helper
|
|
50
44
|
jest
|
|
51
45
|
.spyOn(axios, 'isAxiosError')
|
|
52
46
|
.mockImplementation((e: any) => Boolean(e && e.isAxiosError) as any)
|
|
53
|
-
|
|
47
|
+
|
|
48
|
+
// silence logs during tests but keep spies
|
|
54
49
|
console.warn = jest.fn()
|
|
55
50
|
console.error = jest.fn()
|
|
56
51
|
})
|
|
57
52
|
|
|
58
53
|
afterEach(() => {
|
|
59
|
-
// restore
|
|
60
54
|
Math.random = realMathRandom
|
|
61
55
|
console.warn = realWarn
|
|
62
56
|
console.error = realError
|
|
63
57
|
})
|
|
64
58
|
|
|
65
|
-
it('returns null and logs if
|
|
66
|
-
|
|
67
|
-
mockedGetSecret.mockResolvedValueOnce(undefined)
|
|
68
|
-
|
|
69
|
-
const result = await pregenerateInAppWallet(
|
|
70
|
-
mockProjectId,
|
|
71
|
-
mockClientId,
|
|
72
|
-
mockSecretKeyName,
|
|
73
|
-
mockEmail
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
// should not call axios at all
|
|
59
|
+
it('returns null and logs if params missing', async () => {
|
|
60
|
+
const res = await pregenerateInAppWallet('', mockSecretKey, mockEmail)
|
|
77
61
|
expect(mockedAxios.post).not.toHaveBeenCalled()
|
|
78
|
-
expect(
|
|
79
|
-
|
|
80
|
-
// console.error should be called with "Missing TW secret key"
|
|
62
|
+
expect(res).toBeNull()
|
|
81
63
|
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
82
|
-
expect((console.error as jest.Mock).mock.calls[0][0]).toEqual(
|
|
83
|
-
|
|
84
|
-
expect((console.error as jest.Mock).mock.calls[0][1]).toEqual(
|
|
85
|
-
expect.objectContaining({ projectId: mockProjectId, twSecretKeyName: mockSecretKeyName })
|
|
64
|
+
expect((console.error as jest.Mock).mock.calls[0][0]).toEqual(
|
|
65
|
+
'Missing client id or secret or email'
|
|
86
66
|
)
|
|
87
67
|
})
|
|
88
68
|
|
|
89
69
|
it('retries on 500 then succeeds and returns address', async () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// first call: server 500 -> rejected
|
|
70
|
+
// first: 500, then success
|
|
93
71
|
mockedAxios.post
|
|
94
72
|
.mockRejectedValueOnce(makeAxiosError('server error', 500))
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
wallet: { address: mockWalletAddress }
|
|
99
|
-
}
|
|
100
|
-
} as any)
|
|
101
|
-
|
|
102
|
-
const result = await pregenerateInAppWallet(
|
|
103
|
-
mockProjectId,
|
|
104
|
-
mockClientId,
|
|
105
|
-
mockSecretKeyName,
|
|
106
|
-
mockEmail
|
|
107
|
-
)
|
|
73
|
+
.mockResolvedValueOnce({ data: { wallet: { address: mockWalletAddress } } } as any)
|
|
74
|
+
|
|
75
|
+
const result = await pregenerateInAppWallet(mockClientId, mockSecretKey, mockEmail)
|
|
108
76
|
|
|
109
|
-
expect(mockedGetSecret).toHaveBeenCalledWith(mockProjectId, mockSecretKeyName)
|
|
110
77
|
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
111
78
|
expect(result).toBe(mockWalletAddress)
|
|
112
|
-
// onRetry should have been called (we log via console.warn in our version)
|
|
113
79
|
expect((console.warn as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
114
80
|
})
|
|
115
81
|
|
|
116
82
|
it('does NOT retry on 400 and returns null immediately', async () => {
|
|
117
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
118
83
|
mockedAxios.post.mockRejectedValue(makeAxiosError('bad request', 400))
|
|
119
84
|
|
|
120
|
-
const result = await pregenerateInAppWallet(
|
|
121
|
-
mockProjectId,
|
|
122
|
-
mockClientId,
|
|
123
|
-
mockSecretKeyName,
|
|
124
|
-
mockEmail
|
|
125
|
-
)
|
|
85
|
+
const result = await pregenerateInAppWallet(mockClientId, mockSecretKey, mockEmail)
|
|
126
86
|
|
|
127
87
|
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
|
|
128
88
|
expect(result).toBeNull()
|
|
129
|
-
//
|
|
89
|
+
// no retry log
|
|
130
90
|
expect((console.warn as jest.Mock).mock.calls.length).toBe(0)
|
|
131
91
|
})
|
|
132
92
|
|
|
133
93
|
it('retries on network/no-response errors and then succeeds', async () => {
|
|
134
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
135
|
-
|
|
136
|
-
// First: network error (no response)
|
|
137
94
|
mockedAxios.post
|
|
138
95
|
.mockRejectedValueOnce(makeAxiosError('network down', undefined, true))
|
|
139
|
-
.mockResolvedValueOnce({
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const result = await pregenerateInAppWallet(
|
|
144
|
-
mockProjectId,
|
|
145
|
-
mockClientId,
|
|
146
|
-
mockSecretKeyName,
|
|
147
|
-
mockEmail
|
|
148
|
-
)
|
|
96
|
+
.mockResolvedValueOnce({ data: { wallet: { address: mockWalletAddress } } } as any)
|
|
97
|
+
|
|
98
|
+
const result = await pregenerateInAppWallet(mockClientId, mockSecretKey, mockEmail)
|
|
149
99
|
|
|
150
100
|
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
151
101
|
expect(result).toBe(mockWalletAddress)
|
|
152
102
|
})
|
|
153
103
|
|
|
154
104
|
it('exhausts retries on repeated 500s and returns null', async () => {
|
|
155
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
156
|
-
|
|
157
|
-
// Always 500 errors
|
|
158
105
|
mockedAxios.post.mockRejectedValue(makeAxiosError('server error', 500))
|
|
159
106
|
|
|
160
|
-
const result = await pregenerateInAppWallet(
|
|
161
|
-
mockProjectId,
|
|
162
|
-
mockClientId,
|
|
163
|
-
mockSecretKeyName,
|
|
164
|
-
mockEmail
|
|
165
|
-
)
|
|
107
|
+
const result = await pregenerateInAppWallet(mockClientId, mockSecretKey, mockEmail)
|
|
166
108
|
|
|
167
|
-
// retry
|
|
109
|
+
// retry behavior is implementation-dependent; assert at least one call and final null
|
|
168
110
|
expect(mockedAxios.post.mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
169
|
-
// final result should be null when retries exhausted
|
|
170
111
|
expect(result).toBeNull()
|
|
171
|
-
// error should have been logged
|
|
172
112
|
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
173
113
|
})
|
|
174
114
|
|
|
175
|
-
it('logs invalid wallet shape and returns null when wallet.address
|
|
176
|
-
mockedGetSecret.mockResolvedValueOnce(mockSecretValue)
|
|
177
|
-
|
|
178
|
-
// Simulate a 200 response that has wallet but no address
|
|
115
|
+
it('logs invalid wallet shape and returns null when wallet.address missing (2xx response)', async () => {
|
|
179
116
|
mockedAxios.post.mockResolvedValueOnce(makeSuccessResponse({ wallet: {} }))
|
|
180
117
|
|
|
181
|
-
const result = await pregenerateInAppWallet(
|
|
182
|
-
mockProjectId,
|
|
183
|
-
mockClientId,
|
|
184
|
-
mockSecretKeyName,
|
|
185
|
-
mockEmail
|
|
186
|
-
)
|
|
118
|
+
const result = await pregenerateInAppWallet(mockClientId, mockSecretKey, mockEmail)
|
|
187
119
|
|
|
188
|
-
// axios was called once, but the function returns null (final failure)
|
|
189
120
|
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
|
|
190
121
|
expect(result).toBeNull()
|
|
191
122
|
|
|
192
|
-
//
|
|
123
|
+
// ensure we logged the invalid shape (the implementation logs then throws)
|
|
193
124
|
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
194
|
-
|
|
195
|
-
// the first arg of the logged call should be the string we log in the implementation
|
|
196
|
-
// (adjust if your message differs)
|
|
197
125
|
const firstArg = (console.error as jest.Mock).mock.calls[0][0]
|
|
198
|
-
expect(firstArg).toEqual(
|
|
199
|
-
'Invalid wallet response shape' /* or 'Invalid wallet response shape' */
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
// the second arg should contain status and data info
|
|
126
|
+
expect(firstArg).toEqual('Invalid wallet response shape')
|
|
203
127
|
const secondArg = (console.error as jest.Mock).mock.calls[0][1]
|
|
204
128
|
expect(secondArg).toEqual(expect.objectContaining({ status: 200, data: { wallet: {} } }))
|
|
205
129
|
})
|