@pear-protocol/core-sdk 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.ts +5 -1
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +7 -0
- package/dist/index.js +13 -35
- package/dist/types.d.ts +10 -0
- package/package.json +3 -6
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.js +0 -88
package/dist/auth/index.d.ts
CHANGED
|
@@ -37,7 +37,11 @@ declare class Auth {
|
|
|
37
37
|
scope: "read" | "read_write";
|
|
38
38
|
authMethod: "api_key";
|
|
39
39
|
}>;
|
|
40
|
-
refreshSession(headers?: ClientHeaders): Promise<
|
|
40
|
+
refreshSession(headers?: ClientHeaders): Promise<{
|
|
41
|
+
accessToken: string;
|
|
42
|
+
tokenType: "Bearer";
|
|
43
|
+
expiresIn: number;
|
|
44
|
+
}>;
|
|
41
45
|
refreshToken(body: HttpTypes.RefreshTokenRequest, headers?: ClientHeaders): Promise<{
|
|
42
46
|
accessToken: string;
|
|
43
47
|
refreshToken: string;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -2046,6 +2046,7 @@ declare class Core {
|
|
|
2046
2046
|
status: "OPEN" | "CLOSED";
|
|
2047
2047
|
origin: "PARTIAL" | "PEAR" | "EXTERNAL";
|
|
2048
2048
|
exposure: Record<string, `-${string}` | `+${string}`>;
|
|
2049
|
+
unstableHistoricalData: boolean;
|
|
2049
2050
|
totalFundingPayment: Record<string, `-${string}` | `+${string}`>;
|
|
2050
2051
|
initialExposureUSD: Record<string, `-${string}` | `+${string}`>;
|
|
2051
2052
|
entryPrices: Record<string, string>;
|
|
@@ -2270,6 +2271,7 @@ declare class Core {
|
|
|
2270
2271
|
minimumDriftPct: string;
|
|
2271
2272
|
createdAt: string;
|
|
2272
2273
|
updatedAt: string;
|
|
2274
|
+
pausedReason?: "USER" | "POSITION_CHANGED" | "REPEATED_FAILURES" | undefined;
|
|
2273
2275
|
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2274
2276
|
}[];
|
|
2275
2277
|
nextCursor?: string | undefined;
|
|
@@ -2283,6 +2285,7 @@ declare class Core {
|
|
|
2283
2285
|
minimumDriftPct: string;
|
|
2284
2286
|
createdAt: string;
|
|
2285
2287
|
updatedAt: string;
|
|
2288
|
+
pausedReason?: "USER" | "POSITION_CHANGED" | "REPEATED_FAILURES" | undefined;
|
|
2286
2289
|
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2287
2290
|
};
|
|
2288
2291
|
}>;
|
|
@@ -2295,6 +2298,20 @@ declare class Core {
|
|
|
2295
2298
|
minimumDriftPct: string;
|
|
2296
2299
|
createdAt: string;
|
|
2297
2300
|
updatedAt: string;
|
|
2301
|
+
pausedReason?: "USER" | "POSITION_CHANGED" | "REPEATED_FAILURES" | undefined;
|
|
2302
|
+
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2303
|
+
};
|
|
2304
|
+
}>;
|
|
2305
|
+
replace: (id: string, body: HttpTypes.ReplaceAutoRebalanceConfig, headers?: ClientHeaders) => Promise<{
|
|
2306
|
+
config: {
|
|
2307
|
+
id: string;
|
|
2308
|
+
positionId: string;
|
|
2309
|
+
status: "ACTIVE" | "PAUSED";
|
|
2310
|
+
weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
|
|
2311
|
+
minimumDriftPct: string;
|
|
2312
|
+
createdAt: string;
|
|
2313
|
+
updatedAt: string;
|
|
2314
|
+
pausedReason?: "USER" | "POSITION_CHANGED" | "REPEATED_FAILURES" | undefined;
|
|
2298
2315
|
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2299
2316
|
};
|
|
2300
2317
|
}>;
|
package/dist/core/index.js
CHANGED
|
@@ -282,6 +282,13 @@ class Core {
|
|
|
282
282
|
body
|
|
283
283
|
});
|
|
284
284
|
},
|
|
285
|
+
replace: async (id, body, headers) => {
|
|
286
|
+
return await this.client.fetch(`/rebalance/auto/${id}/replace`, {
|
|
287
|
+
method: "POST",
|
|
288
|
+
headers,
|
|
289
|
+
body
|
|
290
|
+
});
|
|
291
|
+
},
|
|
285
292
|
cancel: async (id, headers) => {
|
|
286
293
|
return await this.client.fetch(`/rebalance/auto/${id}/cancel`, {
|
|
287
294
|
method: "POST",
|
package/dist/index.js
CHANGED
|
@@ -9,28 +9,6 @@ import { HttpClient } from './http';
|
|
|
9
9
|
import { FetchError } from './utils';
|
|
10
10
|
export { FetchError } from './utils';
|
|
11
11
|
|
|
12
|
-
const SESSION_REFRESH_PATH = "/auth/session/refresh";
|
|
13
|
-
const SESSION_REFRESH_TIMEOUT_MS = 15e3;
|
|
14
|
-
const isSessionRefreshRequest = (request) => {
|
|
15
|
-
try {
|
|
16
|
-
return new URL(request.url).pathname.includes(SESSION_REFRESH_PATH);
|
|
17
|
-
} catch {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
const withTimeout = async (promise, ms, label) => {
|
|
22
|
-
let timer;
|
|
23
|
-
try {
|
|
24
|
-
return await Promise.race([
|
|
25
|
-
promise,
|
|
26
|
-
new Promise((_, reject) => {
|
|
27
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
28
|
-
})
|
|
29
|
-
]);
|
|
30
|
-
} finally {
|
|
31
|
-
clearTimeout(timer);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
12
|
class PearSDK {
|
|
35
13
|
admin;
|
|
36
14
|
auth;
|
|
@@ -56,7 +34,14 @@ class PearSDK {
|
|
|
56
34
|
const refreshSession = async () => {
|
|
57
35
|
if (!refreshInFlight) {
|
|
58
36
|
logger.info("[session-refresh] Refreshing session...");
|
|
59
|
-
refreshInFlight =
|
|
37
|
+
refreshInFlight = this.auth.refreshSession().then((session) => {
|
|
38
|
+
logger.info("[session-refresh] Session refreshed successfully");
|
|
39
|
+
try {
|
|
40
|
+
config.onSessionRefreshed?.(session);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.error("[session-refresh] onSessionRefreshed listener threw:", String(err));
|
|
43
|
+
}
|
|
44
|
+
}).catch((err) => {
|
|
60
45
|
logger.error("[session-refresh] Refresh failed:", String(err));
|
|
61
46
|
throw err;
|
|
62
47
|
}).finally(() => {
|
|
@@ -67,25 +52,18 @@ class PearSDK {
|
|
|
67
52
|
}
|
|
68
53
|
return refreshInFlight;
|
|
69
54
|
};
|
|
70
|
-
const expire = (reason) => {
|
|
71
|
-
logger.error(`[session-refresh] ${reason}`);
|
|
72
|
-
config.onSessionExpired?.();
|
|
73
|
-
throw new FetchError("Session expired. Please log in again.", "Unauthorized", 401);
|
|
74
|
-
};
|
|
75
|
-
const alreadyRetried = /* @__PURE__ */ new WeakSet();
|
|
76
55
|
const sessionRefreshMiddleware = async (response, retry, request) => {
|
|
77
56
|
if (response.status !== 401) return response;
|
|
78
|
-
if (isSessionRefreshRequest(request)) return response;
|
|
79
57
|
logger.warn(`[session-refresh] 401 received for ${request.method} ${request.url}`);
|
|
80
|
-
if (alreadyRetried.has(request)) return expire("Still 401 after refresh, giving up");
|
|
81
58
|
try {
|
|
82
59
|
await refreshSession();
|
|
60
|
+
logger.info(`[session-refresh] Retrying ${request.method} ${request.url}`);
|
|
61
|
+
return retry();
|
|
83
62
|
} catch {
|
|
84
|
-
|
|
63
|
+
logger.error("[session-refresh] Session expired, cannot recover");
|
|
64
|
+
config.onSessionExpired?.();
|
|
65
|
+
throw new FetchError("Session expired. Please log in again.", "Unauthorized", 401);
|
|
85
66
|
}
|
|
86
|
-
alreadyRetried.add(request);
|
|
87
|
-
logger.info(`[session-refresh] Retrying ${request.method} ${request.url}`);
|
|
88
|
-
return retry();
|
|
89
67
|
};
|
|
90
68
|
client.useResponse(sessionRefreshMiddleware);
|
|
91
69
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -72,6 +72,16 @@ type SDKConfig = {
|
|
|
72
72
|
* Typical use: clear UI state and redirect to login.
|
|
73
73
|
*/
|
|
74
74
|
onSessionExpired?: () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Called with the freshly minted token whenever the SDK rotates the session
|
|
77
|
+
* itself (session mode only). Session mode keeps the session in httpOnly
|
|
78
|
+
* cookies, so this is the only way a caller can observe the access token
|
|
79
|
+
* behind it — typical use is authenticating a bearer-only API alongside.
|
|
80
|
+
* Throwing from here is swallowed; it never fails the rotation.
|
|
81
|
+
*
|
|
82
|
+
* Example: (session) => localStorage.setItem('accessToken', session.accessToken)
|
|
83
|
+
*/
|
|
84
|
+
onSessionRefreshed?: (session: HttpTypes.RefreshSessionResponse) => void;
|
|
75
85
|
/**
|
|
76
86
|
* Logger for debugging
|
|
77
87
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/core-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Pear Protocol Core SDK",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -22,12 +22,10 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup",
|
|
24
24
|
"clean": "rm -rf dist .turbo",
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"test:watch": "vitest",
|
|
27
25
|
"typecheck": "tsc --noEmit"
|
|
28
26
|
},
|
|
29
27
|
"dependencies": {
|
|
30
|
-
"@pear-protocol/types": "^1.
|
|
28
|
+
"@pear-protocol/types": "^1.13.0",
|
|
31
29
|
"@pear-protocol/utils": "^0.2.0",
|
|
32
30
|
"qs": "^6.14.0"
|
|
33
31
|
},
|
|
@@ -35,7 +33,6 @@
|
|
|
35
33
|
"@backend/typescript-config": "workspace:*",
|
|
36
34
|
"@types/qs": "^6.14.0",
|
|
37
35
|
"tsup": "8.5.1",
|
|
38
|
-
"typescript": "5.9.3"
|
|
39
|
-
"vitest": "4.1.2"
|
|
36
|
+
"typescript": "5.9.3"
|
|
40
37
|
}
|
|
41
38
|
}
|
package/dist/index.test.d.ts
DELETED
package/dist/index.test.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { describe, afterEach, vi, it, expect } from 'vitest';
|
|
2
|
-
import PearSDK from './index';
|
|
3
|
-
|
|
4
|
-
const json = (status, body = {}) => {
|
|
5
|
-
return new Response(JSON.stringify(body), {
|
|
6
|
-
status,
|
|
7
|
-
headers: { "content-type": "application/json" }
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
const silentLogger = {
|
|
11
|
-
error: () => {
|
|
12
|
-
},
|
|
13
|
-
warn: () => {
|
|
14
|
-
},
|
|
15
|
-
info: () => {
|
|
16
|
-
},
|
|
17
|
-
debug: () => {
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
const setup = (routes) => {
|
|
21
|
-
const calls = [];
|
|
22
|
-
const fetchMock = vi.fn(async (input) => {
|
|
23
|
-
const key = `${input.method} ${new URL(input.url).pathname}`;
|
|
24
|
-
calls.push(key);
|
|
25
|
-
const queue = routes[key];
|
|
26
|
-
if (!queue || queue.length === 0) {
|
|
27
|
-
throw new Error(`unexpected request: ${key}`);
|
|
28
|
-
}
|
|
29
|
-
const responder = queue.length > 1 ? queue.shift() : queue[0];
|
|
30
|
-
return responder();
|
|
31
|
-
});
|
|
32
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
33
|
-
const onSessionExpired = vi.fn();
|
|
34
|
-
const sdk = new PearSDK({
|
|
35
|
-
url: "https://gateway.test",
|
|
36
|
-
auth: { type: "session" },
|
|
37
|
-
onSessionExpired,
|
|
38
|
-
logger: silentLogger
|
|
39
|
-
});
|
|
40
|
-
return { sdk, onSessionExpired, calls };
|
|
41
|
-
};
|
|
42
|
-
describe("session-refresh middleware", () => {
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
vi.unstubAllGlobals();
|
|
45
|
-
vi.useRealTimers();
|
|
46
|
-
vi.restoreAllMocks();
|
|
47
|
-
});
|
|
48
|
-
it("rejects (never hangs) when the refresh endpoint itself 401s on a dead cookie", async () => {
|
|
49
|
-
const { sdk, onSessionExpired, calls } = setup({
|
|
50
|
-
"GET /positions": [() => json(401, { message: "unauthorized" })],
|
|
51
|
-
"POST /auth/session/refresh": [() => json(401, { message: "refresh failed" })]
|
|
52
|
-
});
|
|
53
|
-
await expect(sdk.core.positions.list({})).rejects.toMatchObject({ status: 401 });
|
|
54
|
-
expect(onSessionExpired).toHaveBeenCalledTimes(1);
|
|
55
|
-
expect(calls).toEqual(["GET /positions", "POST /auth/session/refresh"]);
|
|
56
|
-
});
|
|
57
|
-
it("gives up after a single retry when the request still 401s after a successful refresh", async () => {
|
|
58
|
-
const { sdk, onSessionExpired, calls } = setup({
|
|
59
|
-
"GET /positions": [() => json(401)],
|
|
60
|
-
"POST /auth/session/refresh": [() => json(200)]
|
|
61
|
-
});
|
|
62
|
-
await expect(sdk.core.positions.list({})).rejects.toMatchObject({ status: 401 });
|
|
63
|
-
expect(calls.filter((call) => call === "GET /positions")).toHaveLength(2);
|
|
64
|
-
expect(calls.filter((call) => call === "POST /auth/session/refresh")).toHaveLength(1);
|
|
65
|
-
expect(onSessionExpired).toHaveBeenCalledTimes(1);
|
|
66
|
-
});
|
|
67
|
-
it("transparently refreshes and retries the original request on a recoverable 401", async () => {
|
|
68
|
-
const { sdk, onSessionExpired, calls } = setup({
|
|
69
|
-
"GET /positions": [() => json(401), () => json(200, { positions: ["ok"] })],
|
|
70
|
-
"POST /auth/session/refresh": [() => json(200)]
|
|
71
|
-
});
|
|
72
|
-
await expect(sdk.core.positions.list({})).resolves.toMatchObject({ positions: ["ok"] });
|
|
73
|
-
expect(onSessionExpired).not.toHaveBeenCalled();
|
|
74
|
-
expect(calls).toEqual(["GET /positions", "POST /auth/session/refresh", "GET /positions"]);
|
|
75
|
-
});
|
|
76
|
-
it("caps a stalled refresh at the timeout instead of wedging the queue", async () => {
|
|
77
|
-
vi.useFakeTimers();
|
|
78
|
-
const { sdk, onSessionExpired } = setup({
|
|
79
|
-
"GET /positions": [() => json(401)],
|
|
80
|
-
"POST /auth/session/refresh": [() => new Promise(() => {
|
|
81
|
-
})]
|
|
82
|
-
});
|
|
83
|
-
const settled = expect(sdk.core.positions.list({})).rejects.toMatchObject({ status: 401 });
|
|
84
|
-
await vi.advanceTimersByTimeAsync(15e3);
|
|
85
|
-
await settled;
|
|
86
|
-
expect(onSessionExpired).toHaveBeenCalledTimes(1);
|
|
87
|
-
});
|
|
88
|
-
});
|