yoto-nodejs-client 0.0.1
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/LICENSE +21 -0
- package/README.md +736 -0
- package/bin/auth.d.ts +3 -0
- package/bin/auth.d.ts.map +1 -0
- package/bin/auth.js +130 -0
- package/bin/content.d.ts +3 -0
- package/bin/content.d.ts.map +1 -0
- package/bin/content.js +117 -0
- package/bin/devices.d.ts +3 -0
- package/bin/devices.d.ts.map +1 -0
- package/bin/devices.js +239 -0
- package/bin/groups.d.ts +3 -0
- package/bin/groups.d.ts.map +1 -0
- package/bin/groups.js +80 -0
- package/bin/icons.d.ts +3 -0
- package/bin/icons.d.ts.map +1 -0
- package/bin/icons.js +100 -0
- package/bin/lib/cli-helpers.d.ts +21 -0
- package/bin/lib/cli-helpers.d.ts.map +1 -0
- package/bin/lib/cli-helpers.js +140 -0
- package/bin/lib/token-helpers.d.ts +14 -0
- package/bin/lib/token-helpers.d.ts.map +1 -0
- package/bin/lib/token-helpers.js +151 -0
- package/bin/refresh-token.d.ts +3 -0
- package/bin/refresh-token.d.ts.map +1 -0
- package/bin/refresh-token.js +168 -0
- package/bin/token-info.d.ts +3 -0
- package/bin/token-info.d.ts.map +1 -0
- package/bin/token-info.js +351 -0
- package/index.d.ts +218 -0
- package/index.d.ts.map +1 -0
- package/index.js +689 -0
- package/lib/api-endpoints/auth.d.ts +56 -0
- package/lib/api-endpoints/auth.d.ts.map +1 -0
- package/lib/api-endpoints/auth.js +209 -0
- package/lib/api-endpoints/auth.test.js +27 -0
- package/lib/api-endpoints/constants.d.ts +6 -0
- package/lib/api-endpoints/constants.d.ts.map +1 -0
- package/lib/api-endpoints/constants.js +31 -0
- package/lib/api-endpoints/content.d.ts +275 -0
- package/lib/api-endpoints/content.d.ts.map +1 -0
- package/lib/api-endpoints/content.js +518 -0
- package/lib/api-endpoints/content.test.js +250 -0
- package/lib/api-endpoints/devices.d.ts +202 -0
- package/lib/api-endpoints/devices.d.ts.map +1 -0
- package/lib/api-endpoints/devices.js +404 -0
- package/lib/api-endpoints/devices.test.js +483 -0
- package/lib/api-endpoints/family-library-groups.d.ts +75 -0
- package/lib/api-endpoints/family-library-groups.d.ts.map +1 -0
- package/lib/api-endpoints/family-library-groups.js +247 -0
- package/lib/api-endpoints/family-library-groups.test.js +272 -0
- package/lib/api-endpoints/family.d.ts +39 -0
- package/lib/api-endpoints/family.d.ts.map +1 -0
- package/lib/api-endpoints/family.js +166 -0
- package/lib/api-endpoints/family.test.js +184 -0
- package/lib/api-endpoints/helpers.d.ts +29 -0
- package/lib/api-endpoints/helpers.d.ts.map +1 -0
- package/lib/api-endpoints/helpers.js +104 -0
- package/lib/api-endpoints/icons.d.ts +62 -0
- package/lib/api-endpoints/icons.d.ts.map +1 -0
- package/lib/api-endpoints/icons.js +201 -0
- package/lib/api-endpoints/icons.test.js +118 -0
- package/lib/api-endpoints/media.d.ts +37 -0
- package/lib/api-endpoints/media.d.ts.map +1 -0
- package/lib/api-endpoints/media.js +155 -0
- package/lib/api-endpoints/test-helpers.d.ts +7 -0
- package/lib/api-endpoints/test-helpers.d.ts.map +1 -0
- package/lib/api-endpoints/test-helpers.js +64 -0
- package/lib/mqtt/client.d.ts +124 -0
- package/lib/mqtt/client.d.ts.map +1 -0
- package/lib/mqtt/client.js +558 -0
- package/lib/mqtt/commands.d.ts +69 -0
- package/lib/mqtt/commands.d.ts.map +1 -0
- package/lib/mqtt/commands.js +238 -0
- package/lib/mqtt/factory.d.ts +12 -0
- package/lib/mqtt/factory.d.ts.map +1 -0
- package/lib/mqtt/factory.js +107 -0
- package/lib/mqtt/index.d.ts +5 -0
- package/lib/mqtt/index.d.ts.map +1 -0
- package/lib/mqtt/index.js +81 -0
- package/lib/mqtt/mqtt.test.js +168 -0
- package/lib/mqtt/topics.d.ts +34 -0
- package/lib/mqtt/topics.d.ts.map +1 -0
- package/lib/mqtt/topics.js +295 -0
- package/lib/pkg.cjs +3 -0
- package/lib/pkg.d.cts +70 -0
- package/lib/pkg.d.cts.map +1 -0
- package/lib/token.d.ts +29 -0
- package/lib/token.d.ts.map +1 -0
- package/lib/token.js +240 -0
- package/package.json +91 -0
- package/yoto.png +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { RequestOptions } from './helpers.js'
|
|
3
|
+
*/
|
|
4
|
+
import { request } from 'undici'
|
|
5
|
+
import { defaultAuthHeaders, handleBadResponse, mergeRequestOptions } from './helpers.js'
|
|
6
|
+
import { YOTO_API_URL } from './constants.js'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Family: Family endpoints for managing family groups and images
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @see https://yoto.dev/api/getfamilyimages/
|
|
14
|
+
* @typedef {Object} YotoFamilyImagesResponse
|
|
15
|
+
* @property {YotoFamilyImage[]} images
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @see https://yoto.dev/api/getfamilyimages/
|
|
20
|
+
* @typedef {Object} YotoFamilyImage
|
|
21
|
+
* @property {string} imageId - The unique identifier for the family image (hash)
|
|
22
|
+
* @property {string} [name] - Optional name of the family image
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves the list of families associated with the authenticated user.
|
|
27
|
+
* @see https://yoto.dev/api/getfamilyimages/
|
|
28
|
+
* @param {object} options
|
|
29
|
+
* @param {string} options.accessToken The API token to request with
|
|
30
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
31
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
32
|
+
* @return {Promise<YotoFamilyImagesResponse>} The user's families
|
|
33
|
+
*/
|
|
34
|
+
export async function getFamilyImages ({
|
|
35
|
+
accessToken,
|
|
36
|
+
userAgent,
|
|
37
|
+
requestOptions
|
|
38
|
+
}) {
|
|
39
|
+
const requestUrl = new URL('/media/family/images', YOTO_API_URL)
|
|
40
|
+
|
|
41
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
42
|
+
method: 'GET',
|
|
43
|
+
headers: defaultAuthHeaders({ accessToken, userAgent })
|
|
44
|
+
}, requestOptions))
|
|
45
|
+
|
|
46
|
+
await handleBadResponse(response)
|
|
47
|
+
|
|
48
|
+
const responseBody = /** @type {YotoFamilyImagesResponse} */ (await response.body.json())
|
|
49
|
+
return responseBody
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @see https://yoto.dev/api/getafamilyimage/
|
|
54
|
+
* @typedef {Object} YotoFamilyImageResponse
|
|
55
|
+
* @property {string} imageUrl - The signed URL to the family image (expires after 7 days)
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Retrieves a signed URL for a specific family image. Returns a 302 redirect with the image URL in the Location header.
|
|
60
|
+
* The signed URL expires after 7 days.
|
|
61
|
+
* @see https://yoto.dev/api/getafamilyimage/
|
|
62
|
+
* @param {object} options
|
|
63
|
+
* @param {string} options.accessToken The API token to request with
|
|
64
|
+
* @param {string} options.imageId The family image ID (hash) to get the image for
|
|
65
|
+
* @param {'640x480' | '320x320'} options.size Image dimensions (supported: '640x480' or '320x320')
|
|
66
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
67
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
68
|
+
* @return {Promise<YotoFamilyImageResponse>} The signed image URL
|
|
69
|
+
*/
|
|
70
|
+
export async function getAFamilyImage ({
|
|
71
|
+
accessToken,
|
|
72
|
+
userAgent,
|
|
73
|
+
imageId,
|
|
74
|
+
size,
|
|
75
|
+
requestOptions
|
|
76
|
+
}) {
|
|
77
|
+
const requestUrl = new URL(`/media/family/images/${imageId}`, YOTO_API_URL)
|
|
78
|
+
|
|
79
|
+
// Map size string to width and height
|
|
80
|
+
const dimensions = size === '640x480' ? { width: 640, height: 480 } : { width: 320, height: 320 }
|
|
81
|
+
|
|
82
|
+
requestUrl.searchParams.set('width', dimensions.width.toString())
|
|
83
|
+
requestUrl.searchParams.set('height', dimensions.height.toString())
|
|
84
|
+
|
|
85
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
86
|
+
method: 'GET',
|
|
87
|
+
headers: defaultAuthHeaders({ accessToken, userAgent })
|
|
88
|
+
}, requestOptions))
|
|
89
|
+
|
|
90
|
+
// 302 is expected for successful image requests
|
|
91
|
+
if (response.statusCode !== 302) {
|
|
92
|
+
await handleBadResponse(response, { imageId })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get the signed URL from the Location header
|
|
96
|
+
const locationHeaders = response.headers['location']
|
|
97
|
+
const imageUrl = Array.isArray(locationHeaders) ? locationHeaders[0] : locationHeaders
|
|
98
|
+
|
|
99
|
+
if (!imageUrl) {
|
|
100
|
+
throw new Error('No Location header found in 302 response')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
imageUrl
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @see https://yoto.dev/api/uploadafamilyimage/
|
|
110
|
+
* @typedef {Object} YotoUploadFamilyImageResponse
|
|
111
|
+
* @property {string} imageId - The SHA256 checksum of the uploaded image
|
|
112
|
+
* @property {string} url - URL to the 'get a family image' endpoint (requires width/height params)
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Uploads a family image for use across various features in Yoto.
|
|
117
|
+
* Images are deduplicated using SHA256 checksums.
|
|
118
|
+
*
|
|
119
|
+
* Constraints:
|
|
120
|
+
* - Max size: 8mb
|
|
121
|
+
* - Supported formats: JPEG, GIF, PNG
|
|
122
|
+
* - Limit: 500 images per family
|
|
123
|
+
* - No restrictions on resolution or aspect ratio
|
|
124
|
+
*
|
|
125
|
+
* @see https://yoto.dev/api/uploadafamilyimage/
|
|
126
|
+
* @param {object} options
|
|
127
|
+
* @param {string} options.accessToken The API token to request with
|
|
128
|
+
* @param {Buffer} options.imageData The binary image data (JPEG, GIF, or PNG)
|
|
129
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
130
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
131
|
+
* @return {Promise<YotoUploadFamilyImageResponse>} The uploaded image details
|
|
132
|
+
* @example
|
|
133
|
+
* import { readFile } from 'fs/promises'
|
|
134
|
+
* import { uploadAFamilyImage } from 'yoto-nodejs-client'
|
|
135
|
+
*
|
|
136
|
+
* const imageData = await readFile('./family-photo.jpg')
|
|
137
|
+
* const result = await uploadAFamilyImage({
|
|
138
|
+
* accessToken,
|
|
139
|
+
* imageData
|
|
140
|
+
* })
|
|
141
|
+
*
|
|
142
|
+
* console.log('Image ID:', result.imageId)
|
|
143
|
+
* console.log('Image URL:', result.url)
|
|
144
|
+
*/
|
|
145
|
+
export async function uploadAFamilyImage ({
|
|
146
|
+
accessToken,
|
|
147
|
+
userAgent,
|
|
148
|
+
imageData,
|
|
149
|
+
requestOptions
|
|
150
|
+
}) {
|
|
151
|
+
const requestUrl = new URL('/media/family/images', YOTO_API_URL)
|
|
152
|
+
|
|
153
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: {
|
|
156
|
+
...defaultAuthHeaders({ accessToken, userAgent }),
|
|
157
|
+
'Content-Type': 'application/octet-stream'
|
|
158
|
+
},
|
|
159
|
+
body: imageData
|
|
160
|
+
}, requestOptions))
|
|
161
|
+
|
|
162
|
+
await handleBadResponse(response)
|
|
163
|
+
|
|
164
|
+
const responseBody = /** @type {YotoUploadFamilyImageResponse} */ (await response.body.json())
|
|
165
|
+
return responseBody
|
|
166
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import { getFamilyImages, getAFamilyImage } from './family.js'
|
|
4
|
+
import { YotoAPIError } from './helpers.js'
|
|
5
|
+
import { loadTestTokens, logResponse } from './test-helpers.js'
|
|
6
|
+
|
|
7
|
+
const { accessToken } = loadTestTokens()
|
|
8
|
+
|
|
9
|
+
test('getFamilyImages', async (t) => {
|
|
10
|
+
await t.test('should fetch user family images', async () => {
|
|
11
|
+
const response = await getFamilyImages({
|
|
12
|
+
accessToken
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Log response for type verification and documentation
|
|
16
|
+
logResponse('GET /media/family/images', response)
|
|
17
|
+
|
|
18
|
+
// Validate response structure matches YotoFamilyImagesResponse
|
|
19
|
+
assert.ok(response, 'Response should exist')
|
|
20
|
+
assert.ok(Array.isArray(response.images), 'Response should have images array')
|
|
21
|
+
|
|
22
|
+
// Note: User may not have any family images, so we only validate structure if images exist
|
|
23
|
+
if (response.images.length > 0) {
|
|
24
|
+
const image = response.images[0]
|
|
25
|
+
assert.ok(image, 'Image should exist')
|
|
26
|
+
assert.ok(typeof image.imageId === 'string', 'Image should have imageId string')
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
await t.test('should fail with invalid token', async () => {
|
|
31
|
+
await assert.rejects(
|
|
32
|
+
async () => {
|
|
33
|
+
await getFamilyImages({
|
|
34
|
+
accessToken: 'invalid-token'
|
|
35
|
+
})
|
|
36
|
+
},
|
|
37
|
+
(err) => {
|
|
38
|
+
assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
|
|
39
|
+
assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
|
|
40
|
+
assert.ok(err.body, 'Error should have body')
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('getAFamilyImage', async (t) => {
|
|
48
|
+
/** @type {string[]} */
|
|
49
|
+
let testImageIds = []
|
|
50
|
+
|
|
51
|
+
// Get real image IDs to test with
|
|
52
|
+
await t.test('setup - get image IDs from family images', async () => {
|
|
53
|
+
const response = await getFamilyImages({
|
|
54
|
+
accessToken
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (response.images.length === 0) {
|
|
58
|
+
console.log('⚠️ User has no family images - getAFamilyImage tests will be skipped')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
testImageIds = response.images.slice(0, 2).map(image => image.imageId).filter(Boolean)
|
|
63
|
+
assert.ok(testImageIds.length > 0, 'Should have extracted at least one image ID')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
await t.test('should fetch family image signed URL', async () => {
|
|
67
|
+
if (testImageIds.length === 0) {
|
|
68
|
+
console.log('⚠️ Skipping - no family images available')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const imageId = testImageIds[0]
|
|
73
|
+
assert.ok(imageId, 'Image ID should exist')
|
|
74
|
+
|
|
75
|
+
const response = await getAFamilyImage({
|
|
76
|
+
accessToken,
|
|
77
|
+
imageId,
|
|
78
|
+
size: '640x480'
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Log response for type verification and documentation
|
|
82
|
+
logResponse('GET /media/family/images/{imageId}', response)
|
|
83
|
+
|
|
84
|
+
// Validate response structure matches YotoFamilyImageResponse
|
|
85
|
+
assert.ok(response, 'Response should exist')
|
|
86
|
+
assert.ok(typeof response.imageUrl === 'string', 'Response should have imageUrl string')
|
|
87
|
+
assert.ok(response.imageUrl.startsWith('http'), 'Image URL should be a valid URL')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
await t.test('should fetch family image with size 640x480', async () => {
|
|
91
|
+
if (testImageIds.length === 0) {
|
|
92
|
+
console.log('⚠️ Skipping - no family images available')
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const imageId = testImageIds[0]
|
|
97
|
+
assert.ok(imageId, 'Image ID should exist')
|
|
98
|
+
|
|
99
|
+
const response = await getAFamilyImage({
|
|
100
|
+
accessToken,
|
|
101
|
+
imageId,
|
|
102
|
+
size: '640x480'
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
assert.ok(response, 'Response should exist')
|
|
106
|
+
assert.ok(typeof response.imageUrl === 'string', 'Response should have imageUrl string')
|
|
107
|
+
assert.ok(response.imageUrl.startsWith('http'), 'Image URL should be a valid URL')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await t.test('should fetch family image with size 320x320', async () => {
|
|
111
|
+
if (testImageIds.length === 0) {
|
|
112
|
+
console.log('⚠️ Skipping - no family images available')
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const imageId = testImageIds[0]
|
|
117
|
+
assert.ok(imageId, 'Image ID should exist')
|
|
118
|
+
|
|
119
|
+
const response = await getAFamilyImage({
|
|
120
|
+
accessToken,
|
|
121
|
+
imageId,
|
|
122
|
+
size: '320x320'
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
assert.ok(response, 'Response should exist')
|
|
126
|
+
assert.ok(typeof response.imageUrl === 'string', 'Response should have imageUrl string')
|
|
127
|
+
assert.ok(response.imageUrl.startsWith('http'), 'Image URL should be a valid URL')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
await t.test('should fail with invalid image ID', async () => {
|
|
131
|
+
await assert.rejects(
|
|
132
|
+
async () => {
|
|
133
|
+
await getAFamilyImage({
|
|
134
|
+
accessToken,
|
|
135
|
+
imageId: 'invalid-image-id-12345',
|
|
136
|
+
size: '640x480'
|
|
137
|
+
})
|
|
138
|
+
},
|
|
139
|
+
(err) => {
|
|
140
|
+
assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
|
|
141
|
+
assert.ok(err.statusCode === 404 || err.statusCode === 400, 'Should return 404 or 400 for invalid image ID')
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
await t.test('should fail with invalid token', async () => {
|
|
148
|
+
if (testImageIds.length === 0) {
|
|
149
|
+
console.log('⚠️ Skipping - no family images available')
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const imageId = testImageIds[0]
|
|
154
|
+
assert.ok(imageId, 'Image ID should exist')
|
|
155
|
+
|
|
156
|
+
await assert.rejects(
|
|
157
|
+
async () => {
|
|
158
|
+
await getAFamilyImage({
|
|
159
|
+
accessToken: 'invalid-token',
|
|
160
|
+
imageId,
|
|
161
|
+
size: '640x480'
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
(err) => {
|
|
165
|
+
assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
|
|
166
|
+
assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
|
|
167
|
+
assert.ok(err.body, 'Error should have body')
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// TODO: Add tests for uploadAFamilyImage
|
|
175
|
+
// - should upload a valid JPEG image
|
|
176
|
+
// - should upload a valid PNG image
|
|
177
|
+
// - should upload a valid GIF image
|
|
178
|
+
// - should return imageId (SHA256 checksum) and url
|
|
179
|
+
// - should deduplicate identical images (same SHA256)
|
|
180
|
+
// - should fail with file larger than 8mb
|
|
181
|
+
// - should fail with unsupported image format
|
|
182
|
+
// - should fail when family has reached 500 image limit
|
|
183
|
+
// - should fail with invalid token
|
|
184
|
+
// - should validate response structure matches YotoUploadFamilyImageResponse
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function defaultHeaders(options?: {
|
|
2
|
+
userAgent?: string | undefined;
|
|
3
|
+
requestOptions?: ({
|
|
4
|
+
dispatcher?: Dispatcher;
|
|
5
|
+
} & Omit<Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
6
|
+
}): {
|
|
7
|
+
Accept: string;
|
|
8
|
+
'User-Agent': string;
|
|
9
|
+
};
|
|
10
|
+
export function defaultAuthHeaders({ accessToken: token, userAgent }: {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
userAgent?: string | undefined;
|
|
13
|
+
}): {
|
|
14
|
+
Authorization: string;
|
|
15
|
+
Accept: string;
|
|
16
|
+
'User-Agent': string;
|
|
17
|
+
};
|
|
18
|
+
export function mergeRequestOptions(baseOptions: RequestOptions, requestOptions?: RequestOptions): object;
|
|
19
|
+
export function handleBadResponse(response: Dispatcher.ResponseData, extra?: any): Promise<void>;
|
|
20
|
+
export class YotoAPIError extends Error {
|
|
21
|
+
constructor(response: Dispatcher.ResponseData, body: string | object, extra?: any);
|
|
22
|
+
statusCode: number;
|
|
23
|
+
body: string | object;
|
|
24
|
+
extra: any;
|
|
25
|
+
}
|
|
26
|
+
export type RequestOptions = NonNullable<Parameters<typeof request>[1]>;
|
|
27
|
+
import type { Dispatcher } from 'undici';
|
|
28
|
+
import type { request } from 'undici';
|
|
29
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["helpers.js"],"names":[],"mappings":"AAiBA,yCAHG;IAAyB,SAAS;IACD,cAAc;;;CACjD;;;EAWA;AAOD,sEAHG;IAAwB,WAAW,EAA1B,MAAM;IACS,SAAS;CACnC;;;;EAMA;AAUD,iDAJW,cAAc,mBACd,cAAc,GACZ,MAAM,CAoBlB;AAMD,4CAHY,uBAAuB,UACvB,GAAG,iBAWd;AAED;IAUE,sBAJY,uBAAuB,QACvB,MAAM,GAAG,MAAM,UACf,GAAG,EAUd;IAjBuB,YAAZ,MAAM,CAAgB;IACF,MAArB,MAAM,GAAG,MAAM,CAAU;IACjB,OAAR,GAAG,CAAU;CAgBzB;6BA9FY,WAAW,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;gCARxB,QAAQ;6BACX,QAAQ"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { Dispatcher } from 'undici'
|
|
3
|
+
* @import { request } from 'undici'
|
|
4
|
+
*/
|
|
5
|
+
import { pkg } from '../pkg.cjs'
|
|
6
|
+
import os from 'node:os'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Request options derived from undici.request
|
|
10
|
+
* @typedef {NonNullable<Parameters<typeof request>[1]>} RequestOptions
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} [options]
|
|
15
|
+
* @param {string} [options.userAgent] - Optional user agent string to prepend to library user agent
|
|
16
|
+
* @param {RequestOptions} [options.requestOptions] - Additional undici request options
|
|
17
|
+
*/
|
|
18
|
+
export function defaultHeaders (options = {}) {
|
|
19
|
+
const libraryAgent = `${pkg.name}/${pkg.version} (${os.type()})`
|
|
20
|
+
const userAgent = options.userAgent
|
|
21
|
+
? `${options.userAgent} ${libraryAgent}`
|
|
22
|
+
: libraryAgent
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
Accept: 'application/json',
|
|
26
|
+
'User-Agent': userAgent
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {object} params
|
|
32
|
+
* @param {string} params.accessToken
|
|
33
|
+
* @param {string} [params.userAgent] - Optional user agent string to prepend to library user agent
|
|
34
|
+
*/
|
|
35
|
+
export function defaultAuthHeaders ({ accessToken: token, userAgent }) {
|
|
36
|
+
return {
|
|
37
|
+
...defaultHeaders({ userAgent }),
|
|
38
|
+
Authorization: `Bearer ${token}`,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Merge undici request options with defaults
|
|
44
|
+
* Properly merges headers by supplementing rather than overriding
|
|
45
|
+
* Only supports object headers (not arrays)
|
|
46
|
+
* @param {RequestOptions} baseOptions - Base request options with headers
|
|
47
|
+
* @param {RequestOptions} [requestOptions] - Additional request options to merge
|
|
48
|
+
* @returns {object} Merged options
|
|
49
|
+
*/
|
|
50
|
+
export function mergeRequestOptions (baseOptions, requestOptions) {
|
|
51
|
+
if (!requestOptions) return baseOptions
|
|
52
|
+
|
|
53
|
+
// Extract headers from both options
|
|
54
|
+
const { headers: baseHeaders, ...baseRest } = baseOptions
|
|
55
|
+
const { headers: requestHeaders, ...requestRest } = requestOptions
|
|
56
|
+
|
|
57
|
+
// Merge headers - only support object headers
|
|
58
|
+
const mergedHeaders = {
|
|
59
|
+
...(baseHeaders || {}),
|
|
60
|
+
...(requestHeaders || {})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...baseRest,
|
|
65
|
+
...requestRest,
|
|
66
|
+
headers: mergedHeaders
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Dispatcher.ResponseData} response
|
|
72
|
+
* @param {any} [extra]
|
|
73
|
+
*/
|
|
74
|
+
export async function handleBadResponse (response, extra) {
|
|
75
|
+
if (response.statusCode > 299) {
|
|
76
|
+
const contentTypeHeaders = response.headers['Content-Type']
|
|
77
|
+
const contentType = Array.isArray(contentTypeHeaders) ? contentTypeHeaders[0] : contentTypeHeaders
|
|
78
|
+
const isJSON = contentType && contentType.match(/json/)
|
|
79
|
+
/** @type { any } */
|
|
80
|
+
const body = isJSON ? await response.body.json() : await response.body.text()
|
|
81
|
+
throw new YotoAPIError(response, body, extra)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class YotoAPIError extends Error {
|
|
86
|
+
/** @type { number } */ statusCode
|
|
87
|
+
/** @type {string | object } */ body
|
|
88
|
+
/** @type {any} */ extra
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {Dispatcher.ResponseData} response A undici Response
|
|
92
|
+
* @param {string | object} body response body
|
|
93
|
+
* @param {any} [extra] any extra info to attach to the error
|
|
94
|
+
*/
|
|
95
|
+
constructor (response, body, extra) {
|
|
96
|
+
super('Unexpected response status code')
|
|
97
|
+
this.name = this.constructor.name
|
|
98
|
+
Error.captureStackTrace(this, this.constructor)
|
|
99
|
+
|
|
100
|
+
this.statusCode = response.statusCode
|
|
101
|
+
this.body = body
|
|
102
|
+
this.extra = extra
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function getPublicIcons({ accessToken, userAgent, requestOptions }: {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
userAgent?: string | undefined;
|
|
4
|
+
requestOptions?: ({
|
|
5
|
+
dispatcher?: import("undici").Dispatcher;
|
|
6
|
+
} & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
7
|
+
}): Promise<YotoPublicIconsResponse>;
|
|
8
|
+
export function getUserIcons({ accessToken, userAgent, requestOptions }: {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
userAgent?: string | undefined;
|
|
11
|
+
requestOptions?: ({
|
|
12
|
+
dispatcher?: import("undici").Dispatcher;
|
|
13
|
+
} & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
14
|
+
}): Promise<YotoUserIconsResponse>;
|
|
15
|
+
export function uploadIcon({ accessToken, userAgent, imageData, autoConvert, filename, requestOptions }: {
|
|
16
|
+
accessToken: string;
|
|
17
|
+
imageData: Buffer;
|
|
18
|
+
autoConvert?: boolean | undefined;
|
|
19
|
+
filename?: string | undefined;
|
|
20
|
+
userAgent?: string | undefined;
|
|
21
|
+
requestOptions?: ({
|
|
22
|
+
dispatcher?: import("undici").Dispatcher;
|
|
23
|
+
} & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
24
|
+
}): Promise<YotoUploadIconResponse>;
|
|
25
|
+
export type YotoPublicIconsResponse = {
|
|
26
|
+
displayIcons: YotoPublicIcon[];
|
|
27
|
+
};
|
|
28
|
+
export type YotoPublicIcon = {
|
|
29
|
+
displayIconId: string;
|
|
30
|
+
mediaId: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
title: string;
|
|
34
|
+
url: string;
|
|
35
|
+
public: boolean;
|
|
36
|
+
new?: boolean;
|
|
37
|
+
publicTags: string[];
|
|
38
|
+
};
|
|
39
|
+
export type YotoUserIconsResponse = {
|
|
40
|
+
displayIcons: YotoUserIcon[];
|
|
41
|
+
};
|
|
42
|
+
export type YotoUserIcon = {
|
|
43
|
+
displayIconId: string;
|
|
44
|
+
mediaId: string;
|
|
45
|
+
userId: string;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
url: string;
|
|
48
|
+
public: boolean;
|
|
49
|
+
};
|
|
50
|
+
export type YotoUploadIconResponse = {
|
|
51
|
+
displayIcon: YotoDisplayIcon;
|
|
52
|
+
};
|
|
53
|
+
export type YotoDisplayIcon = {
|
|
54
|
+
displayIconId: string;
|
|
55
|
+
mediaId: string;
|
|
56
|
+
userId: string;
|
|
57
|
+
url: string | object;
|
|
58
|
+
new?: boolean;
|
|
59
|
+
_id?: string;
|
|
60
|
+
createdAt?: string;
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["icons.js"],"names":[],"mappings":"AAmDA,2EAhBG;IAAyB,WAAW,EAA3B,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,uBAAuB,CAAC,CA6B3C;AA4BD,yEALG;IAAyB,WAAW,EAA3B,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,qBAAqB,CAAC,CAkBzC;AA+DD,yGAzBG;IAAyB,WAAW,EAA3B,MAAM;IACU,SAAS,EAAzB,MAAM;IACY,WAAW;IACZ,QAAQ;IACR,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,sBAAsB,CAAC,CA6C1C;;kBA1La,cAAc,EAAE;;;mBAMhB,MAAM;aACN,MAAM;YACN,MAAM;eACN,MAAM;WACN,MAAM;SACN,MAAM;YACN,OAAO;UACP,OAAO;gBACP,MAAM,EAAE;;;kBA4CR,YAAY,EAAE;;;mBAMd,MAAM;aACN,MAAM;YACN,MAAM;eACN,MAAM;SACN,MAAM;YACN,OAAO;;;iBAiCP,eAAe;;;mBAMf,MAAM;aACN,MAAM;YACN,MAAM;SACN,MAAM,GAAG,MAAM;UACf,OAAO;UACP,MAAM;gBACN,MAAM"}
|