@overlaysymphony/twitch 0.1.0 → 0.2.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 +1 -1
- package/package.json +21 -22
- package/src/authentication/authentication.ts +85 -55
- package/src/chat/chat.ts +9 -8
- package/src/chat/interfaces/events.ts +29 -29
- package/src/chat/interfaces/index.ts +1 -1
- package/src/chat/parser.ts +9 -20
- package/src/eventsub/events/channel.ad_break.begin.ts +1 -1
- package/src/eventsub/events/channel.ban.ts +1 -1
- package/src/eventsub/events/channel.channel_points_custom_reward.add.ts +2 -2
- package/src/eventsub/events/channel.channel_points_custom_reward.remove.ts +2 -2
- package/src/eventsub/events/channel.channel_points_custom_reward.update.ts +2 -2
- package/src/eventsub/events/channel.channel_points_custom_reward_redemption.add.ts +2 -2
- package/src/eventsub/events/channel.channel_points_custom_reward_redemption.update.ts +2 -2
- package/src/eventsub/events/channel.charity_campaign.donate.ts +1 -1
- package/src/eventsub/events/channel.charity_campaign.progress.ts +1 -1
- package/src/eventsub/events/channel.charity_campaign.start.ts +1 -1
- package/src/eventsub/events/channel.charity_campaign.stop.ts +1 -1
- package/src/eventsub/events/channel.chat.clear.ts +1 -1
- package/src/eventsub/events/channel.chat.clear_user_messages.ts +1 -1
- package/src/eventsub/events/channel.chat.message_delete.ts +1 -1
- package/src/eventsub/events/channel.chat.notification.ts +1 -1
- package/src/eventsub/events/channel.cheer.ts +1 -1
- package/src/eventsub/events/channel.follow.ts +1 -1
- package/src/eventsub/events/channel.goal.begin.ts +1 -1
- package/src/eventsub/events/channel.goal.end.ts +1 -1
- package/src/eventsub/events/channel.goal.progress.ts +1 -1
- package/src/eventsub/events/channel.guest_star_guest.update.ts +1 -1
- package/src/eventsub/events/channel.guest_star_session.begin.ts +1 -1
- package/src/eventsub/events/channel.guest_star_session.end.ts +1 -1
- package/src/eventsub/events/channel.guest_star_settings.update.ts +1 -1
- package/src/eventsub/events/channel.hype_train.begin.ts +2 -2
- package/src/eventsub/events/channel.hype_train.end.ts +2 -2
- package/src/eventsub/events/channel.hype_train.progress.ts +2 -2
- package/src/eventsub/events/channel.moderator.add.ts +1 -1
- package/src/eventsub/events/channel.moderator.remove.ts +1 -1
- package/src/eventsub/events/channel.poll.begin.ts +2 -2
- package/src/eventsub/events/channel.poll.end.ts +2 -2
- package/src/eventsub/events/channel.poll.progress.ts +2 -2
- package/src/eventsub/events/channel.prediction.begin.ts +2 -2
- package/src/eventsub/events/channel.prediction.end.ts +2 -2
- package/src/eventsub/events/channel.prediction.lock.ts +2 -2
- package/src/eventsub/events/channel.prediction.progress.ts +2 -2
- package/src/eventsub/events/channel.raid.ts +1 -1
- package/src/eventsub/events/channel.shield_mode.begin.ts +1 -1
- package/src/eventsub/events/channel.shield_mode.end.ts +1 -1
- package/src/eventsub/events/channel.shoutout.create.ts +1 -1
- package/src/eventsub/events/channel.shoutout.receive.ts +1 -1
- package/src/eventsub/events/channel.subscribe.ts +1 -1
- package/src/eventsub/events/channel.subscription.end.ts +1 -1
- package/src/eventsub/events/channel.subscription.gift.ts +1 -1
- package/src/eventsub/events/channel.subscription.message.ts +1 -1
- package/src/eventsub/events/channel.unban.ts +1 -1
- package/src/eventsub/events/channel.update.ts +1 -1
- package/src/eventsub/events/index.ts +52 -51
- package/src/eventsub/events/stream.offline.ts +1 -1
- package/src/eventsub/events/stream.online.ts +1 -1
- package/src/eventsub/events/user.update.ts +1 -1
- package/src/eventsub/events-helpers.ts +2 -2
- package/src/eventsub/eventsub.ts +9 -9
- package/src/eventsub/messages.ts +1 -1
- package/src/helix/channel-points/custom-rewards.ts +2 -3
- package/src/helix/helix.ts +1 -1
- package/src/helix/subscriptions/subscriptions.ts +9 -19
- package/src/helix/users/users.ts +1 -1
- package/src/helpers/alerts/alerts.ts +3 -3
- package/src/helpers/charity/charity.ts +1 -1
- package/src/helpers/goal/goal.ts +1 -1
- package/src/helpers/hype-train/hype-train.ts +1 -1
- package/src/helpers/poll/poll.ts +1 -1
- package/src/helpers/prediction/prediction.ts +1 -1
- package/src/helpers/redemption/redemption.ts +1 -1
- package/src/helpers/status/status.ts +1 -1
- package/src/ui/authentication.ts +29 -85
- package/src/ui/popup.ts +45 -6
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Authentication } from "../../authentication/index.js"
|
|
1
|
+
import { type Authentication } from "../../authentication/index.js"
|
|
2
2
|
import {
|
|
3
|
-
TwitchSubscription,
|
|
4
|
-
TwitchSubscriptionType,
|
|
3
|
+
type TwitchSubscription,
|
|
4
|
+
type TwitchSubscriptionType,
|
|
5
5
|
buildSubscription,
|
|
6
6
|
} from "../../eventsub/events/index.js"
|
|
7
|
-
import { BaseSubscription } from "../../eventsub/events-helpers.js"
|
|
7
|
+
import { type BaseSubscription } from "../../eventsub/events-helpers.js"
|
|
8
8
|
import { helix } from "../helix.js"
|
|
9
9
|
|
|
10
10
|
interface SubscriptionWebhookTransport {
|
|
@@ -22,7 +22,7 @@ type SubscriptionTransport =
|
|
|
22
22
|
| SubscriptionWebhookTransport
|
|
23
23
|
| SubscriptionWebsocketTransport
|
|
24
24
|
|
|
25
|
-
export
|
|
25
|
+
export interface SubscriptionRequest<Subscription extends BaseSubscription> {
|
|
26
26
|
type: Subscription["type"]
|
|
27
27
|
version: Subscription["version"]
|
|
28
28
|
condition: Subscription["condition"]
|
|
@@ -61,20 +61,11 @@ export async function createSubscription<
|
|
|
61
61
|
authentication: Authentication,
|
|
62
62
|
type: Type,
|
|
63
63
|
): Promise<ActiveSubscription<Subscription>> {
|
|
64
|
-
const subscription = buildSubscription(
|
|
65
|
-
type,
|
|
66
|
-
authentication.user.id,
|
|
67
|
-
) as Subscription
|
|
64
|
+
const subscription = buildSubscription(type, authentication.user.id)
|
|
68
65
|
|
|
69
|
-
const [activeSubscription] = await helix
|
|
70
|
-
ActiveSubscription<Subscription>,
|
|
71
|
-
never,
|
|
72
|
-
never,
|
|
73
|
-
SubscriptionRequest<Subscription>
|
|
74
|
-
>(authentication, {
|
|
66
|
+
const [activeSubscription] = await helix(authentication, {
|
|
75
67
|
method: "post",
|
|
76
68
|
path: "/eventsub/subscriptions",
|
|
77
|
-
// @ts-ignore
|
|
78
69
|
body: {
|
|
79
70
|
...subscription,
|
|
80
71
|
transport: {
|
|
@@ -91,7 +82,7 @@ export async function deleteSubscription(
|
|
|
91
82
|
authentication: Authentication,
|
|
92
83
|
id: string,
|
|
93
84
|
): Promise<void> {
|
|
94
|
-
await helix<never, never, { id: string }
|
|
85
|
+
await helix<never, never, { id: string }>(authentication, {
|
|
95
86
|
method: "delete",
|
|
96
87
|
path: "/eventsub/subscriptions",
|
|
97
88
|
params: {
|
|
@@ -111,8 +102,7 @@ export async function getSubscriptions(
|
|
|
111
102
|
type?: TwitchSubscriptionType
|
|
112
103
|
user_id?: string
|
|
113
104
|
after?: string
|
|
114
|
-
}
|
|
115
|
-
never
|
|
105
|
+
}
|
|
116
106
|
>(authentication, {
|
|
117
107
|
method: "get",
|
|
118
108
|
path: "/eventsub/subscriptions",
|
package/src/helix/users/users.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import createQueue, { Queue } from "@overlaysymphony/core/libs/queue"
|
|
1
|
+
import createQueue, { type Queue } from "@overlaysymphony/core/libs/queue"
|
|
2
2
|
|
|
3
|
-
import { TwitchNotificationMessage } from "../../eventsub/events/index.js"
|
|
4
|
-
import { TwitchEventSub } from "../../eventsub/index.js"
|
|
3
|
+
import { type TwitchNotificationMessage } from "../../eventsub/events/index.js"
|
|
4
|
+
import { type TwitchEventSub } from "../../eventsub/index.js"
|
|
5
5
|
|
|
6
6
|
export type Alert = TwitchNotificationMessage<
|
|
7
7
|
| "channel.cheer"
|
package/src/helpers/goal/goal.ts
CHANGED
package/src/helpers/poll/poll.ts
CHANGED
package/src/ui/authentication.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
clearCachedAuthentication,
|
|
3
|
+
getAuthentication,
|
|
4
|
+
popupAuthentication,
|
|
4
5
|
} from "../authentication/index.js"
|
|
5
6
|
|
|
6
7
|
declare global {
|
|
7
8
|
interface Window {
|
|
8
|
-
overlaysymphonyTwitchScopes
|
|
9
|
+
overlaysymphonyTwitchScopes?: string[]
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const localStorageKey = "overlaysymphony:service:twitch"
|
|
13
|
-
|
|
14
13
|
export class TwitchAuthentication extends HTMLElement {
|
|
15
14
|
static get observedAttributes(): string[] {
|
|
16
15
|
return ["client-id", "scopes-key", "popup-url"]
|
|
@@ -31,7 +30,7 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
31
30
|
`)
|
|
32
31
|
|
|
33
32
|
stylesheet.insertRule(`
|
|
34
|
-
|
|
33
|
+
.authenticating, .validating, .authenticated, .unauthenticated {
|
|
35
34
|
display: block;
|
|
36
35
|
box-sizing: border-box;
|
|
37
36
|
width: 100%;
|
|
@@ -47,13 +46,6 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
47
46
|
}
|
|
48
47
|
`)
|
|
49
48
|
|
|
50
|
-
stylesheet.insertRule(`
|
|
51
|
-
button {
|
|
52
|
-
background: #9146FF;
|
|
53
|
-
border: 2px outset buttonBorder;
|
|
54
|
-
}
|
|
55
|
-
`)
|
|
56
|
-
|
|
57
49
|
stylesheet.insertRule(`
|
|
58
50
|
.authenticating, .validating {
|
|
59
51
|
background: #949494;
|
|
@@ -68,9 +60,17 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
68
60
|
}
|
|
69
61
|
`)
|
|
70
62
|
|
|
63
|
+
stylesheet.insertRule(`
|
|
64
|
+
.unauthenticated {
|
|
65
|
+
background: #9146FF;
|
|
66
|
+
border: 2px outset buttonBorder;
|
|
67
|
+
}
|
|
68
|
+
`)
|
|
69
|
+
|
|
71
70
|
this.root = this.attachShadow({ mode: "open" })
|
|
72
71
|
this.root.adoptedStyleSheets.push(stylesheet)
|
|
73
72
|
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
74
74
|
this.render()
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -82,7 +82,7 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
82
82
|
const key = (this.getAttribute("scopes-key") ??
|
|
83
83
|
"overlaysymphonyTwitchScopes") as "overlaysymphonyTwitchScopes"
|
|
84
84
|
|
|
85
|
-
return window[key]
|
|
85
|
+
return window[key] ?? []
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
get popupUrl(): string {
|
|
@@ -90,53 +90,21 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
90
90
|
const end = parent.lastIndexOf("/")
|
|
91
91
|
const baseURL = window.location.href.slice(0, end)
|
|
92
92
|
|
|
93
|
-
return this.getAttribute("
|
|
93
|
+
return this.getAttribute("popup-url") ?? `${baseURL}/popup-twitch.html`
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
async authenticate(): Promise<void> {
|
|
97
97
|
this.renderAuthenticating()
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
`${this.popupUrl}?scopes=${this.scopes.join("+")}&clientId=${this.clientId}`,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
await new Promise<void>((resolve) => {
|
|
104
|
-
const listener = (event: MessageEvent) => {
|
|
105
|
-
if (event.origin !== url.origin) return
|
|
106
|
-
|
|
107
|
-
// const source = (event.source as Window | null)?.name
|
|
108
|
-
// if (source !== "OverlaySymphonyTwitchAuthenticationPopup") return
|
|
109
|
-
|
|
110
|
-
const { type, authentication } = event.data
|
|
111
|
-
if (type !== "authentication") return
|
|
99
|
+
await popupAuthentication(this.clientId, this.scopes, this.popupUrl)
|
|
112
100
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
setCached(authentication)
|
|
116
|
-
this.render()
|
|
117
|
-
resolve()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
window.addEventListener("message", listener)
|
|
121
|
-
|
|
122
|
-
window.open(
|
|
123
|
-
url,
|
|
124
|
-
"OverlaySymphonyTwitchAuthenticationPopup",
|
|
125
|
-
"width=520,height=840",
|
|
126
|
-
)
|
|
127
|
-
})
|
|
101
|
+
return this.render()
|
|
128
102
|
}
|
|
129
103
|
|
|
130
|
-
async
|
|
131
|
-
|
|
104
|
+
async unauthenticate(): Promise<void> {
|
|
105
|
+
clearCachedAuthentication()
|
|
132
106
|
|
|
133
|
-
|
|
134
|
-
await validateAuthentication(authentication)
|
|
135
|
-
this.renderAuthenticated()
|
|
136
|
-
} catch (e) {
|
|
137
|
-
clearCached()
|
|
138
|
-
this.render()
|
|
139
|
-
}
|
|
107
|
+
return this.render()
|
|
140
108
|
}
|
|
141
109
|
|
|
142
110
|
clear(): void {
|
|
@@ -169,28 +137,31 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
169
137
|
renderAuthenticated(): void {
|
|
170
138
|
this.clear()
|
|
171
139
|
|
|
172
|
-
this.element = document.createElement("
|
|
140
|
+
this.element = document.createElement("button")
|
|
173
141
|
this.element.classList.add("authenticated")
|
|
174
142
|
this.element.innerText = "Authenticated"
|
|
175
|
-
|
|
176
143
|
this.root.append(this.element)
|
|
144
|
+
|
|
145
|
+
this.element.addEventListener("click", () => this.unauthenticate())
|
|
177
146
|
}
|
|
178
147
|
|
|
179
148
|
renderUnauthenticated(): void {
|
|
180
149
|
this.clear()
|
|
181
150
|
|
|
182
151
|
this.element = document.createElement("button")
|
|
152
|
+
this.element.classList.add("unauthenticated")
|
|
183
153
|
this.element.innerText = "Authenticate with Twitch"
|
|
184
154
|
this.root.append(this.element)
|
|
185
155
|
|
|
186
156
|
this.element.addEventListener("click", () => this.authenticate())
|
|
187
157
|
}
|
|
188
158
|
|
|
189
|
-
render(): void {
|
|
190
|
-
|
|
159
|
+
async render(): Promise<void> {
|
|
160
|
+
this.renderValidating()
|
|
191
161
|
|
|
192
|
-
|
|
193
|
-
|
|
162
|
+
const authentication = await getAuthentication(this.scopes)
|
|
163
|
+
if (authentication) {
|
|
164
|
+
this.renderAuthenticated()
|
|
194
165
|
} else {
|
|
195
166
|
this.renderUnauthenticated()
|
|
196
167
|
}
|
|
@@ -201,30 +172,3 @@ window.customElements.define(
|
|
|
201
172
|
"overlaysymfony-twitch-authentication",
|
|
202
173
|
TwitchAuthentication,
|
|
203
174
|
)
|
|
204
|
-
|
|
205
|
-
export function getCached(scopes: string[]): BareAuthentication | undefined {
|
|
206
|
-
const cache = localStorage.getItem(localStorageKey)
|
|
207
|
-
if (cache) {
|
|
208
|
-
const authentication = JSON.parse(cache) as BareAuthentication
|
|
209
|
-
authentication.expires = new Date(authentication.expires)
|
|
210
|
-
|
|
211
|
-
for (const scope of scopes) {
|
|
212
|
-
if (!authentication.scope.includes(scope)) {
|
|
213
|
-
localStorage.removeItem(localStorageKey)
|
|
214
|
-
return undefined
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return authentication
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return undefined
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function setCached(authentication: BareAuthentication): void {
|
|
225
|
-
localStorage.setItem(localStorageKey, JSON.stringify(authentication))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export function clearCached(): void {
|
|
229
|
-
localStorage.removeItem(localStorageKey)
|
|
230
|
-
}
|
package/src/ui/popup.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import querystring from "@overlaysymphony/core/libs/querystring"
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
type BareAuthentication,
|
|
5
|
+
validateAuthentication,
|
|
6
6
|
} from "../authentication/index.js"
|
|
7
7
|
|
|
8
8
|
const state = getState()
|
|
9
9
|
|
|
10
10
|
if (state.step === "initial") {
|
|
11
|
-
|
|
11
|
+
initiateAuthentication(
|
|
12
12
|
state.clientId,
|
|
13
13
|
`${window.location.origin}${window.location.pathname}`,
|
|
14
14
|
state.scopes,
|
|
@@ -17,12 +17,13 @@ if (state.step === "initial") {
|
|
|
17
17
|
|
|
18
18
|
if (state.step === "token") {
|
|
19
19
|
const authentication = await authenticateResult(state.clientId, state)
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
20
21
|
window.opener.postMessage({ type: "authentication", authentication }, "*")
|
|
21
22
|
window.close()
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
if (state.step === "error") {
|
|
25
|
-
alert(`${state.error}. ${state.description
|
|
26
|
+
alert(`${state.error}. ${state.description ?? ""}`)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function getState():
|
|
@@ -101,15 +102,53 @@ function getState():
|
|
|
101
102
|
|
|
102
103
|
return {
|
|
103
104
|
step: "error",
|
|
104
|
-
error: validateString(query.error)
|
|
105
|
+
error: validateString(query.error) ?? "Unknown Error",
|
|
105
106
|
description: validateString(query.error_description),
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function validateString(input:
|
|
110
|
+
function validateString(input: unknown): string | undefined {
|
|
110
111
|
if (typeof input !== "string" || !input) {
|
|
111
112
|
return undefined
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
return input
|
|
115
116
|
}
|
|
117
|
+
|
|
118
|
+
function initiateAuthentication(
|
|
119
|
+
clientId: string,
|
|
120
|
+
redirect: string,
|
|
121
|
+
scope: string[],
|
|
122
|
+
): void {
|
|
123
|
+
window.location.href = `https://id.twitch.tv/oauth2/authorize?${querystring.stringify(
|
|
124
|
+
{
|
|
125
|
+
response_type: "token",
|
|
126
|
+
client_id: clientId,
|
|
127
|
+
redirect_uri: redirect,
|
|
128
|
+
scope: scope.join("+"),
|
|
129
|
+
state: clientId,
|
|
130
|
+
},
|
|
131
|
+
)}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function authenticateResult(
|
|
135
|
+
clientId: string,
|
|
136
|
+
result: {
|
|
137
|
+
token_type?: "bearer"
|
|
138
|
+
access_token?: string
|
|
139
|
+
scope?: string
|
|
140
|
+
},
|
|
141
|
+
): Promise<BareAuthentication> {
|
|
142
|
+
if (!result.token_type || !result.access_token || !result.scope) {
|
|
143
|
+
throw new Error("Invalid result.")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const authentication = await validateAuthentication({
|
|
147
|
+
clientId,
|
|
148
|
+
tokenType: result.token_type,
|
|
149
|
+
accessToken: result.access_token,
|
|
150
|
+
scope: result.scope.split("+"),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return authentication
|
|
154
|
+
}
|