kicksdk-js 0.1.1 → 0.3.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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +125 -1
- package/index.d.ts +129 -11
- package/index.js +422 -98
- package/package.json +6 -3
- package/pkg/kicksdk.d.ts +735 -97
- package/pkg/kicksdk.js +2390 -580
- package/pkg/kicksdk_bg.wasm +0 -0
- package/pkg/kicksdk_bg.wasm.d.ts +107 -57
package/LICENSE-APACHE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/LICENSE-MIT
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anis Abdellatif
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -19,10 +19,40 @@ const chat = kick.connectChat({
|
|
|
19
19
|
if (event.type === 'chat_message') console.log(`${event.sender.username}: ${event.content}`);
|
|
20
20
|
},
|
|
21
21
|
onState: ({ connected, subscribed }) => {},
|
|
22
|
+
onDrop: (reason, { code, terminal, waitMs }) => {
|
|
23
|
+
if (terminal) console.warn(`Kick refused this client (${reason}); back in ${waitMs / 60_000} min`);
|
|
24
|
+
},
|
|
22
25
|
});
|
|
23
26
|
chat.close();
|
|
24
27
|
```
|
|
25
28
|
|
|
29
|
+
`connectChat` keeps the chat up until `close()`. Whenever a connection ends
|
|
30
|
+
(Kick went quiet, the socket closed, a request or the handshake took longer
|
|
31
|
+
than `timeoutMs`, 10 s by default), `onDrop` says why and how long it waits
|
|
32
|
+
before connecting again (`waitMs`): a jittered backoff from 0.5 s to 30 s.
|
|
33
|
+
After one of Kick's terminal closes (a refused token, a ban, a connection
|
|
34
|
+
limit: `terminal: true`) it never gives up, but waits long - 5, 10, 20, then
|
|
35
|
+
30 minutes for terminal closes in a row, each plus up to a quarter - and
|
|
36
|
+
comes back as a new guest client (a new client id, so a new token). The chat
|
|
37
|
+
feed subscribing again ends the streak. `docs/KICK.md` §6.1 has the rules.
|
|
38
|
+
A callback that throws is reported to `onError` (or `console.error`) without
|
|
39
|
+
disturbing the connection; none fires after `close()`, and what throws after
|
|
40
|
+
it goes to `console.error`. A panic in the WebAssembly is reported too, and
|
|
41
|
+
ends that connection; the next one starts on a new session.
|
|
42
|
+
|
|
43
|
+
A mistake in the options throws from `connectChat` at once: a bad id or
|
|
44
|
+
endpoint, a `timeoutMs` that is not a number of ms above 0 and at most
|
|
45
|
+
2147483647 (setTimeout's limit), an option that should be a function and is
|
|
46
|
+
not. The backoff resets when the first of `channels` (the chat feed, by
|
|
47
|
+
default) subscribes, or, with `channels: []`, when Kick accepts the
|
|
48
|
+
connection. A `now` clock that returns anything but a number of ms from 0
|
|
49
|
+
to `Number.MAX_SAFE_INTEGER` is reported to `onError` once, and
|
|
50
|
+
`performance.now()` takes over.
|
|
51
|
+
|
|
52
|
+
Node 22 and later have a `WebSocket`. On older Node, pass one:
|
|
53
|
+
`connectChat({ ..., WebSocket })` with the `ws` package's class. Without
|
|
54
|
+
one, `connectChat` throws.
|
|
55
|
+
|
|
26
56
|
**Where the feed works.** Kick's realtime server only accepts a WebSocket whose
|
|
27
57
|
Origin is `https://kick.com`. Node: works (the driver sets the header). Browser:
|
|
28
58
|
only on kick.com - a page there or an extension's content script. Elsewhere,
|
|
@@ -31,7 +61,101 @@ relay through a server.
|
|
|
31
61
|
Everything else - `channelRequest` / `parseChannel`, `historyRequest`,
|
|
32
62
|
`livestreamsRequests`, `verifyWebhook`, `parseEvent`, `parseContent`, … -
|
|
33
63
|
returns plain request objects for your `fetch` and parses what comes back.
|
|
34
|
-
Parsers throw an `Error` with a `status` when Kick answered with an error
|
|
64
|
+
Parsers throw an `Error` with a `status` when Kick answered with an error,
|
|
65
|
+
and an `excerpt`: the start of Kick's answer, which often says why
|
|
66
|
+
(`CHANNEL_IS_NOT_LIVE`). The message ends with it too.
|
|
67
|
+
Every number must be a JS number - a numeric string or a boolean throws a
|
|
68
|
+
`TypeError` rather than being converted - and ids, statuses, times and
|
|
69
|
+
counts whole ones in range (ids at most `Number.MAX_SAFE_INTEGER`), or a
|
|
70
|
+
`RangeError` is thrown rather than the number being rounded into another
|
|
71
|
+
id. The same holds for results: one that would carry an integer above
|
|
72
|
+
`Number.MAX_SAFE_INTEGER` (an id Kick sent that large) throws rather than
|
|
73
|
+
coming back rounded.
|
|
74
|
+
|
|
75
|
+
**User tokens.** To act as a Kick user (send chat, moderate, edit their
|
|
76
|
+
channel, manage rewards), get a user token with OAuth's authorization code
|
|
77
|
+
flow and PKCE. The core draws no random numbers: `newPkce()` and `newState()`
|
|
78
|
+
take them from `crypto.getRandomValues`.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const pkce = kick.newPkce(); // { verifier, challenge }
|
|
82
|
+
const state = kick.newState();
|
|
83
|
+
// keep pkce.verifier and state (a session) until the user comes back
|
|
84
|
+
location = kick.oauthAuthorizeUrl(clientId, redirectUri, ['user:read', 'chat:write'], state, pkce.verifier);
|
|
85
|
+
|
|
86
|
+
// on the redirect: check ?state= is yours, then
|
|
87
|
+
const token = kick.oauthParseUserToken(...await call(
|
|
88
|
+
kick.oauthCodeRequest(clientId, clientSecret, redirectUri, code, pkce.verifier)));
|
|
89
|
+
// later: oauthRefreshRequest(clientId, clientSecret, token.refresh_token) -> a new pair
|
|
90
|
+
```
|
|
91
|
+
`oauthRevokeRequest(token, 'access_token' | 'refresh_token')` /
|
|
92
|
+
`oauthParseRevoke`, `oauthIntrospectRequest(token)` / `oauthParseIntrospect`
|
|
93
|
+
(`{active, client_id, exp, scopes, token_type}`), and `oauthScopes()`, the 13
|
|
94
|
+
scopes Kick documents, complete the set; `oauthPkce(bytes)` and
|
|
95
|
+
`oauthPkceFromVerifier(verifier)` make a pair from your own 32 to 96 random
|
|
96
|
+
bytes or a kept verifier. **The verifier, the client secret and both tokens
|
|
97
|
+
are secrets: never log them**, nor a request carrying them
|
|
98
|
+
(`oauthRevokeRequest` puts the token in its URL).
|
|
99
|
+
|
|
100
|
+
With the token (`call` being your `fetch` that returns `[status, text]`):
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const sent = kick.chatParseSend(...await call(kick.chatSendRequest(token, { user: broadcasterUserId }, 'hi')));
|
|
104
|
+
kick.parseDone(...await call(kick.moderationTimeoutRequest(token, broadcasterUserId, userId, 10, 'spam')));
|
|
105
|
+
```
|
|
106
|
+
- chat: `chatSendRequest(token, {user} | 'bot', content, replyToMessageId?)` /
|
|
107
|
+
`chatParseSend`, `chatDeleteRequest(token, messageId)`;
|
|
108
|
+
- moderation: `moderationBanRequest(token, broadcasterUserId, userId, reason?)`,
|
|
109
|
+
`moderationTimeoutRequest(…, minutes, reason?)`, `moderationUnbanRequest`;
|
|
110
|
+
- the token's channel: `channelsUpdateRequest(token, categoryId?, customTags?, streamTitle?)`;
|
|
111
|
+
- rewards: `rewardsListRequest` / `rewardsParseRewards`,
|
|
112
|
+
`rewardsCreateRequest(token, fields)` and `rewardsUpdateRequest(token, id, fields)` /
|
|
113
|
+
`rewardsParseReward` (`fields` as Kick names them: `{title, cost, is_enabled, …}`),
|
|
114
|
+
`rewardsDeleteRequest`, `rewardsRedemptionsRequest(token, rewardId?, status?, cursor?)`
|
|
115
|
+
and `rewardsRedemptionsByIdsRequest(token, ids, cursor?)` / `rewardsParseRedemptions`,
|
|
116
|
+
`rewardsAcceptRequest` and `rewardsRejectRequest(token, ids)` / `rewardsParseFailures`;
|
|
117
|
+
- ads: `adsAdBreakRequest(token, crypto.randomUUID(), seconds)` / `adsParseAdBreak`,
|
|
118
|
+
`adsStatusRequest` / `adsParseStatus`, `adsEnrollRequest`;
|
|
119
|
+
- drops (an app token): `dropsClaimsRequest(token, {campaign_id, user_id, limit, cursor, …})` /
|
|
120
|
+
`dropsParseClaims`, `dropsUpdateClaimsRequest(token, [{claim_id, external_status}])`;
|
|
121
|
+
- reads, an app or a user token: `usersRequest(token, userIds?)` / `parseUsers`,
|
|
122
|
+
`categoriesRequest(token, names?, tags?, ids?, limit?, cursor?)` / `parseCategories`,
|
|
123
|
+
`livestreamStatsRequest` / `parseLivestreamStats`, `kicksLeaderboardRequest(token, top?)` /
|
|
124
|
+
`parseKicksLeaderboard`.
|
|
125
|
+
|
|
126
|
+
Calls whose answer carries nothing are read with `parseDone(status, body)`,
|
|
127
|
+
which throws with the `status` unless it is a 2xx. Option objects (`fields`,
|
|
128
|
+
the drops filter, the claim updates, `{user}`) refuse a key they do not know.
|
|
129
|
+
`index.d.ts` types every result (`UserToken`, `Reward`, `Page<Category>`, …).
|
|
130
|
+
|
|
131
|
+
**Webhooks.** Read Kick's key once and check each delivery against the raw
|
|
132
|
+
body (text or bytes), its headers (pairs, a fetch `Headers`, or an object),
|
|
133
|
+
and the clock: the timestamp must be within 5 minutes of `now` (Unix
|
|
134
|
+
seconds; this machine's clock by default).
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
const key = new kick.WebhookPublicKey(pem); // from publicKeyRequest / parsePublicKey
|
|
138
|
+
const { headers, event } = key.verify(req.headers, rawBody);
|
|
139
|
+
// or: kick.verifyWebhook(pem, headers, rawBody, { now, toleranceSecs })
|
|
140
|
+
// a stored delivery, checked again later: { skipFreshness: true }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A valid signature means Kick sent this body to *some* app (every app shares
|
|
144
|
+
the key): deduplicate on `headers.message_id`. See `docs/KICK.md` §4.3.
|
|
35
145
|
|
|
36
146
|
See the repository's `docs/KICK.md` for everything known about Kick, with
|
|
37
147
|
sources. MIT OR Apache-2.0. Not affiliated with Kick.
|
|
148
|
+
|
|
149
|
+
## Live API coverage
|
|
150
|
+
|
|
151
|
+
The shared core was checked against Kick on 2026-11-15: OAuth code exchange
|
|
152
|
+
with PKCE, refresh/revocation/introspection, app and user reads, chat
|
|
153
|
+
send/reply/delete, timeout/ban/unban and reward create/update/delete.
|
|
154
|
+
Bot send returned 404; channel editing was skipped because no original title
|
|
155
|
+
was available to restore. Ad breaks, enrollment, drops and redemption
|
|
156
|
+
accept/reject remain documentation-based. See the repository `docs/KICK.md`
|
|
157
|
+
and `fixtures/{public_api,id}/README.md` for evidence and limits.
|
|
158
|
+
|
|
159
|
+
Check granted OAuth scopes before protected calls: Kick can grant fewer than
|
|
160
|
+
requested. Never log tokens, client secrets, authorization codes, PKCE
|
|
161
|
+
verifiers or requests carrying them.
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
export * from './pkg/kicksdk.js';
|
|
2
|
+
import type { Endpoints, Pkce } from './pkg/kicksdk.js';
|
|
2
3
|
|
|
3
4
|
/** The Origin Kick's realtime server accepts. */
|
|
4
5
|
export const REQUIRED_ORIGIN: 'https://kick.com';
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A new PKCE pair from 32 bytes of `crypto.getRandomValues`, for
|
|
9
|
+
* `oauthAuthorizeUrl` and `oauthCodeRequest`. The verifier is a secret: keep
|
|
10
|
+
* it until the user comes back, never log it.
|
|
11
|
+
*/
|
|
12
|
+
export function newPkce(): Pkce;
|
|
13
|
+
|
|
14
|
+
/** A new OAuth `state`: 32 bytes of `crypto.getRandomValues`, base64url without padding. */
|
|
15
|
+
export function newState(): string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Loads the WebAssembly: a URL, a Response (or a promise of one), the .wasm
|
|
19
|
+
* bytes or a compiled module. Safe to call more than once; after a failure,
|
|
20
|
+
* the next call tries again.
|
|
21
|
+
*/
|
|
22
|
+
export function init(
|
|
23
|
+
input?: string | URL | Request | Response | Promise<Response> | BufferSource | WebAssembly.Module
|
|
24
|
+
): Promise<void>;
|
|
8
25
|
|
|
9
26
|
export interface ChatEvent {
|
|
10
27
|
/** The feed it came on, e.g. `chatrooms.668.v2`. */
|
|
@@ -17,18 +34,119 @@ export interface ChatEvent {
|
|
|
17
34
|
event: { type: string; [key: string]: unknown };
|
|
18
35
|
}
|
|
19
36
|
|
|
37
|
+
/** What connectChat knows of the connection. */
|
|
38
|
+
export interface ChatState {
|
|
39
|
+
/** Kick accepted the connection. */
|
|
40
|
+
connected: boolean;
|
|
41
|
+
/** The feeds subscribed now. */
|
|
42
|
+
subscribed: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Why a connection, or an attempt at one, ended, and when the next one starts. */
|
|
46
|
+
export interface ChatDrop {
|
|
47
|
+
/**
|
|
48
|
+
* Kick's disconnect or error code, the socket's close code (1006 for a
|
|
49
|
+
* network drop or a refused handshake), or 0 (a timeout, a failed request,
|
|
50
|
+
* silence).
|
|
51
|
+
*/
|
|
52
|
+
code: number;
|
|
53
|
+
/**
|
|
54
|
+
* Kick will not take this client back as it is (a refused token, a ban, a
|
|
55
|
+
* connection limit: codes 3500-3999 and 4500-4999, among others). Not the
|
|
56
|
+
* end: connectChat waits longer (`waitMs`, 5 minutes or more) and comes
|
|
57
|
+
* back as a new guest client. Worth a warning.
|
|
58
|
+
*/
|
|
59
|
+
terminal: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* How long connectChat waits before connecting again, ms: the ordinary
|
|
62
|
+
* backoff (0.5 s growing to 30 s, jittered), or after a terminal close 5,
|
|
63
|
+
* 10, 20, then 30 minutes in a row, plus up to a quarter.
|
|
64
|
+
*/
|
|
65
|
+
waitMs: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The part of a WebSocket connectChat uses: the browser's, Node's (22 and
|
|
70
|
+
* later), and the `ws` package's all fit.
|
|
71
|
+
*/
|
|
72
|
+
export interface ChatSocket {
|
|
73
|
+
readonly readyState: number;
|
|
74
|
+
send(data: string): void;
|
|
75
|
+
close(): void;
|
|
76
|
+
onopen: ((ev: any) => any) | null;
|
|
77
|
+
onmessage: ((ev: any) => any) | null;
|
|
78
|
+
onclose: ((ev: any) => any) | null;
|
|
79
|
+
onerror: ((ev: any) => any) | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A WebSocket class. In Node it is given `{ headers: { Origin } }` as its
|
|
84
|
+
* second argument, as Node's own and the `ws` package's take it.
|
|
85
|
+
*/
|
|
86
|
+
export type ChatSocketConstructor = new (url: string, options?: any) => ChatSocket;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* connectChat's options. Every optional one may be left out or given as
|
|
90
|
+
* undefined (as a `Partial<>` or optional config carries it), with the same
|
|
91
|
+
* meaning.
|
|
92
|
+
*/
|
|
20
93
|
export interface ChatOptions {
|
|
94
|
+
/** The chatroom (website API `chatroom.id`). */
|
|
21
95
|
chatroomId: number;
|
|
22
|
-
|
|
23
|
-
|
|
96
|
+
/** The channel; also locates the chat's region. */
|
|
97
|
+
channelId?: number | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The feeds to subscribe to; `chatChannels(chatroomId, channelId)` by
|
|
100
|
+
* default. The first is taken for the chat feed: its subscribe resets the
|
|
101
|
+
* reconnect backoff (with none listed, Kick accepting the connection does).
|
|
102
|
+
* A feed listed twice is subscribed and reported once.
|
|
103
|
+
*/
|
|
104
|
+
channels?: readonly string[] | undefined;
|
|
105
|
+
/** An event on a feed. */
|
|
24
106
|
onEvent(e: ChatEvent): void;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
107
|
+
/** The connection or its subscriptions changed. */
|
|
108
|
+
onState?: ((s: ChatState) => void) | undefined;
|
|
109
|
+
/** A connection, or an attempt at one, ended; another follows after `drop.waitMs`. */
|
|
110
|
+
onDrop?: ((reason: string, drop: ChatDrop) => void) | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* A callback above threw, or `now` gave a reading it cannot use. Default:
|
|
113
|
+
* console.error. The connection carries on either way. Not called after
|
|
114
|
+
* `close()`: what throws then goes to console.error.
|
|
115
|
+
*/
|
|
116
|
+
onError?: ((error: unknown) => void) | undefined;
|
|
117
|
+
/** The fetch to make Kick's requests with. Default: the global fetch. */
|
|
118
|
+
fetch?: typeof fetch | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* The WebSocket class, where the runtime has none (Node before 22: pass the
|
|
121
|
+
* `ws` package's). Default: the global WebSocket.
|
|
122
|
+
*/
|
|
123
|
+
WebSocket?: ChatSocketConstructor | undefined;
|
|
124
|
+
/** Opens the socket yourself, instead of `WebSocket`. */
|
|
125
|
+
createSocket?: ((url: string) => ChatSocket) | undefined;
|
|
126
|
+
/** Kick's URLs to use instead; a key left out or undefined stays Kick's. */
|
|
127
|
+
endpoints?: Endpoints | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* The session's clock, ms, only ever going forward. Default:
|
|
130
|
+
* `performance.now()`. A reading that is not a number from 0 to
|
|
131
|
+
* `Number.MAX_SAFE_INTEGER` (or a throw) is reported to `onError` once,
|
|
132
|
+
* and connectChat carries on with `performance.now()`.
|
|
133
|
+
*/
|
|
134
|
+
now?: (() => number) | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* How long a request, or the WebSocket handshake, may take, ms: above 0
|
|
137
|
+
* and at most 2147483647 (setTimeout's limit; there is no "no timeout").
|
|
138
|
+
* Default 10000. Out of range, or not a number: connectChat throws a
|
|
139
|
+
* RangeError.
|
|
140
|
+
*/
|
|
141
|
+
timeoutMs?: number | undefined;
|
|
31
142
|
}
|
|
32
143
|
|
|
33
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Connects to one channel's chat and keeps it up until `close()`: whatever
|
|
146
|
+
* ends a connection, `onDrop` says why and how long connectChat waits before
|
|
147
|
+
* the next (long after a `terminal` one). No callback fires after `close()`,
|
|
148
|
+
* which may be called more than once, and from inside a callback. Throws at
|
|
149
|
+
* once on a mistake in the options, or when the runtime has no WebSocket and
|
|
150
|
+
* none is given.
|
|
151
|
+
*/
|
|
34
152
|
export function connectChat(options: ChatOptions): { close(): void };
|