@overlaysymphony/twitch 0.1.1 → 0.2.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/README.md +1 -1
- package/package.json +15 -17
- package/src/authentication/authentication.ts +110 -54
- package/src/chat/chat.ts +9 -8
- package/src/chat/helpers.ts +36 -0
- package/src/chat/index.ts +1 -0
- 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 +51 -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 -10
- 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 +17 -3
- package/src/helpers/prediction/prediction.ts +1 -1
- package/src/helpers/redemption/redemption.ts +21 -2
- package/src/helpers/status/status.ts +1 -1
- package/src/ui/authentication.ts +29 -86
- package/src/ui/popup.ts +46 -7
- package/src/ui/vite-env.d.ts +1 -0
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,10 +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
|
|
|
74
|
-
this.render()
|
|
73
|
+
void this.render()
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
get clientId(): string {
|
|
@@ -82,7 +81,7 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
82
81
|
const key = (this.getAttribute("scopes-key") ??
|
|
83
82
|
"overlaysymphonyTwitchScopes") as "overlaysymphonyTwitchScopes"
|
|
84
83
|
|
|
85
|
-
return window[key]
|
|
84
|
+
return window[key] ?? []
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
get popupUrl(): string {
|
|
@@ -90,53 +89,21 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
90
89
|
const end = parent.lastIndexOf("/")
|
|
91
90
|
const baseURL = window.location.href.slice(0, end)
|
|
92
91
|
|
|
93
|
-
return this.getAttribute("
|
|
92
|
+
return this.getAttribute("popup-url") ?? `${baseURL}/popup-twitch.html`
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
async authenticate(): Promise<void> {
|
|
97
96
|
this.renderAuthenticating()
|
|
98
97
|
|
|
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
|
|
98
|
+
await popupAuthentication(this.clientId, this.scopes, this.popupUrl)
|
|
112
99
|
|
|
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
|
-
})
|
|
100
|
+
return this.render()
|
|
128
101
|
}
|
|
129
102
|
|
|
130
|
-
async
|
|
131
|
-
|
|
103
|
+
async unauthenticate(): Promise<void> {
|
|
104
|
+
clearCachedAuthentication()
|
|
132
105
|
|
|
133
|
-
|
|
134
|
-
await validateAuthentication(authentication)
|
|
135
|
-
this.renderAuthenticated()
|
|
136
|
-
} catch (e) {
|
|
137
|
-
clearCached()
|
|
138
|
-
this.render()
|
|
139
|
-
}
|
|
106
|
+
return this.render()
|
|
140
107
|
}
|
|
141
108
|
|
|
142
109
|
clear(): void {
|
|
@@ -169,28 +136,31 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
169
136
|
renderAuthenticated(): void {
|
|
170
137
|
this.clear()
|
|
171
138
|
|
|
172
|
-
this.element = document.createElement("
|
|
139
|
+
this.element = document.createElement("button")
|
|
173
140
|
this.element.classList.add("authenticated")
|
|
174
141
|
this.element.innerText = "Authenticated"
|
|
175
|
-
|
|
176
142
|
this.root.append(this.element)
|
|
143
|
+
|
|
144
|
+
this.element.addEventListener("click", () => this.unauthenticate())
|
|
177
145
|
}
|
|
178
146
|
|
|
179
147
|
renderUnauthenticated(): void {
|
|
180
148
|
this.clear()
|
|
181
149
|
|
|
182
150
|
this.element = document.createElement("button")
|
|
151
|
+
this.element.classList.add("unauthenticated")
|
|
183
152
|
this.element.innerText = "Authenticate with Twitch"
|
|
184
153
|
this.root.append(this.element)
|
|
185
154
|
|
|
186
155
|
this.element.addEventListener("click", () => this.authenticate())
|
|
187
156
|
}
|
|
188
157
|
|
|
189
|
-
render(): void {
|
|
190
|
-
|
|
158
|
+
async render(): Promise<void> {
|
|
159
|
+
this.renderValidating()
|
|
191
160
|
|
|
192
|
-
|
|
193
|
-
|
|
161
|
+
const authentication = await getAuthentication(this.scopes)
|
|
162
|
+
if (authentication) {
|
|
163
|
+
this.renderAuthenticated()
|
|
194
164
|
} else {
|
|
195
165
|
this.renderUnauthenticated()
|
|
196
166
|
}
|
|
@@ -201,30 +171,3 @@ window.customElements.define(
|
|
|
201
171
|
"overlaysymfony-twitch-authentication",
|
|
202
172
|
TwitchAuthentication,
|
|
203
173
|
)
|
|
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
|
-
window.opener
|
|
20
|
+
const opener = window.opener as Window | undefined
|
|
21
|
+
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
|
+
tokenType: result.token_type,
|
|
148
|
+
clientId,
|
|
149
|
+
accessToken: result.access_token,
|
|
150
|
+
scope: result.scope.split("+"),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return authentication
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|