Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a27f8804e4 | ||
|
|
2788275d25 | ||
|
|
2265fd39e0 | ||
|
|
83f3217565 | ||
|
|
a704849061 | ||
|
|
2c89510400 | ||
|
|
041bebe4c5 | ||
|
|
f50403f381 | ||
|
|
6c138c93a4 | ||
|
|
6590dcdac7 | ||
|
|
ace36340a8 | ||
|
|
4918696c5a | ||
|
|
4bf079571b | ||
|
|
f032477067 | ||
|
|
ada811dda2 | ||
|
|
c2c952e96a | ||
|
|
bed578e80d | ||
|
|
c31eea94b0 | ||
|
|
eaf5343e27 |
@@ -1,121 +1,25 @@
|
|||||||
name: Docker
|
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
# Publish semver tags as releases.
|
tags: [ '*' ]
|
||||||
tags: [ '*.*.*' ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Use docker.io for Docker Hub if empty
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
# github.repository as <account>/<repo>
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
# This is used to complete the identity challenge
|
|
||||||
# with sigstore/fulcio when running outside of PRs.
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: latest
|
node-version: latest
|
||||||
|
|
||||||
- name: Install Worker Dependencies
|
- name: Install Worker Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- run: npm run deploy
|
- run: npm run deploy
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
- run: npm pack
|
- run: npm version from-git --allow-same-version --no-git-tag-version
|
||||||
- run: mv twitch-cloud-ebs*tgz twitch-cloud-ebs.tgz
|
- run: npm config set registry https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/
|
||||||
- run: git log $(git describe --tags --abbrev=0 HEAD~1)..HEAD --oneline > changelog.txt
|
- run: npm config set -- '//gitea.sugoidogo.com/api/packages/sugoidogo/npm/:_authToken' "$PACKAGES_KEY"
|
||||||
- uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
make_latest: true
|
|
||||||
body_path: changelog.txt
|
|
||||||
files: '*.tgz'
|
|
||||||
|
|
||||||
- name: Compile Worker
|
|
||||||
run: npx selflare compile
|
|
||||||
|
|
||||||
- name: Generate Dockerfile
|
|
||||||
run: npx selflare docker
|
|
||||||
|
|
||||||
# Install the cosign tool except on PR
|
|
||||||
# https://github.com/sigstore/cosign-installer
|
|
||||||
- name: Install cosign
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
|
|
||||||
with:
|
|
||||||
cosign-release: 'v2.2.4'
|
|
||||||
|
|
||||||
# Set up BuildKit Docker container builder to be able to build
|
|
||||||
# multi-platform images and export cache
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
|
||||||
# https://github.com/docker/login-action
|
|
||||||
- name: Log into registry ${{ env.REGISTRY }}
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Extract metadata (tags, labels) for Docker
|
|
||||||
# https://github.com/docker/metadata-action
|
|
||||||
- name: Extract Docker metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
# Build and push Docker image with Buildx (don't push on PR)
|
|
||||||
# https://github.com/docker/build-push-action
|
|
||||||
- name: Build and push Docker image
|
|
||||||
id: build-and-push
|
|
||||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
# Sign the resulting Docker image digest except on PRs.
|
|
||||||
# This will only write to the public Rekor transparency log when the Docker
|
|
||||||
# repository is public to avoid leaking data. If you would like to publish
|
|
||||||
# transparency data even for private images, pass --force to cosign below.
|
|
||||||
# https://github.com/sigstore/cosign
|
|
||||||
- name: Sign the published Docker image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
env:
|
env:
|
||||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
|
PACKAGES_KEY: ${{ secrets.PACKAGES_KEY }}
|
||||||
TAGS: ${{ steps.meta.outputs.tags }}
|
- run: npm publish
|
||||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
|
||||||
# This step uses the identity token to provision an ephemeral certificate
|
|
||||||
# against the sigstore community Fulcio instance.
|
|
||||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
|
||||||
@@ -5,30 +5,28 @@ This is a server-side component providing cloud storage and [OAuth Code Grant Fl
|
|||||||
|
|
||||||
### Cloudflare Workers
|
### Cloudflare Workers
|
||||||
|
|
||||||
[](https://deploy.workers.cloudflare.com/?url=https%3A%2F%2Fgithub.com%2Fsugoidogo%2Ftwitch-cloud-ebs)
|
Previously, there was a "deploy to cloudflare" button here, but it seems to only work with GitHub repos, so you'll have to clone and deploy this yourself.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
This worker requires that the KV store is pre-populated with client-id keys and client-secret values, but doesn't provide any mechanism for this to work with selflare. As such, the docker container may not be usable until this is resolved. PRs are welcome.
|
Previously, this repo produced oci images, but they were inclomlete and more work is needed before they are ready.
|
||||||
|
|
||||||
This worker is also availible as a docker container at [ghcr.io/sugoidogo/twitch-cloud-ebs](https://github.com/sugoidogo/twitch-clips-consent-api/pkgs/container/twitch-clips-consent-api).
|
|
||||||
It listens on HTTP port 8080 and requires mounts for `/worker/cache`, `/worker/kv`, `/worker/d1`, and `/worker/r2`.
|
|
||||||
The r2 and kv mounts are the only ones actually used, but the selflare runtime requires all of them regardless.
|
|
||||||
|
|
||||||
## Twurple Module
|
## Twurple Module
|
||||||
|
|
||||||
|
https://gitea.sugoidogo.com/sugoidogo/-/packages/npm/@sugoidogo%2Ftwitch-cloud-ebs/
|
||||||
|
|
||||||
This API provides two Twurple modules, one for authorization and one for storage, allowing for easy integration if you're already using the Twurple javascript library.
|
This API provides two Twurple modules, one for authorization and one for storage, allowing for easy integration if you're already using the Twurple javascript library.
|
||||||
the WebStorage module can be used to cache fetched resources from anywhere, but when used as demonstrated below, allows your client to maintain access to previously stored resources from cloud storage via the browser's CacheStorage API.
|
the WebStorage module can be used to cache fetched resources from anywhere, but when used as demonstrated below, allows your client to maintain access to previously stored resources from cloud storage via the browser's CacheStorage API.
|
||||||
|
|
||||||
<details><summary>javascript</summary>
|
<details><summary>javascript</summary>
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import SugoiAuthProvider from 'https://ebs.domain.com/SugoiAuthProvider.js'
|
import { SugoiAuthProvider, WebStorage } from '@sugoidogo/twitch-cloud-ebs'
|
||||||
import WebStorage from 'https://ebs.domain.com/WebStorage.js'
|
import { ApiClient } from '@twurple/ebs';
|
||||||
import { ApiClient } from 'https://cdn.jsdelivr.net/npm/@twurple/api@7/+esm';
|
|
||||||
|
|
||||||
const authProvicder=new SugoiAuthProvider('your-client-id')
|
const ebs_host='https://your.ebs.hostname'
|
||||||
const webStorage=new WebStorage(authProvider)
|
const authProvicder=new SugoiAuthProvider('your-client-id',ebs_host)
|
||||||
|
const webStorage=new WebStorage(authProvider,undefined,ebs_host)
|
||||||
const apiClient = new ApiClient({ authProvider });
|
const apiClient = new ApiClient({ authProvider });
|
||||||
|
|
||||||
const config=await webStorage.fetch('config.json').then(response=>response.json())
|
const config=await webStorage.fetch('config.json').then(response=>response.json())
|
||||||
|
|||||||
Generated
+65
-2
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-cloud-ebs",
|
"name": "@sugoidogo/twitch-cloud-ebs",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-cloud-ebs",
|
"name": "@sugoidogo/twitch-cloud-ebs",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sugoidogo/selflare": "^1.1.3",
|
"@sugoidogo/selflare": "^1.1.3",
|
||||||
|
"@twurple/api": "^7.4.0",
|
||||||
"@twurple/auth": "^7.4.0",
|
"@twurple/auth": "^7.4.0",
|
||||||
"fetch-retry": "^6.0.0",
|
"fetch-retry": "^6.0.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
@@ -57,6 +58,17 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@d-fischer/cache-decorators": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@d-fischer/cache-decorators/-/cache-decorators-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-HNYLBLWs/t28GFZZeqdIBqq8f37mqDIFO6xNPof94VjpKvuP6ROqCZGafx88dk5zZUlBfViV9jD8iNNlXfc4CA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@d-fischer/shared-utils": "^3.6.3",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@d-fischer/cross-fetch": {
|
"node_modules/@d-fischer/cross-fetch": {
|
||||||
"version": "5.0.5",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@d-fischer/cross-fetch/-/cross-fetch-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@d-fischer/cross-fetch/-/cross-fetch-5.0.5.tgz",
|
||||||
@@ -102,6 +114,21 @@
|
|||||||
"url": "https://github.com/sponsors/d-fischer"
|
"url": "https://github.com/sponsors/d-fischer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@d-fischer/rate-limiter": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@d-fischer/rate-limiter/-/rate-limiter-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-O5HgACwApyCZhp4JTEBEtbv/W3eAwEkrARFvgWnEsDmXgCMWjIHwohWoHre5BW6IYXFSHBGsuZB/EvNL3942kQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@d-fischer/logger": "^4.2.3",
|
||||||
|
"@d-fischer/shared-utils": "^3.6.3",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/d-fischer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@d-fischer/shared-utils": {
|
"node_modules/@d-fischer/shared-utils": {
|
||||||
"version": "3.6.4",
|
"version": "3.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/@d-fischer/shared-utils/-/shared-utils-3.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/@d-fischer/shared-utils/-/shared-utils-3.6.4.tgz",
|
||||||
@@ -1741,6 +1768,32 @@
|
|||||||
"stacktracey": "^2.1.8"
|
"stacktracey": "^2.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@twurple/api": {
|
||||||
|
"version": "7.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@twurple/api/-/api-7.4.0.tgz",
|
||||||
|
"integrity": "sha512-RlXLs4ZvS8n0+iIk7YyVDwrjhlwpn+N+h7fX5Q61HoxlmzoCShmnnFo03abYw9i8Cc3deGpbQATOSVmigXM4qg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@d-fischer/cache-decorators": "^4.0.0",
|
||||||
|
"@d-fischer/cross-fetch": "^5.0.1",
|
||||||
|
"@d-fischer/detect-node": "^3.0.1",
|
||||||
|
"@d-fischer/logger": "^4.2.1",
|
||||||
|
"@d-fischer/rate-limiter": "^1.1.0",
|
||||||
|
"@d-fischer/shared-utils": "^3.6.1",
|
||||||
|
"@d-fischer/typed-event-emitter": "^3.3.1",
|
||||||
|
"@twurple/api-call": "7.4.0",
|
||||||
|
"@twurple/common": "7.4.0",
|
||||||
|
"retry": "^0.13.1",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/d-fischer"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@twurple/auth": "7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@twurple/api-call": {
|
"node_modules/@twurple/api-call": {
|
||||||
"version": "7.4.0",
|
"version": "7.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-7.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@twurple/api-call/-/api-call-7.4.0.tgz",
|
||||||
@@ -2393,6 +2446,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/retry": {
|
||||||
|
"version": "0.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||||
|
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup-plugin-inject": {
|
"node_modules/rollup-plugin-inject": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-cloud-ebs",
|
"name": "@sugoidogo/twitch-cloud-ebs",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"files": [
|
"files": [
|
||||||
"static",
|
"static",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sugoidogo/selflare": "^1.1.3",
|
"@sugoidogo/selflare": "^1.1.3",
|
||||||
|
"@twurple/api": "^7.4.0",
|
||||||
"@twurple/auth": "^7.4.0",
|
"@twurple/auth": "^7.4.0",
|
||||||
"fetch-retry": "^6.0.0",
|
"fetch-retry": "^6.0.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
import * as TwitchAuth from "./TwitchAuth.ts";
|
import * as TwitchAuth from "./TwitchAuth.ts";
|
||||||
import { AccessTokenMaybeWithUserId, AuthProvider, AccessToken, AccessTokenWithUserId } from "@twurple/auth";
|
import { AccessTokenMaybeWithUserId, AuthProvider, AccessTokenWithUserId } from "@twurple/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that represents a user and contains a user ID.
|
||||||
|
*/
|
||||||
|
interface UserIdResolvableType {
|
||||||
|
/**
|
||||||
|
* The ID of the user.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user ID or a user or channel object.
|
||||||
|
*/
|
||||||
|
type UserIdResolvable = string | number | UserIdResolvableType;
|
||||||
|
|
||||||
type Token = TwitchAuth.TwitchToken & AccessTokenMaybeWithUserId
|
type Token = TwitchAuth.TwitchToken & AccessTokenMaybeWithUserId
|
||||||
|
|
||||||
@@ -59,7 +74,7 @@ export default class SugoiAuthProvider implements AuthProvider {
|
|||||||
this.#token = null
|
this.#token = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccessTokenForUser(user: string | number, ...scopeSets: string[][]) {
|
async getAccessTokenForUser(user: UserIdResolvable, ...scopeSets: string[][]) {
|
||||||
if ((!scopeSets[0]) && (this.#token)) {
|
if ((!scopeSets[0]) && (this.#token)) {
|
||||||
return this.#token as AccessTokenWithUserId
|
return this.#token as AccessTokenWithUserId
|
||||||
}
|
}
|
||||||
@@ -72,18 +87,18 @@ export default class SugoiAuthProvider implements AuthProvider {
|
|||||||
return this.#token as AccessTokenWithUserId
|
return this.#token as AccessTokenWithUserId
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAnyAccessToken(user: string | number) {
|
async getAnyAccessToken(user: UserIdResolvable) {
|
||||||
return this.#token || TwitchAuth.getAppToken(this.clientId).then(getTwurpleProxy)
|
return this.#token || TwitchAuth.getAppToken(this.clientId).then(getTwurpleProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentScopesForUser(user: string | number) {
|
getCurrentScopesForUser(user: UserIdResolvable) {
|
||||||
if (!this.#token || this.#token instanceof Promise) {
|
if (!this.#token || this.#token instanceof Promise) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return this.#token.scope
|
return this.#token.scope
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshAccessTokenForUser(user: string | number) {
|
async refreshAccessTokenForUser(user: UserIdResolvable) {
|
||||||
this.#token = await TwitchAuth.refreshToken(this.clientId, this.#token.refresh_token).then(this.#setToken)
|
this.#token = await TwitchAuth.refreshToken(this.clientId, this.#token.refresh_token).then(this.#setToken)
|
||||||
return this.#token as AccessTokenWithUserId
|
return this.#token as AccessTokenWithUserId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import type { AccessTokenMaybeWithUserId, AccessTokenWithUserId, AuthProvider } from "@twurple/auth";
|
||||||
|
import type { UserIdResolvable } from "@twurple/api"
|
||||||
|
import * as TwitchAuth from "./TwitchAuth";
|
||||||
|
|
||||||
|
async function ensureUserId(token: TwitchAuth.TwitchToken | null): Promise<TwitchAuth.TwitchToken | null> {
|
||||||
|
if (!token) return null
|
||||||
|
if (!token.user_id) {
|
||||||
|
const refresh_token = token.refresh_token
|
||||||
|
token = await TwitchAuth.validateToken(token.access_token)
|
||||||
|
token.refresh_token = refresh_token
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTwurpleProxy(token: TwitchAuth.TwitchToken): Promise<AccessTokenWithUserId> {
|
||||||
|
token=await ensureUserId(token)
|
||||||
|
return new Proxy(token, {
|
||||||
|
get(target, name, receiver) {
|
||||||
|
return target[name.toString().replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) as keyof TwitchAuth.TwitchToken]
|
||||||
|
}
|
||||||
|
}) as unknown as AccessTokenWithUserId
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SugoiAuthProvider implements AuthProvider {
|
||||||
|
#token: TwitchAuth.TwitchToken | Promise<TwitchAuth.TwitchToken> | Promise<TwitchAuth.TwitchToken | null> | null = null;
|
||||||
|
clientId: string;
|
||||||
|
constructor(clientId:string,proxyOrigin?:string) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
if (proxyOrigin) TwitchAuth.setProxyOrigin(proxyOrigin);
|
||||||
|
this.#token = TwitchAuth.getUserTokenPassive(clientId).then(ensureUserId)
|
||||||
|
}
|
||||||
|
#authorize(scopes: string[]) {
|
||||||
|
const promise = TwitchAuth.requestAuthCode(this.clientId, ...scopes)
|
||||||
|
this.#token = promise
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks cached token for given scopes, and redirects to authorization page on failure.
|
||||||
|
* You should only call this from your configured redirect uri.
|
||||||
|
*
|
||||||
|
* https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#get-the-user-to-authorize-your-app
|
||||||
|
*/
|
||||||
|
async getAuthorization(...scopes: string[]): Promise<void> {
|
||||||
|
const token = await this.#token
|
||||||
|
if(!token) return this.#authorize(scopes)
|
||||||
|
for (const scope of scopes) {
|
||||||
|
if (!token.scope!.includes(scope)) {
|
||||||
|
return this.#authorize(scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getCurrentScopesForUser(user: UserIdResolvable):string[] {
|
||||||
|
if (!this.#token || this.#token instanceof Promise) throw new Error("unauthorized")
|
||||||
|
return this.#token.scope || []
|
||||||
|
}
|
||||||
|
async getAccessTokenForUser(user: UserIdResolvable, ...scopeSets: (string[] | undefined)[]): Promise<AccessTokenWithUserId | null> {
|
||||||
|
const token = await this.#token
|
||||||
|
if (!token) return null
|
||||||
|
return getTwurpleProxy(token)
|
||||||
|
}
|
||||||
|
async getAnyAccessToken(user?: UserIdResolvable): Promise<AccessTokenMaybeWithUserId> {
|
||||||
|
const token = await this.#token
|
||||||
|
if (!token) throw new Error("unauthorized")
|
||||||
|
return getTwurpleProxy(token)
|
||||||
|
}
|
||||||
|
async refreshAccessTokenForUser(user: UserIdResolvable): Promise<AccessTokenWithUserId> {
|
||||||
|
const token=await this.#token
|
||||||
|
if (!token) throw new Error("unauthorized")
|
||||||
|
if (!token.refresh_token) throw new Error("missing refresh token")
|
||||||
|
this.#token = await TwitchAuth.refreshToken(this.clientId, token.refresh_token).then(ensureUserId)
|
||||||
|
return getTwurpleProxy(this.#token!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SugoiAuthProvider
|
||||||
@@ -86,7 +86,7 @@ export function requestAccessToken(client_id: string, ...scopes: Array<string>)
|
|||||||
* @param {Array<string>|string} scopes
|
* @param {Array<string>|string} scopes
|
||||||
* @returns {Promise<AuthCode>}
|
* @returns {Promise<AuthCode>}
|
||||||
*/
|
*/
|
||||||
export function requestAuthCode(client_id: string, ...scopes: Array<string>): Promise<any> {
|
export function requestAuthCode(client_id: string, ...scopes: Array<string>): Promise<never> {
|
||||||
console.debug('requesting authorization code')
|
console.debug('requesting authorization code')
|
||||||
const url = new URL('https://id.twitch.tv/oauth2/authorize')
|
const url = new URL('https://id.twitch.tv/oauth2/authorize')
|
||||||
url.searchParams.append('response_type', 'code')
|
url.searchParams.append('response_type', 'code')
|
||||||
@@ -178,7 +178,7 @@ export function refreshToken(client_id: string, refresh_token: string): Promise<
|
|||||||
* @param {string} client_id
|
* @param {string} client_id
|
||||||
* @returns {TwitchToken}
|
* @returns {TwitchToken}
|
||||||
*/
|
*/
|
||||||
export async function getUserToken(client_id: string, ...scopes): Promise<TwitchToken> {
|
export async function getUserToken(client_id: string, ...scopes:string[]): Promise<TwitchToken> {
|
||||||
const token=await getUserTokenPassive(client_id, ...scopes)
|
const token=await getUserTokenPassive(client_id, ...scopes)
|
||||||
if(!token){
|
if(!token){
|
||||||
return requestAuthCode(client_id, ...scopes)
|
return requestAuthCode(client_id, ...scopes)
|
||||||
@@ -193,19 +193,19 @@ export async function getUserToken(client_id: string, ...scopes): Promise<Twitch
|
|||||||
* @param {string} client_id
|
* @param {string} client_id
|
||||||
* @returns {TwitchToken}
|
* @returns {TwitchToken}
|
||||||
*/
|
*/
|
||||||
export async function getUserTokenPassive(client_id: string, ...scopes): Promise<TwitchToken>{
|
export async function getUserTokenPassive(client_id: string, ...scopes:string[]): Promise<TwitchToken|null>{
|
||||||
console.debug('scopes requested:',...scopes)
|
console.debug('scopes requested:',...scopes)
|
||||||
const params = new URLSearchParams(location.search + '&' + location.hash.substring(1))
|
const params = new URLSearchParams(location.search + '&' + location.hash.substring(1))
|
||||||
if (params.has('code')) {
|
if (params.has('code')) {
|
||||||
const code = params.get('code')
|
const code = params.get('code')!
|
||||||
history.replaceState(null, '', redirect_uri)
|
history.replaceState(null, '', redirect_uri)
|
||||||
return exchangeCode(client_id, code)
|
return exchangeCode(client_id, code)
|
||||||
}
|
}
|
||||||
if (params.has('refresh_token')) {
|
if (params.has('refresh_token')) {
|
||||||
return refreshToken(client_id, params.get('refresh_token'))
|
return refreshToken(client_id, params.get('refresh_token')!)
|
||||||
}
|
}
|
||||||
if (params.has('access_token')) {
|
if (params.has('access_token')) {
|
||||||
return validateToken(params.get('access_token'))
|
return validateToken(params.get('access_token')!)
|
||||||
}
|
}
|
||||||
if (params.has('error')) {
|
if (params.has('error')) {
|
||||||
const error_message = params.get('error') + ': ' + params.get('error_description')
|
const error_message = params.get('error') + ': ' + params.get('error_description')
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ export default class WebStorage {
|
|||||||
* @param {import('@twurple/auth').AuthProvider} auth_provider used to add the authentication header to requests for web storage
|
* @param {import('@twurple/auth').AuthProvider} auth_provider used to add the authentication header to requests for web storage
|
||||||
* @param {fetch} fetch defaults to `globalThis.fetch`, allows you to further customize fetch behavior via chaining, for example with `fetch-retry`
|
* @param {fetch} fetch defaults to `globalThis.fetch`, allows you to further customize fetch behavior via chaining, for example with `fetch-retry`
|
||||||
*/
|
*/
|
||||||
constructor(auth_provider: import('@twurple/auth').AuthProvider, fetch: typeof globalThis.fetch = (resource,options)=>{return globalThis.fetch(resource,options)}) {
|
constructor(auth_provider: import('@twurple/auth').AuthProvider, fetch: typeof globalThis.fetch = (input, init) => globalThis.fetch(input, init), origin?: string) {
|
||||||
this.#fetch = fetch
|
this.#fetch = fetch
|
||||||
this.#auth_provider = auth_provider
|
this.#auth_provider = auth_provider
|
||||||
auth_provider.getAccessTokenForUser(undefined)
|
auth_provider.getAccessTokenForUser(undefined)
|
||||||
.then(token => caches.open(token.userId + '/' + auth_provider.clientId))
|
.then(token => caches.open(token.userId + '/' + auth_provider.clientId))
|
||||||
.then(cache => this.#cache = cache)
|
.then(cache => this.#cache = cache)
|
||||||
|
if (origin) this.#origin = origin
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+5
-2
@@ -1,2 +1,5 @@
|
|||||||
export * as SugoiAuth from './SugoiAuthProvider'
|
export { default as SugoiAuthProvider } from "./SugoiAuthProvider"
|
||||||
export * as WebStorage from './WebStorage'
|
export { default as SugoiAuthProvider2 } from "./SugoiAuthProvider2"
|
||||||
|
export { default as WebStorage } from "./WebStorage"
|
||||||
|
export * as TwitchAuth from "./TwitchAuth"
|
||||||
|
export * as tba from "./tba"
|
||||||
@@ -69,4 +69,6 @@ export async function get_tokens(client_id: string,scope='',redirect_uri=locatio
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setProxyOrigin(proxy_origin:string) { TwitchAuth.setProxyOrigin(proxy_origin) }
|
||||||
|
|
||||||
export default get_tokens
|
export default get_tokens
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"module": "es2020",
|
"module": "es2020",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"skipLibCheck": true,
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user