@nadohq/mobile-client 0.65.0 → 0.67.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 +202 -10
- package/dist/{MobileClient-CfVZXTcz.cjs → MobileClient-vuI5wlB_.cjs} +303 -21
- package/dist/MobileClient-vuI5wlB_.cjs.map +1 -0
- package/dist/MobileClient.cjs +1 -1
- package/dist/MobileClient.d.cts +175 -20
- package/dist/MobileClient.d.cts.map +1 -1
- package/dist/MobileClient.d.ts +175 -20
- package/dist/MobileClient.d.ts.map +1 -1
- package/dist/MobileClient.js +303 -21
- package/dist/MobileClient.js.map +1 -1
- package/dist/{MobileServerFailureError-BZVwlsMg.d.ts → MobileServerFailureError-BeCCNShj.d.cts} +2 -3
- package/dist/MobileServerFailureError-BeCCNShj.d.cts.map +1 -0
- package/dist/{MobileServerFailureError-CbtK8kD4.d.cts → MobileServerFailureError-hHv28weI.d.ts} +2 -3
- package/dist/MobileServerFailureError-hHv28weI.d.ts.map +1 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.cts +15 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.cts.map +1 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.ts +15 -0
- package/dist/MobileUnsupportedNotificationRouteError-DpiNBvvY.d.ts.map +1 -0
- package/dist/{buildSignedMobileRequest-C1v0flCR.d.cts → buildSignedMobileRequest-DcEMKU_4.d.cts} +2 -2
- package/dist/{buildSignedMobileRequest-C1v0flCR.d.cts.map → buildSignedMobileRequest-DcEMKU_4.d.cts.map} +1 -1
- package/dist/{buildSignedMobileRequest-UeeWdYGU.d.ts → buildSignedMobileRequest-DlTMvoXR.d.ts} +2 -2
- package/dist/{buildSignedMobileRequest-UeeWdYGU.d.ts.map → buildSignedMobileRequest-DlTMvoXR.d.ts.map} +1 -1
- package/dist/{canonicalize-5eeZrkzQ.d.ts → canonicalize-BOZlflI3.d.ts} +2 -2
- package/dist/canonicalize-BOZlflI3.d.ts.map +1 -0
- package/dist/{canonicalize-CBaZprTo.d.cts → canonicalize-BvCozzvJ.d.cts} +2 -2
- package/dist/canonicalize-BvCozzvJ.d.cts.map +1 -0
- package/dist/{clientTypes-_mEgzmNs.d.cts → clientTypes-KpHlCDGZ.d.cts} +341 -38
- package/dist/clientTypes-KpHlCDGZ.d.cts.map +1 -0
- package/dist/{clientTypes-C9dJufxk.d.ts → clientTypes-XRI41VdC.d.ts} +341 -38
- package/dist/clientTypes-XRI41VdC.d.ts.map +1 -0
- package/dist/dataMappers.cjs +257 -20
- package/dist/dataMappers.cjs.map +1 -1
- package/dist/dataMappers.d.cts +98 -15
- package/dist/dataMappers.d.cts.map +1 -1
- package/dist/dataMappers.d.ts +98 -15
- package/dist/dataMappers.d.ts.map +1 -1
- package/dist/dataMappers.js +242 -20
- package/dist/dataMappers.js.map +1 -1
- package/dist/{eip712MethodByType-D8jLoxDu.d.cts → eip712MethodByType-DyAmzH7b.d.cts} +2 -2
- package/dist/eip712MethodByType-DyAmzH7b.d.cts.map +1 -0
- package/dist/{eip712MethodByType-DcUh8h-l.d.ts → eip712MethodByType-qSSAF6i2.d.ts} +2 -2
- package/dist/eip712MethodByType-qSSAF6i2.d.ts.map +1 -0
- package/dist/feed-BrgkFqP3.d.cts +23 -0
- package/dist/feed-BrgkFqP3.d.cts.map +1 -0
- package/dist/feed-DcbhLoo1.d.ts +23 -0
- package/dist/feed-DcbhLoo1.d.ts.map +1 -0
- package/dist/index-BVVgDSdq.d.cts +1 -0
- package/dist/index-BVVgDSdq.d.ts +1 -0
- package/dist/index.cjs +8 -2
- package/dist/index.d.cts +14 -16
- package/dist/index.d.ts +14 -16
- package/dist/index.js +4 -3
- package/dist/{mobileErrorCodes-CmjYEIYm.d.ts → mobileErrorCodes-CdwX6QTO.d.cts} +8 -3
- package/dist/mobileErrorCodes-CdwX6QTO.d.cts.map +1 -0
- package/dist/{mobileErrorCodes-CmjYEIYm.d.cts → mobileErrorCodes-CdwX6QTO.d.ts} +8 -3
- package/dist/mobileErrorCodes-CdwX6QTO.d.ts.map +1 -0
- package/dist/{payloadHash-DhHmgRqL.d.cts → payloadHash-BgGSZock.d.cts} +2 -2
- package/dist/payloadHash-BgGSZock.d.cts.map +1 -0
- package/dist/{payloadHash-E1_8fpLW.d.ts → payloadHash-Bi6upCLQ.d.ts} +2 -2
- package/dist/payloadHash-Bi6upCLQ.d.ts.map +1 -0
- package/dist/{serverExecuteTypes-BwWGspOg.d.ts → serverExecuteTypes-DuCWe_H_.d.cts} +23 -14
- package/dist/serverExecuteTypes-DuCWe_H_.d.cts.map +1 -0
- package/dist/{serverExecuteTypes-B-OJok19.d.cts → serverExecuteTypes-usJ6HMJv.d.ts} +23 -14
- package/dist/serverExecuteTypes-usJ6HMJv.d.ts.map +1 -0
- package/dist/{serverModelTypes-CiA-dy1R.d.ts → serverModelTypes-CTHMNIQS.d.cts} +170 -13
- package/dist/serverModelTypes-CTHMNIQS.d.cts.map +1 -0
- package/dist/{serverModelTypes-CiA-dy1R.d.cts → serverModelTypes-CTHMNIQS.d.ts} +170 -13
- package/dist/serverModelTypes-CTHMNIQS.d.ts.map +1 -0
- package/dist/{serverResponseGuards-CShiXpHT.d.ts → serverResponseGuards-CzmldAad.d.cts} +2 -2
- package/dist/{serverResponseGuards-BW69PS7O.d.cts.map → serverResponseGuards-CzmldAad.d.cts.map} +1 -1
- package/dist/{serverResponseGuards-BW69PS7O.d.cts → serverResponseGuards-Dvkbjp2_.d.ts} +2 -2
- package/dist/{serverResponseGuards-CShiXpHT.d.ts.map → serverResponseGuards-Dvkbjp2_.d.ts.map} +1 -1
- package/dist/signing/buildSignedMobileRequest.d.cts +1 -1
- package/dist/signing/buildSignedMobileRequest.d.ts +1 -1
- package/dist/signing/canonicalize.cjs +53 -2
- package/dist/signing/canonicalize.cjs.map +1 -1
- package/dist/signing/canonicalize.d.cts +1 -1
- package/dist/signing/canonicalize.d.ts +1 -1
- package/dist/signing/canonicalize.js +53 -2
- package/dist/signing/canonicalize.js.map +1 -1
- package/dist/signing/eip712MethodByType.cjs +7 -0
- package/dist/signing/eip712MethodByType.cjs.map +1 -1
- package/dist/signing/eip712MethodByType.d.cts +1 -1
- package/dist/signing/eip712MethodByType.d.ts +1 -1
- package/dist/signing/eip712MethodByType.js +7 -0
- package/dist/signing/eip712MethodByType.js.map +1 -1
- package/dist/signing/index.d.cts +5 -5
- package/dist/signing/index.d.ts +5 -5
- package/dist/signing/payloadHash.cjs +1 -1
- package/dist/signing/payloadHash.d.cts +1 -1
- package/dist/signing/payloadHash.d.ts +1 -1
- package/dist/signing/payloadHash.js +1 -1
- package/dist/signing/types.d.cts +1 -1
- package/dist/signing/types.d.ts +1 -1
- package/dist/types/MobileServerFailureError.d.cts +1 -1
- package/dist/types/MobileServerFailureError.d.ts +1 -1
- package/dist/types/MobileUnsupportedNotificationRouteError.cjs +19 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.cjs.map +1 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.d.cts +2 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.d.ts +2 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.js +18 -0
- package/dist/types/MobileUnsupportedNotificationRouteError.js.map +1 -0
- package/dist/types/clientTypes.d.cts +2 -2
- package/dist/types/clientTypes.d.ts +2 -2
- package/dist/types/index.cjs +4 -0
- package/dist/types/index.d.cts +8 -9
- package/dist/types/index.d.ts +8 -9
- package/dist/types/index.js +3 -2
- package/dist/types/mobileErrorCodes.cjs +11 -1
- package/dist/types/mobileErrorCodes.cjs.map +1 -1
- package/dist/types/mobileErrorCodes.d.cts +1 -1
- package/dist/types/mobileErrorCodes.d.ts +1 -1
- package/dist/types/mobileErrorCodes.js +11 -1
- package/dist/types/mobileErrorCodes.js.map +1 -1
- package/dist/types/serverBaseTypes.d.cts +1 -1
- package/dist/types/serverBaseTypes.d.ts +1 -1
- package/dist/types/serverExecuteTypes.d.cts +2 -2
- package/dist/types/serverExecuteTypes.d.ts +2 -2
- package/dist/types/serverModelTypes.cjs +15 -0
- package/dist/types/serverModelTypes.cjs.map +1 -1
- package/dist/types/serverModelTypes.d.cts +2 -2
- package/dist/types/serverModelTypes.d.ts +2 -2
- package/dist/types/serverModelTypes.js +14 -1
- package/dist/types/serverModelTypes.js.map +1 -1
- package/dist/types/serverQueryTypes.d.cts +2 -2
- package/dist/types/serverQueryTypes.d.ts +2 -2
- package/dist/types/serverRequestTypes.d.cts +2 -2
- package/dist/types/serverRequestTypes.d.ts +2 -2
- package/dist/types-CByl_NyJ.d.ts +825 -0
- package/dist/types-CByl_NyJ.d.ts.map +1 -0
- package/dist/types-CR5iaKR4.d.cts +825 -0
- package/dist/types-CR5iaKR4.d.cts.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.cjs +31 -0
- package/dist/utils/buildMobileFeedImageMultipart.cjs.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.cts +15 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.cts.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.ts +15 -0
- package/dist/utils/buildMobileFeedImageMultipart.d.ts.map +1 -0
- package/dist/utils/buildMobileFeedImageMultipart.js +30 -0
- package/dist/utils/buildMobileFeedImageMultipart.js.map +1 -0
- package/dist/utils/feed.cjs +12 -0
- package/dist/utils/feed.cjs.map +1 -1
- package/dist/utils/feed.d.cts +2 -2
- package/dist/utils/feed.d.ts +2 -2
- package/dist/utils/feed.js +11 -1
- package/dist/utils/feed.js.map +1 -1
- package/dist/utils/index.cjs +3 -1
- package/dist/utils/index.d.cts +3 -3
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +2 -2
- package/dist/utils/serverResponseGuards.d.cts +1 -1
- package/dist/utils/serverResponseGuards.d.ts +1 -1
- package/package.json +3 -3
- package/src/MobileClient.ts +444 -19
- package/src/dataMappers.test.ts +511 -6
- package/src/dataMappers.ts +325 -24
- package/src/feedImages.test.ts +309 -0
- package/src/notifications.test.ts +950 -0
- package/src/signing/canonicalize.ts +72 -0
- package/src/signing/eip712MethodByType.ts +10 -0
- package/src/signing/signing.test.ts +67 -1
- package/src/signing/types.ts +55 -9
- package/src/socialFeed.test.ts +63 -2
- package/src/types/MobileUnsupportedNotificationRouteError.ts +16 -0
- package/src/types/clientTypes.ts +427 -36
- package/src/types/index.ts +1 -0
- package/src/types/mobileErrorCodes.ts +10 -0
- package/src/types/serverExecuteTypes.ts +22 -10
- package/src/types/serverModelTypes.ts +216 -11
- package/src/types/serverQueryTypes.ts +106 -9
- package/src/types/serverRequestTypes.ts +114 -17
- package/src/utils/buildMobileFeedImageMultipart.test.ts +45 -0
- package/src/utils/buildMobileFeedImageMultipart.ts +36 -0
- package/src/utils/feed.ts +16 -0
- package/dist/MobileClient-CfVZXTcz.cjs.map +0 -1
- package/dist/MobileServerFailureError-BZVwlsMg.d.ts.map +0 -1
- package/dist/MobileServerFailureError-CbtK8kD4.d.cts.map +0 -1
- package/dist/canonicalize-5eeZrkzQ.d.ts.map +0 -1
- package/dist/canonicalize-CBaZprTo.d.cts.map +0 -1
- package/dist/clientTypes-C9dJufxk.d.ts.map +0 -1
- package/dist/clientTypes-_mEgzmNs.d.cts.map +0 -1
- package/dist/eip712MethodByType-D8jLoxDu.d.cts.map +0 -1
- package/dist/eip712MethodByType-DcUh8h-l.d.ts.map +0 -1
- package/dist/feed-DTJlsg8W.d.cts +0 -14
- package/dist/feed-DTJlsg8W.d.cts.map +0 -1
- package/dist/feed-DTJlsg8W.d.ts +0 -14
- package/dist/feed-DTJlsg8W.d.ts.map +0 -1
- package/dist/mobileErrorCodes-CmjYEIYm.d.cts.map +0 -1
- package/dist/mobileErrorCodes-CmjYEIYm.d.ts.map +0 -1
- package/dist/payloadHash-DhHmgRqL.d.cts.map +0 -1
- package/dist/payloadHash-E1_8fpLW.d.ts.map +0 -1
- package/dist/serverBaseTypes-B5J1_TyC.d.cts +0 -25
- package/dist/serverBaseTypes-B5J1_TyC.d.cts.map +0 -1
- package/dist/serverBaseTypes-DnLzsdm-.d.ts +0 -25
- package/dist/serverBaseTypes-DnLzsdm-.d.ts.map +0 -1
- package/dist/serverExecuteTypes-B-OJok19.d.cts.map +0 -1
- package/dist/serverExecuteTypes-BwWGspOg.d.ts.map +0 -1
- package/dist/serverModelTypes-CiA-dy1R.d.cts.map +0 -1
- package/dist/serverModelTypes-CiA-dy1R.d.ts.map +0 -1
- package/dist/serverQueryTypes-CJcjLB7n.d.ts +0 -283
- package/dist/serverQueryTypes-CJcjLB7n.d.ts.map +0 -1
- package/dist/serverQueryTypes-V1f9yE7L.d.cts +0 -283
- package/dist/serverQueryTypes-V1f9yE7L.d.cts.map +0 -1
- package/dist/serverRequestTypes-CRlkNSu5.d.cts +0 -270
- package/dist/serverRequestTypes-CRlkNSu5.d.cts.map +0 -1
- package/dist/serverRequestTypes-DZoOhV2o.d.ts +0 -270
- package/dist/serverRequestTypes-DZoOhV2o.d.ts.map +0 -1
- package/dist/types-BiRmH5Iv.d.cts +0 -108
- package/dist/types-BiRmH5Iv.d.cts.map +0 -1
- package/dist/types-DoJuq0Ij.d.ts +0 -108
- package/dist/types-DoJuq0Ij.d.ts.map +0 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, jest } from '@jest/globals';
|
|
2
|
+
import { createWalletClient, http, keccak256, stringToBytes } from 'viem';
|
|
3
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
+
import { ink } from 'viem/chains';
|
|
5
|
+
import { MobileClient } from './MobileClient';
|
|
6
|
+
import {
|
|
7
|
+
MOBILE_ERROR_CODES,
|
|
8
|
+
MobileServerFailureError,
|
|
9
|
+
MobileServerFailureResponse,
|
|
10
|
+
MobileServerSocialFeedImage,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { MOBILE_FEED_IMAGE_MAX_REQUEST_BYTES } from './utils/feed';
|
|
13
|
+
|
|
14
|
+
const imagePath = '/v1/mobile/feed/images/test/standard';
|
|
15
|
+
const session = { sessionId: 'Session', viewerToken: 'Viewer' };
|
|
16
|
+
const imageBytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0, 127, 128, 255]);
|
|
17
|
+
|
|
18
|
+
function mockImageClient(data: ArrayBuffer | Uint8Array, status = 200) {
|
|
19
|
+
const client = new MobileClient({ url: 'https://mobile.example/mobile/v1' });
|
|
20
|
+
client.axiosInstance.defaults.adapter = (config) => {
|
|
21
|
+
expect(config.url).toBe(
|
|
22
|
+
'https://mobile.example/mobile/v1/feed/images/test/standard',
|
|
23
|
+
);
|
|
24
|
+
expect(config.method).toBe('get');
|
|
25
|
+
expect(config.responseType).toBe('arraybuffer');
|
|
26
|
+
expect(config.headers.get('x-feed-session-id')).toBe(session.sessionId);
|
|
27
|
+
expect(config.headers.get('x-feed-viewer-token')).toBe(session.viewerToken);
|
|
28
|
+
return Promise.resolve({
|
|
29
|
+
data,
|
|
30
|
+
status,
|
|
31
|
+
statusText: 'mock',
|
|
32
|
+
headers: {},
|
|
33
|
+
config,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
return client;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('[mobile-client]: feed image reads', () => {
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
jest.restoreAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it.each([
|
|
45
|
+
'/v1/mobile/feed/images/test/standard',
|
|
46
|
+
'/feed/images/test/standard',
|
|
47
|
+
'/v1/mobile/feed/comment-images/test/thumbnail',
|
|
48
|
+
])('exposes a native image request without fetching bytes: %s', (path) => {
|
|
49
|
+
const client = new MobileClient({
|
|
50
|
+
url: 'https://mobile.example/mobile/v1',
|
|
51
|
+
});
|
|
52
|
+
const get = jest.spyOn(client.axiosInstance, 'get');
|
|
53
|
+
expect(client.getFeedImageRequest({ ...session, path })).toEqual({
|
|
54
|
+
url: client.resolveFeedImageUrl(path),
|
|
55
|
+
headers: {
|
|
56
|
+
'x-feed-session-id': session.sessionId,
|
|
57
|
+
'x-feed-viewer-token': session.viewerToken,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
expect(get).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it.each(['uint8array', 'arraybuffer'] as const)(
|
|
64
|
+
'preserves successful image bytes from %s responses',
|
|
65
|
+
async (format) => {
|
|
66
|
+
const data =
|
|
67
|
+
format === 'uint8array' ? imageBytes : imageBytes.slice().buffer;
|
|
68
|
+
const client = mockImageClient(data);
|
|
69
|
+
const getRequest = jest.spyOn(client, 'getFeedImageRequest');
|
|
70
|
+
await expect(
|
|
71
|
+
client.getFeedImageBytes({ ...session, path: imagePath }),
|
|
72
|
+
).resolves.toEqual(imageBytes);
|
|
73
|
+
expect(getRequest).toHaveBeenCalledWith({ ...session, path: imagePath });
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
describe.each(['uint8array', 'arraybuffer'] as const)(
|
|
78
|
+
'%s error responses',
|
|
79
|
+
(format) => {
|
|
80
|
+
it.each([
|
|
81
|
+
[401, MOBILE_ERROR_CODES.SOCIAL_FEED_SESSION_EXPIRED, 'feed_image'],
|
|
82
|
+
[403, MOBILE_ERROR_CODES.SOCIAL_FEED_SESSION_MISMATCH, 'feed_image'],
|
|
83
|
+
[404, MOBILE_ERROR_CODES.SOCIAL_FEED_CONTENT_UNAVAILABLE, 'feed_image'],
|
|
84
|
+
[
|
|
85
|
+
404,
|
|
86
|
+
MOBILE_ERROR_CODES.SOCIAL_FEED_CONTENT_UNAVAILABLE,
|
|
87
|
+
'feed_comment_image',
|
|
88
|
+
],
|
|
89
|
+
] as const)(
|
|
90
|
+
'preserves the failure envelope for HTTP %i',
|
|
91
|
+
async (status, errorCode, requestType) => {
|
|
92
|
+
const failure: MobileServerFailureResponse = {
|
|
93
|
+
status: 'failure',
|
|
94
|
+
error: 'Image unavailable: \u56fe\u7247\u4e0d\u53ef\u7528',
|
|
95
|
+
error_code: errorCode,
|
|
96
|
+
request_type: requestType,
|
|
97
|
+
};
|
|
98
|
+
const encoded = stringToBytes(JSON.stringify(failure));
|
|
99
|
+
const data =
|
|
100
|
+
format === 'uint8array' ? encoded : Uint8Array.from(encoded).buffer;
|
|
101
|
+
const client = mockImageClient(data, status);
|
|
102
|
+
const result = client.getFeedImageBytes({
|
|
103
|
+
...session,
|
|
104
|
+
path: imagePath,
|
|
105
|
+
});
|
|
106
|
+
await expect(result).rejects.toBeInstanceOf(MobileServerFailureError);
|
|
107
|
+
await expect(result).rejects.toMatchObject({
|
|
108
|
+
errorCode,
|
|
109
|
+
requestType,
|
|
110
|
+
httpStatus: status,
|
|
111
|
+
responseData: failure,
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
it.each([
|
|
119
|
+
'',
|
|
120
|
+
'<html>Bad gateway</html>',
|
|
121
|
+
'{invalid',
|
|
122
|
+
'{"message":"unavailable"}',
|
|
123
|
+
])('keeps non-envelope failures as transport errors: %s', async (body) => {
|
|
124
|
+
const client = mockImageClient(stringToBytes(body), 502);
|
|
125
|
+
const result = client.getFeedImageBytes({ ...session, path: imagePath });
|
|
126
|
+
await expect(result).rejects.not.toBeInstanceOf(MobileServerFailureError);
|
|
127
|
+
await expect(result).rejects.toThrow(
|
|
128
|
+
'Unexpected response from mobile service: 502',
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it.each([
|
|
133
|
+
'/v1/mobile/feed/images/test/standard',
|
|
134
|
+
'/feed/images/test/standard',
|
|
135
|
+
])('resolves the metadata path %s', (path) => {
|
|
136
|
+
const client = new MobileClient({
|
|
137
|
+
url: 'https://mobile.example/mobile/v1',
|
|
138
|
+
});
|
|
139
|
+
expect(client.resolveFeedImageUrl(path)).toBe(
|
|
140
|
+
'https://mobile.example/mobile/v1/feed/images/test/standard',
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('[mobile-client]: feed image uploads', () => {
|
|
146
|
+
it.each([
|
|
147
|
+
['createFeedPostImageUpload', 'create_feed_post_image_uploads'],
|
|
148
|
+
['createFeedCommentImageUpload', 'create_feed_comment_image_uploads'],
|
|
149
|
+
] as const)('sends signed multipart bytes for %s', async (method, type) => {
|
|
150
|
+
const account = privateKeyToAccount(
|
|
151
|
+
'0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b786907',
|
|
152
|
+
);
|
|
153
|
+
const client = new MobileClient({
|
|
154
|
+
url: 'https://mobile.example/mobile/v1',
|
|
155
|
+
walletClient: createWalletClient({
|
|
156
|
+
account,
|
|
157
|
+
chain: ink,
|
|
158
|
+
transport: http(),
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
const image: MobileServerSocialFeedImage = {
|
|
162
|
+
image_id: '88888888-8888-4888-8888-888888888888',
|
|
163
|
+
url: imagePath,
|
|
164
|
+
width: 1,
|
|
165
|
+
height: 1,
|
|
166
|
+
content_type: 'image/jpeg',
|
|
167
|
+
byte_size: imageBytes.length,
|
|
168
|
+
thumbnail: {
|
|
169
|
+
url: '/v1/mobile/feed/images/test/thumbnail',
|
|
170
|
+
width: 1,
|
|
171
|
+
height: 1,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
client.axiosInstance.defaults.adapter = (config) => {
|
|
175
|
+
expect(config.url).toBe('https://mobile.example/mobile/v1/execute');
|
|
176
|
+
expect(config.method).toBe('post');
|
|
177
|
+
expect(config.data).toBeInstanceOf(ArrayBuffer);
|
|
178
|
+
const body = new Uint8Array(config.data as ArrayBuffer);
|
|
179
|
+
const text = String.fromCharCode(...body);
|
|
180
|
+
const contentType = config.headers.get('Content-Type');
|
|
181
|
+
if (typeof contentType !== 'string')
|
|
182
|
+
throw new Error('Expected multipart Content-Type');
|
|
183
|
+
expect(contentType).toMatch(/^multipart\/form-data; boundary=/);
|
|
184
|
+
const boundary = contentType.split('boundary=')[1];
|
|
185
|
+
const payloadStart = text.indexOf('\r\n\r\n') + 4;
|
|
186
|
+
const payloadEnd = text.indexOf(`\r\n--${boundary}`, payloadStart);
|
|
187
|
+
const payload = JSON.parse(
|
|
188
|
+
text.slice(payloadStart, payloadEnd),
|
|
189
|
+
) as Record<string, unknown>;
|
|
190
|
+
expect(payload).toMatchObject({
|
|
191
|
+
type,
|
|
192
|
+
content_hash: keccak256(imageBytes),
|
|
193
|
+
});
|
|
194
|
+
expect(payload.signature).toEqual(expect.stringMatching(/^0x[\da-f]+$/));
|
|
195
|
+
const fileHeader = text.indexOf('name="file"; filename="image"');
|
|
196
|
+
expect(fileHeader).toBeGreaterThan(payloadEnd);
|
|
197
|
+
const fileStart = text.indexOf('\r\n\r\n', fileHeader) + 4;
|
|
198
|
+
expect(
|
|
199
|
+
new Uint8Array(body.subarray(fileStart, fileStart + imageBytes.length)),
|
|
200
|
+
).toEqual(imageBytes);
|
|
201
|
+
expect(text.slice(fileStart + imageBytes.length)).toBe(
|
|
202
|
+
`\r\n--${boundary}--\r\n`,
|
|
203
|
+
);
|
|
204
|
+
return Promise.resolve({
|
|
205
|
+
data: { status: 'success', image },
|
|
206
|
+
status: 200,
|
|
207
|
+
statusText: 'OK',
|
|
208
|
+
headers: {},
|
|
209
|
+
config,
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
await expect(
|
|
213
|
+
client[method]({
|
|
214
|
+
subaccountOwner: account.address,
|
|
215
|
+
subaccountName: 'default',
|
|
216
|
+
chainId: ink.id,
|
|
217
|
+
verifyingAddr: '0x0000000000000000000000000000000000000001',
|
|
218
|
+
fileBytes: imageBytes,
|
|
219
|
+
}),
|
|
220
|
+
).resolves.toEqual({
|
|
221
|
+
imageId: image.image_id,
|
|
222
|
+
url: imagePath,
|
|
223
|
+
width: 1,
|
|
224
|
+
height: 1,
|
|
225
|
+
contentType: 'image/jpeg',
|
|
226
|
+
byteSize: imageBytes.length,
|
|
227
|
+
thumbnail: image.thumbnail,
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe.each([
|
|
233
|
+
'createFeedPostImageUpload',
|
|
234
|
+
'createFeedCommentImageUpload',
|
|
235
|
+
] as const)('[mobile-client]: %s request size', (method) => {
|
|
236
|
+
afterEach(() => {
|
|
237
|
+
jest.restoreAllMocks();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it.each(
|
|
241
|
+
[-1, 0, 1].flatMap((offset) =>
|
|
242
|
+
[1n, undefined].map((nonce) => ({ offset, nonce })),
|
|
243
|
+
),
|
|
244
|
+
)(
|
|
245
|
+
'checks the complete body at offset $offset with nonce $nonce',
|
|
246
|
+
async ({ offset, nonce }) => {
|
|
247
|
+
const account = privateKeyToAccount(
|
|
248
|
+
'0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b786907',
|
|
249
|
+
);
|
|
250
|
+
const walletClient = createWalletClient({
|
|
251
|
+
account,
|
|
252
|
+
chain: ink,
|
|
253
|
+
transport: http(),
|
|
254
|
+
});
|
|
255
|
+
const sign = jest.spyOn(walletClient, 'signTypedData');
|
|
256
|
+
const client = new MobileClient({
|
|
257
|
+
url: 'https://mobile.example/mobile/v1',
|
|
258
|
+
walletClient,
|
|
259
|
+
});
|
|
260
|
+
const params = {
|
|
261
|
+
subaccountOwner: account.address,
|
|
262
|
+
subaccountName: 'default',
|
|
263
|
+
chainId: ink.id,
|
|
264
|
+
verifyingAddr: '0x0000000000000000000000000000000000000001',
|
|
265
|
+
nonce,
|
|
266
|
+
} as const;
|
|
267
|
+
const post = jest.spyOn(client.axiosInstance, 'post').mockResolvedValue({
|
|
268
|
+
data: {
|
|
269
|
+
status: 'success',
|
|
270
|
+
image: {
|
|
271
|
+
image_id: 'test',
|
|
272
|
+
url: imagePath,
|
|
273
|
+
width: 1,
|
|
274
|
+
height: 1,
|
|
275
|
+
content_type: 'image/jpeg',
|
|
276
|
+
byte_size: 0,
|
|
277
|
+
thumbnail: { url: imagePath, width: 1, height: 1 },
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
status: 200,
|
|
281
|
+
});
|
|
282
|
+
await client[method]({ ...params, fileBytes: new Uint8Array() });
|
|
283
|
+
const emptyBody: unknown = post.mock.calls[0][1];
|
|
284
|
+
if (!(emptyBody instanceof Uint8Array))
|
|
285
|
+
throw new Error('Expected multipart bytes');
|
|
286
|
+
post.mockClear();
|
|
287
|
+
sign.mockClear();
|
|
288
|
+
const fileBytes = new Uint8Array(
|
|
289
|
+
MOBILE_FEED_IMAGE_MAX_REQUEST_BYTES - emptyBody.byteLength + offset,
|
|
290
|
+
);
|
|
291
|
+
const result = client[method]({ ...params, fileBytes });
|
|
292
|
+
if (offset > 0) {
|
|
293
|
+
await expect(result).rejects.toBeInstanceOf(RangeError);
|
|
294
|
+
await expect(result).rejects.toThrow('2097152 bytes (2 MiB)');
|
|
295
|
+
expect(post).not.toHaveBeenCalled();
|
|
296
|
+
expect(sign).not.toHaveBeenCalled();
|
|
297
|
+
} else {
|
|
298
|
+
await expect(result).resolves.toMatchObject({ imageId: 'test' });
|
|
299
|
+
expect(sign).toHaveBeenCalledTimes(1);
|
|
300
|
+
expect(post).toHaveBeenCalledTimes(1);
|
|
301
|
+
const body: unknown = post.mock.calls[0][1];
|
|
302
|
+
expect(body).toHaveProperty(
|
|
303
|
+
'byteLength',
|
|
304
|
+
MOBILE_FEED_IMAGE_MAX_REQUEST_BYTES + offset,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
);
|
|
309
|
+
});
|