[chore] Convert some settings / admin panel JS to TypeScript (#2247)

* initial conversion of STUFF to typescript

* more stuff

* update babel deps, include commonjs transform

* update bundler & eslint configuration

* eslint --fix

* upgrade deps

* update docs, build stuff, peripheral stuff

---------

Co-authored-by: f0x <f0x@cthu.lu>
This commit is contained in:
tobi 2023-10-05 16:06:19 +02:00 committed by GitHub
parent 6e508830e1
commit d173fcdfa3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 2365 additions and 1621 deletions

View file

@ -53,8 +53,7 @@ steps:
- name: yarn_cache - name: yarn_cache
path: /tmp/cache path: /tmp/cache
commands: commands:
- cd web/source - yarn --cwd ./web/source install --frozen-lockfile --cache-folder /tmp/cache
- yarn --frozen-lockfile --cache-folder /tmp/cache
- name: web-lint - name: web-lint
image: node:18-alpine image: node:18-alpine
@ -65,8 +64,7 @@ steps:
depends_on: depends_on:
- web-setup - web-setup
commands: commands:
- cd web/source - yarn --cwd ./web/source lint
- yarn run lint
- name: web-build - name: web-build
image: node:18-alpine image: node:18-alpine
@ -77,8 +75,7 @@ steps:
depends_on: depends_on:
- web-setup - web-setup
commands: commands:
- cd web/source - yarn --cwd ./web/source build
- yarn run build
- name: snapshot - name: snapshot
image: superseriousbusiness/gotosocial-drone-build:0.3.0 # https://github.com/superseriousbusiness/gotosocial-drone-build image: superseriousbusiness/gotosocial-drone-build:0.3.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
@ -194,6 +191,6 @@ steps:
--- ---
kind: signature kind: signature
hmac: 74653d67ed44ceefb7e19d6125d4a457e6308b5ef627df6325d72a0cc7d7cc0a hmac: c3efbd528a76016562f88ae435141cfb5fd6d4d07b6ad2a24ecc23cb529cc1c6
... ...

View file

@ -6,9 +6,9 @@ before:
# generate the swagger.yaml file using go-swagger and bundle it into the assets directory # generate the swagger.yaml file using go-swagger and bundle it into the assets directory
- swagger generate spec --scan-models --exclude-deps -o web/assets/swagger.yaml - swagger generate spec --scan-models --exclude-deps -o web/assets/swagger.yaml
- sed -i "s/REPLACE_ME/{{ incpatch .Version }}/" web/assets/swagger.yaml - sed -i "s/REPLACE_ME/{{ incpatch .Version }}/" web/assets/swagger.yaml
# bundle web assets # Install web deps + bundle web assets
- yarn install --cwd web/source - yarn --cwd ./web/source install
- scripts/bundle.sh - yarn --cwd ./web/source build
builds: builds:
# https://goreleaser.com/customization/build/ # https://goreleaser.com/customization/build/
- -

View file

@ -226,31 +226,44 @@ To bundle changes, you need [Node.js](https://nodejs.org/en/download/) and [Yarn
Using [NVM](https://github.com/nvm-sh/nvm) is one convenient way to install them which also supports managing different Node versions. Using [NVM](https://github.com/nvm-sh/nvm) is one convenient way to install them which also supports managing different Node versions.
To install Yarn dependencies: To install frontend dependencies:
```bash ```bash
yarn install --cwd web/source yarn --cwd web/source
``` ```
To recompile bundles: To recompile frontend bundles into `web/assets/dist`:
```bash ```bash
node web/source yarn --cwd web/source build
``` ```
#### Live Reloading #### Live Reloading
For a more convenient development environment, you can run a livereloading version of the bundler alongside the [testrig](#testing). For a more convenient development environment, you can run a livereloading version of the bundler alongside the [testrig](#testing).
Open two terminals, first start the testrig on port 8081: First build the GtS binary with DEBUG=1 to enable the testrig:
``` bash ``` bash
GTS_PORT=8081 go run ./cmd/gotosocial testrig start DEBUG=1 ./scripts/build.sh
``` ```
Now open two terminals.
In the first terminal, run the testrig on port 8081, using the binary you just built:
```bash
DEBUG=1 GTS_PORT=8081 ./gotosocial testrig start
```
Then start the bundler, which will run on port 8080, and proxy requests to the testrig instance where needed. Then start the bundler, which will run on port 8080, and proxy requests to the testrig instance where needed.
``` bash ``` bash
NODE_ENV=development node web/source NODE_ENV=development yarn --cwd ./web/source dev
``` ```
You can then log in to the GoToSocial settings panel at `http://localhost:8080/settings` and see your changes reflected in real time as the dev bundler reloads.
The livereloading bundler *will not* change the bundled assets in `dist/`, so once you are finished with changes and want to deploy it somewhere, you have to run `node web/source` to generate production-ready bundles. The livereloading bundler *will not* change the bundled assets in `dist/`, so once you are finished with changes and want to deploy it somewhere, you have to run `node web/source` to generate production-ready bundles.
### Project Structure ### Project Structure

View file

@ -12,12 +12,12 @@ WORKDIR /go/src/github.com/superseriousbusiness/gotosocial
RUN swagger generate spec -o /go/src/github.com/superseriousbusiness/gotosocial/swagger.yaml --scan-models RUN swagger generate spec -o /go/src/github.com/superseriousbusiness/gotosocial/swagger.yaml --scan-models
# stage 2: generate the web/assets/dist bundles # stage 2: generate the web/assets/dist bundles
FROM --platform=${BUILDPLATFORM} node:16.19.1-alpine3.17 AS bundler FROM --platform=${BUILDPLATFORM} node:18-alpine AS bundler
COPY web web COPY web web
RUN yarn install --cwd web/source && \ RUN yarn --cwd ./web/source install && \
BUDO_BUILD=1 node web/source && \ yarn --cwd ./web/source build && \
rm -r web/source rm -rf ./web/source
# stage 3: build the executor container # stage 3: build the executor container
FROM --platform=${TARGETPLATFORM} alpine:3.17.2 as executor FROM --platform=${TARGETPLATFORM} alpine:3.17.2 as executor

View file

@ -1,8 +0,0 @@
#!/bin/sh
# this script is really just here because GoReleaser doesn't let
# you set env vars in your 'before' commands in the free version
set -eu
BUDO_BUILD=1 node web/source

View file

@ -17,13 +17,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
module.exports = { module.exports = {
"extends": ["@joepie91/eslint-config/react"], "extends": ["@joepie91/eslint-config/react"],
"plugins": ["license-header"], "parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "license-header"],
"rules": { "rules": {
"license-header/header": ["error", __dirname + "/.license-header.js"], "license-header/header": ["error", __dirname + "/.license-header.js"],
"no-console": 'error' "no-console": 'error'
},
"parserOptions": {
"sourceType": "module"
} }
}; };

View file

@ -1,2 +1,5 @@
node_modules node_modules
public/bundle.* public/bundle.*
# Built/Typescript-compiled settings files.
settings-js

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const Photoswipe = require("photoswipe/dist/umd/photoswipe.umd.min.js"); const Photoswipe = require("photoswipe/dist/umd/photoswipe.umd.min.js");
const PhotoswipeLightbox = require("photoswipe/dist/umd/photoswipe-lightbox.umd.min.js"); const PhotoswipeLightbox = require("photoswipe/dist/umd/photoswipe-lightbox.umd.min.js");
const PhotoswipeCaptionPlugin = require("photoswipe-dynamic-caption-plugin").default; const PhotoswipeCaptionPlugin = require("photoswipe-dynamic-caption-plugin").default;

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const skulk = require("skulk"); const skulk = require("skulk");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
@ -70,10 +68,17 @@ skulk({
entryFile: "settings", entryFile: "settings",
outputFile: "settings.js", outputFile: "settings.js",
prodCfg: prodCfg, prodCfg: prodCfg,
plugin: [
// Additional settings for TS are passed from tsconfig.json.
// See: https://github.com/TypeStrong/tsify#tsconfigjson
["tsify"]
],
transform: [ transform: [
// tsify is called before babelify, so we're just babelifying
// commonjs here, no need for the typescript preset.
["babelify", { ["babelify", {
global: true, global: true,
ignore: [/node_modules\/(?!nanoid)/] ignore: [/node_modules\/(?!nanoid)/],
}] }]
], ],
presets: [ presets: [

View file

@ -6,7 +6,7 @@
"author": "f0x", "author": "f0x",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"lint": "eslint . --ext .js,.jsx", "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"build": "node index.js", "build": "node index.js",
"dev": "NODE_ENV=development node index.js" "dev": "NODE_ENV=development node index.js"
}, },
@ -29,21 +29,25 @@
"psl": "^1.9.0", "psl": "^1.9.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.0.4", "react-redux": "^8.1.3",
"redux": "^4.2.0", "redux": "^4.2.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"skulk": "^0.0.6", "skulk": "^0.0.8-fix",
"split-filter-n": "^1.1.3", "split-filter-n": "^1.1.3",
"syncpipe": "^1.0.0", "syncpipe": "^1.0.0",
"wouter": "^2.8.0-alpha.2" "wouter": "^2.8.0-alpha.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.19.6", "@babel/core": "^7.23.0",
"@babel/preset-env": "^7.19.4", "@babel/preset-env": "^7.22.20",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@browserify/envify": "^6.0.0", "@browserify/envify": "^6.0.0",
"@browserify/uglifyify": "^6.0.0", "@browserify/uglifyify": "^6.0.0",
"@joepie91/eslint-config": "^1.1.1", "@joepie91/eslint-config": "^1.1.1",
"@types/react-dom": "^18.2.8",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"babelify": "^10.0.0", "babelify": "^10.0.0",
"css-extract": "^2.0.0", "css-extract": "^2.0.0",
@ -56,6 +60,10 @@
"postcss": "^8.4.18", "postcss": "^8.4.18",
"postcss-custom-prop-vars": "^0.0.5", "postcss-custom-prop-vars": "^0.0.5",
"postcss-import": "^15.0.0", "postcss-import": "^15.0.0",
"postcss-nested": "^6.0.0" "postcss-nested": "^6.0.0",
"source-map-loader": "^4.0.1",
"ts-loader": "^9.4.4",
"tsify": "^5.0.4",
"typescript": "^5.2.2"
} }
} }

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { useRoute, Redirect } = require("wouter"); const { useRoute, Redirect } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Switch, Route, Link } = require("wouter"); const { Switch, Route, Link } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const ExpireRemote = require("./expireremote"); const ExpireRemote = require("./expireremote");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const Cleanup = require("./cleanup"); const Cleanup = require("./cleanup");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const splitFilterN = require("split-filter-n"); const splitFilterN = require("split-filter-n");
const syncpipe = require('syncpipe'); const syncpipe = require('syncpipe');

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { useRoute, Link, Redirect } = require("wouter"); const { useRoute, Link, Redirect } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Switch, Route } = require("wouter"); const { Switch, Route } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Link } = require("wouter"); const { Link } = require("wouter");
const syncpipe = require("syncpipe"); const syncpipe = require("syncpipe");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const ParseFromToot = require("./parse-from-toot"); const ParseFromToot = require("./parse-from-toot");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { useRoute, Redirect, useLocation } = require("wouter"); const { useRoute, Redirect, useLocation } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
module.exports = function ExportFormatTable() { module.exports = function ExportFormatTable() {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Switch, Route, Redirect, useLocation } = require("wouter"); const { Switch, Route, Redirect, useLocation } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../../lib/query"); const query = require("../../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Switch, Route } = require("wouter"); const { Switch, Route } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Link, useLocation } = require("wouter"); const { Link, useLocation } = require("wouter");
const { matchSorter } = require("match-sorter"); const { matchSorter } = require("match-sorter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { useRoute, Redirect } = require("wouter"); const { useRoute, Redirect } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Link, Switch, Route } = require("wouter"); const { Link, Switch, Route } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Link } = require("wouter"); const { Link } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../../lib/query"); const query = require("../../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Switch, Route, Link, Redirect, useRoute } = require("wouter"); const { Switch, Route, Link, Redirect, useRoute } = require("wouter");

View file

@ -17,23 +17,25 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict"; import { useVerifyCredentialsQuery } from "../../lib/query/oauth";
import { store } from "../../redux/store";
const React = require("react"); import React from "react";
const Redux = require("react-redux");
const query = require("../../lib/query"); import Login from "./login";
import Loading from "../loading";
import { Error } from "../error";
const Login = require("./login"); export function Authorization({ App }) {
const Loading = require("../loading"); const { loginState, expectingRedirect } = store.getState().oauth;
const { Error } = require("../error"); const skip = (loginState == "none" || loginState == "logout" || expectingRedirect);
module.exports = function Authorization({ App }) { const {
const { loginState, expectingRedirect } = Redux.useSelector((state) => state.oauth); isLoading,
isSuccess,
const { isLoading, isSuccess, data: account, error } = query.useVerifyCredentialsQuery(undefined, { data: account,
skip: loginState == "none" || loginState == "logout" || expectingRedirect error,
}); } = useVerifyCredentialsQuery(null, { skip: skip });
let showLogin = true; let showLogin = true;
let content = null; let content = null;
@ -73,4 +75,4 @@ module.exports = function Authorization({ App }) {
</section> </section>
); );
} }
}; }

View file

@ -17,18 +17,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict"; import React from "react";
const React = require("react"); import { useAuthorizeFlowMutation } from "../../lib/query/oauth";
import { useTextInput, useValue } from "../../lib/form";
import useFormSubmit from "../../lib/form/submit";
import { TextInput } from "../form/inputs";
import MutationButton from "../form/mutation-button";
import Loading from "../loading";
const query = require("../../lib/query"); export default function Login({ }) {
const { useTextInput, useValue } = require("../../lib/form");
const useFormSubmit = require("../../lib/form/submit");
const { TextInput } = require("../form/inputs");
const MutationButton = require("../form/mutation-button");
const Loading = require("../loading");
module.exports = function Login({ }) {
const form = { const form = {
instance: useTextInput("instance", { instance: useTextInput("instance", {
defaultValue: window.location.origin defaultValue: window.location.origin
@ -38,8 +36,11 @@ module.exports = function Login({ }) {
const [formSubmit, result] = useFormSubmit( const [formSubmit, result] = useFormSubmit(
form, form,
query.useAuthorizeFlowMutation(), useAuthorizeFlowMutation(),
{ changedOnly: false } {
changedOnly: false,
onFinish: undefined,
}
); );
if (result.isLoading) { if (result.isLoading) {
@ -63,7 +64,11 @@ module.exports = function Login({ }) {
label="Instance" label="Instance"
name="instance" name="instance"
/> />
<MutationButton label="Login" result={result} /> <MutationButton
label="Login"
result={result}
disabled={false}
/>
</form> </form>
); );
}; }

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Link } = require("wouter"); const { Link } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
module.exports = function CheckList({ field, header = "All", EntryComponent, getExtraProps }) { module.exports = function CheckList({ field, header = "All", EntryComponent, getExtraProps }) {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { const {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
function ErrorFallback({ error, resetErrorBoundary }) { function ErrorFallback({ error, resetErrorBoundary }) {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
module.exports = function FakeProfile({ avatar, header, display_name, username, role }) { module.exports = function FakeProfile({ avatar, header, display_name, username, role }) {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../lib/query"); const query = require("../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
function TextInput({ label, field, ...inputProps }) { function TextInput({ label, field, ...inputProps }) {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Error } = require("../error"); const { Error } = require("../error");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const langs = require("langs"); const langs = require("langs");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
module.exports = function Loading() { module.exports = function Loading() {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../lib/query"); const query = require("../lib/query");

View file

@ -17,17 +17,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const ReactDom = require("react-dom/client"); const ReactDom = require("react-dom/client");
const { Provider } = require("react-redux"); const { Provider } = require("react-redux");
const { PersistGate } = require("redux-persist/integration/react"); const { PersistGate } = require("redux-persist/integration/react");
const { store, persistor } = require("./redux"); const { store, persistor } = require("./redux/store");
const { createNavigation, Menu, Item } = require("./lib/navigation"); const { createNavigation, Menu, Item } = require("./lib/navigation");
const AuthorizationGate = require("./components/authorization"); const { Authorization } = require("./components/authorization");
const Loading = require("./components/loading"); const Loading = require("./components/loading");
const UserLogoutCard = require("./components/user-logout-card"); const UserLogoutCard = require("./components/user-logout-card");
const { RoleContext } = require("./lib/navigation/util"); const { RoleContext } = require("./lib/navigation/util");
@ -90,7 +88,7 @@ function Main() {
return ( return (
<Provider store={store}> <Provider store={store}>
<PersistGate loading={<section><Loading /></section>} persistor={persistor}> <PersistGate loading={<section><Loading /></section>} persistor={persistor}>
<AuthorizationGate App={App} /> <Authorization App={App} />
</PersistGate> </PersistGate>
</Provider> </Provider>
); );

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const isValidDomain = require("is-valid-domain"); const isValidDomain = require("is-valid-domain");
const psl = require("psl"); const psl = require("psl");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const _default = false; const _default = false;

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const syncpipe = require("syncpipe"); const syncpipe = require("syncpipe");
const { createSlice } = require("@reduxjs/toolkit"); const { createSlice } = require("@reduxjs/toolkit");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { useComboboxState } = require("ariakit/combobox"); const { useComboboxState } = require("ariakit/combobox");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const FormContext = React.createContext({}); const FormContext = React.createContext({});

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const getFormMutations = require("./get-form-mutations"); const getFormMutations = require("./get-form-mutations");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const prettierBytes = require("prettier-bytes"); const prettierBytes = require("prettier-bytes");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Error } = require("../../components/error"); const { Error } = require("../../components/error");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const syncpipe = require("syncpipe"); const syncpipe = require("syncpipe");
module.exports = function getFormMutations(form, { changedOnly }) { module.exports = function getFormMutations(form, { changedOnly }) {

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const getByDot = require("get-by-dot").default; const getByDot = require("get-by-dot").default;

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const _default = ""; const _default = "";

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const Promise = require("bluebird"); const Promise = require("bluebird");
const React = require("react"); const React = require("react");
const getFormMutations = require("./get-form-mutations"); const getFormMutations = require("./get-form-mutations");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const _default = ""; const _default = "";

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { Link, Route, Redirect, Switch, useLocation, useRouter } = require("wouter"); const { Link, Route, Redirect, Switch, useLocation, useRouter } = require("wouter");
const syncpipe = require("syncpipe"); const syncpipe = require("syncpipe");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const { nanoid } = require("nanoid"); const { nanoid } = require("nanoid");
const { Redirect } = require("wouter"); const { Redirect } = require("wouter");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const RoleContext = React.createContext([]); const RoleContext = React.createContext([]);
const BaseUrlContext = React.createContext(null); const BaseUrlContext = React.createContext(null);

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const Promise = require("bluebird"); const Promise = require("bluebird");
const { unwrapRes } = require("../lib"); const { unwrapRes } = require("../lib");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const Promise = require("bluebird"); const Promise = require("bluebird");
const fileDownload = require("js-file-download"); const fileDownload = require("js-file-download");
const csv = require("papaparse"); const csv = require("papaparse");

View file

@ -17,15 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const { const {
replaceCacheOnMutation, replaceCacheOnMutation,
removeFromCacheOnMutation, removeFromCacheOnMutation,
domainListToObject, domainListToObject,
idListToObject idListToObject
} = require("../lib"); } = require("../lib");
const base = require("../base"); const { gtsApi } = require("../gts-api");
const endpoints = (build) => ({ const endpoints = (build) => ({
updateInstance: build.mutation({ updateInstance: build.mutation({
@ -164,4 +162,4 @@ const endpoints = (build) => ({
...require("./reports")(build) ...require("./reports")(build)
}); });
module.exports = base.injectEndpoints({ endpoints }); module.exports = gtsApi.injectEndpoints({ endpoints });

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
module.exports = (build) => ({ module.exports = (build) => ({
listReports: build.query({ listReports: build.query({
query: (params = {}) => ({ query: (params = {}) => ({

View file

@ -1,70 +0,0 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const { createApi, fetchBaseQuery } = require("@reduxjs/toolkit/query/react");
const { serialize: serializeForm } = require("object-to-formdata");
function instanceBasedQuery(args, api, extraOptions) {
const state = api.getState();
const { instance, token } = state.oauth;
if (args.baseUrl == undefined) {
args.baseUrl = instance;
}
if (args.discardEmpty) {
if (args.body == undefined || Object.keys(args.body).length == 0) {
return { data: null };
}
delete args.discardEmpty;
}
if (args.asForm) {
delete args.asForm;
args.body = serializeForm(args.body, {
indices: true, // Array indices, for profile fields
});
}
return fetchBaseQuery({
baseUrl: args.baseUrl,
prepareHeaders: (headers) => {
if (token != undefined) {
headers.set('Authorization', token);
}
headers.set("Accept", "application/json");
return headers;
},
})(args, api, extraOptions);
}
module.exports = createApi({
reducerPath: "api",
baseQuery: instanceBasedQuery,
tagTypes: ["Auth", "Emoji", "Reports", "Account", "InstanceRules"],
endpoints: (build) => ({
instance: build.query({
query: () => ({
url: `/api/v1/instance`
})
})
})
});

View file

@ -0,0 +1,149 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query/react';
import { serialize as serializeForm } from "object-to-formdata";
import type { RootState } from '../../redux/store';
/**
* GTSFetchArgs extends standard FetchArgs used by
* RTK Query with a couple helpers of our own.
*/
export interface GTSFetchArgs extends FetchArgs {
/**
* If provided, will be used as base URL. Else,
* will fall back to authorized instance as baseUrl.
*/
baseUrl?: string;
/**
* If true, and no args.body is set, or args.body is empty,
* then a null response will be returned from the API call.
*/
discardEmpty?: boolean;
/**
* If true, then args.body will be serialized
* as FormData before submission.
*/
asForm?: boolean;
}
/**
* gtsBaseQuery wraps the redux toolkit fetchBaseQuery with some helper functionality.
*
* For an explainer of what's happening in this function, see:
* - https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#customizing-queries-with-basequery
* - https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#constructing-a-dynamic-base-url-using-redux-state
*
* @param args
* @param api
* @param extraOptions
* @returns
*/
const gtsBaseQuery: BaseQueryFn<
string | GTSFetchArgs,
any,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
// Retrieve state at the moment
// this function was called.
const state = api.getState() as RootState;
const { instanceUrl, token } = state.oauth;
// Derive baseUrl dynamically.
let baseUrl: string;
// Check if simple string baseUrl provided
// as args, or if more complex args provided.
if (typeof args === "string") {
baseUrl = args;
} else {
if (args.baseUrl != undefined) {
baseUrl = args.baseUrl;
} else {
baseUrl = instanceUrl;
}
if (args.discardEmpty) {
if (args.body == undefined || Object.keys(args.body).length == 0) {
return { data: null };
}
}
if (args.asForm) {
args.body = serializeForm(args.body, {
// Array indices, for profile fields.
indices: true,
});
}
// Delete any of our extended arguments
// to avoid confusing fetchBaseQuery.
delete args.baseUrl;
delete args.discardEmpty;
delete args.asForm;
}
if (!baseUrl) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: {"error":"No baseUrl set for request"},
},
};
}
return fetchBaseQuery({
baseUrl: baseUrl,
prepareHeaders: (headers) => {
if (token != undefined) {
headers.set('Authorization', token);
}
headers.set("Accept", "application/json");
return headers;
},
})(args, api, extraOptions);
};
export const gtsApi = createApi({
reducerPath: "api",
baseQuery: gtsBaseQuery,
tagTypes: [
"Auth",
"Emoji",
"Reports",
"Account",
"InstanceRules",
],
endpoints: (builder) => ({
instance: builder.query<any, void>({
query: () => ({
url: `/api/v1/instance`
})
})
})
});
export const { useInstanceQuery } = gtsApi;

View file

@ -17,11 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
module.exports = { module.exports = {
...require("./base"), ...require("./gts-api"),
...require("./oauth"), ...require("./oauth"),
...require("./user"), ...require("./user"),
...require("./admin") ...require("./admin")
}; };

View file

@ -17,10 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const syncpipe = require("syncpipe"); const syncpipe = require("syncpipe");
const base = require("./base"); const { gtsApi } = require("./gts-api");
module.exports = { module.exports = {
unwrapRes(res) { unwrapRes(res) {
@ -70,7 +68,7 @@ function makeCacheMutation(action) {
return { return {
onQueryStarted: (_, { dispatch, queryFulfilled }) => { onQueryStarted: (_, { dispatch, queryFulfilled }) => {
queryFulfilled.then(({ data: newData }) => { queryFulfilled.then(({ data: newData }) => {
dispatch(base.util.updateQueryData(queryName, arg, (draft) => { dispatch(gtsApi.util.updateQueryData(queryName, arg, (draft) => {
if (findKey != undefined) { if (findKey != undefined) {
key = findKey(draft, newData); key = findKey(draft, newData);
} }

View file

@ -1,160 +0,0 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const Promise = require("bluebird");
const base = require("./base");
const { unwrapRes } = require("./lib");
const oauth = require("../../redux/oauth").actions;
function getSettingsURL() {
/* needed in case the settings interface isn't hosted at /settings but
some subpath like /gotosocial/settings. Other parts of the code don't
take this into account yet so mostly future-proofing.
Also drops anything past /settings/, because authorization urls that are too long
get rejected by GTS.
*/
let [pre, _past] = window.location.pathname.split("/settings");
return `${window.location.origin}${pre}/settings`;
}
const SETTINGS_URL = getSettingsURL();
const endpoints = (build) => ({
verifyCredentials: build.query({
providesTags: (_res, error) =>
error == undefined
? ["Auth"]
: [],
queryFn: (_arg, api, _extraOpts, baseQuery) => {
const state = api.getState();
return Promise.try(() => {
// Process callback code first, if available
if (state.oauth.loginState == "callback") {
let urlParams = new URLSearchParams(window.location.search);
let code = urlParams.get("code");
if (code == undefined) {
throw {
message: "Waiting for callback, but no ?code= provided in url."
};
} else {
let app = state.oauth.registration;
if (app == undefined || app.client_id == undefined) {
throw {
message: "No stored registration data, can't finish login flow."
};
}
return baseQuery({
method: "POST",
url: "/oauth/token",
body: {
client_id: app.client_id,
client_secret: app.client_secret,
redirect_uri: SETTINGS_URL,
grant_type: "authorization_code",
code: code
}
}).then(unwrapRes).then((token) => {
// remove ?code= from url
window.history.replaceState({}, document.title, window.location.pathname);
api.dispatch(oauth.setToken(token));
});
}
}
}).then(() => {
return baseQuery({
url: `/api/v1/accounts/verify_credentials`
});
}).catch((e) => {
return { error: e };
});
}
}),
authorizeFlow: build.mutation({
queryFn: (formData, api, _extraOpts, baseQuery) => {
let instance;
const state = api.getState();
return Promise.try(() => {
if (!formData.instance.startsWith("http")) {
formData.instance = `https://${formData.instance}`;
}
instance = new URL(formData.instance).origin;
const stored = state.oauth.instance;
if (stored?.instance == instance && stored.registration) {
return stored.registration;
}
return baseQuery({
method: "POST",
baseUrl: instance,
url: "/api/v1/apps",
body: {
client_name: "GoToSocial Settings",
scopes: formData.scopes,
redirect_uris: SETTINGS_URL,
website: SETTINGS_URL
}
}).then(unwrapRes).then((app) => {
app.scopes = formData.scopes;
api.dispatch(oauth.authorize({
instance: instance,
registration: app,
loginState: "callback",
expectingRedirect: true
}));
return app;
});
}).then((app) => {
let url = new URL(instance);
url.pathname = "/oauth/authorize";
url.searchParams.set("client_id", app.client_id);
url.searchParams.set("redirect_uri", SETTINGS_URL);
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", app.scopes);
let redirectURL = url.toString();
window.location.assign(redirectURL);
return { data: null };
}).catch((e) => {
return { error: e };
});
},
}),
logout: build.mutation({
queryFn: (_arg, api) => {
api.dispatch(oauth.remove());
return { data: null };
},
invalidatesTags: ["Auth"]
})
});
module.exports = base.injectEndpoints({ endpoints });

View file

@ -0,0 +1,204 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { gtsApi } from "../gts-api";
import {
setToken as oauthSetToken,
remove as oauthRemove,
authorize as oauthAuthorize,
} from "../../../redux/oauth";
import { RootState } from '../../../redux/store';
export interface OauthTokenRequestBody {
client_id: string;
client_secret: string;
redirect_uri: string;
grant_type: string;
code: string;
}
function getSettingsURL() {
/*
needed in case the settings interface isn't hosted at /settings but
some subpath like /gotosocial/settings. Other parts of the code don't
take this into account yet so mostly future-proofing.
Also drops anything past /settings/, because authorization urls that are too long
get rejected by GTS.
*/
let [pre, _past] = window.location.pathname.split("/settings");
return `${window.location.origin}${pre}/settings`;
}
const SETTINGS_URL = (getSettingsURL());
// Couple auth functions here require multiple requests as
// part of an OAuth token 'flow'. To keep things simple for
// callers of these query functions, the multiple requests
// are chained within one query.
//
// https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#performing-multiple-requests-with-a-single-query
const extended = gtsApi.injectEndpoints({
endpoints: (builder) => ({
verifyCredentials: builder.query<any, void>({
providesTags: (_res, error) =>
error == undefined ? ["Auth"] : [],
async queryFn(_arg, api, _extraOpts, fetchWithBQ) {
const state = api.getState() as RootState;
const oauthState = state.oauth;
// If we're not in the middle of an auth/callback,
// we may already have an auth token, so just
// return a standard verify_credentials query.
if (oauthState.loginState != 'callback') {
return fetchWithBQ({
url: `/api/v1/accounts/verify_credentials`
});
}
// We're in the middle of an auth/callback flow.
// Try to retrieve callback code from URL query.
let urlParams = new URLSearchParams(window.location.search);
let code = urlParams.get("code");
if (code == undefined) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: {"error":"Waiting for callback, but no ?code= provided in url."},
},
};
}
// Retrieve app with which the
// callback code was generated.
let app = oauthState.app;
if (app == undefined || app.client_id == undefined) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: {"error":"No stored app registration data, can't finish login flow."},
},
};
}
// Use the provided code and app
// secret to request an auth token.
const tokenReqBody: OauthTokenRequestBody = {
client_id: app.client_id,
client_secret: app.client_secret,
redirect_uri: SETTINGS_URL,
grant_type: "authorization_code",
code: code
};
const tokenResult = await fetchWithBQ({
method: "POST",
url: "/oauth/token",
body: tokenReqBody,
});
if (tokenResult.error) {
return { error: tokenResult.error as FetchBaseQueryError };
}
// Remove ?code= query param from
// url, we don't want it anymore.
window.history.replaceState({}, document.title, window.location.pathname);
// Store returned token in redux.
api.dispatch(oauthSetToken(tokenResult.data));
// We're now authed! So return
// standard verify_credentials query.
return fetchWithBQ({
url: `/api/v1/accounts/verify_credentials`
});
}
}),
authorizeFlow: builder.mutation({
async queryFn(formData, api, _extraOpts, fetchWithBQ) {
const state = api.getState() as RootState;
const oauthState = state.oauth;
let instanceUrl: string;
if (!formData.instance.startsWith("http")) {
formData.instance = `https://${formData.instance}`;
}
instanceUrl = new URL(formData.instance).origin;
if (oauthState?.instanceUrl == instanceUrl && oauthState.app) {
return { data: oauthState.app };
}
const appResult = await fetchWithBQ({
method: "POST",
baseUrl: instanceUrl,
url: "/api/v1/apps",
body: {
client_name: "GoToSocial Settings",
scopes: formData.scopes,
redirect_uris: SETTINGS_URL,
website: SETTINGS_URL
}
});
if (appResult.error) {
return { error: appResult.error as FetchBaseQueryError };
}
let app = appResult.data as any;
app.scopes = formData.scopes;
api.dispatch(oauthAuthorize({
instanceUrl: instanceUrl,
app: app,
loginState: "callback",
expectingRedirect: true
}));
let url = new URL(instanceUrl);
url.pathname = "/oauth/authorize";
url.searchParams.set("client_id", app.client_id);
url.searchParams.set("redirect_uri", SETTINGS_URL);
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", app.scopes);
let redirectURL = url.toString();
window.location.assign(redirectURL);
return { data: null };
},
}),
logout: builder.mutation({
queryFn: (_arg, api) => {
api.dispatch(oauthRemove());
return { data: null };
},
invalidatesTags: ["Auth"]
})
})
});
export const {
useVerifyCredentialsQuery,
useAuthorizeFlowMutation,
useLogoutMutation,
} = extended;

View file

@ -17,29 +17,32 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict"; import { replaceCacheOnMutation } from "../lib";
import { gtsApi } from "../gts-api";
const { replaceCacheOnMutation } = require("./lib"); const extended = gtsApi.injectEndpoints({
const base = require("./base"); endpoints: (builder) => ({
updateCredentials: builder.mutation({
const endpoints = (build) => ({ query: (formData) => ({
updateCredentials: build.mutation({ method: "PATCH",
query: (formData) => ({ url: `/api/v1/accounts/update_credentials`,
method: "PATCH", asForm: true,
url: `/api/v1/accounts/update_credentials`, body: formData,
asForm: true, discardEmpty: true
body: formData, }),
discardEmpty: true ...replaceCacheOnMutation("verifyCredentials")
}), }),
...replaceCacheOnMutation("verifyCredentials") passwordChange: builder.mutation({
}), query: (data) => ({
passwordChange: build.mutation({ method: "POST",
query: (data) => ({ url: `/api/v1/user/password_change`,
method: "POST", body: data
url: `/api/v1/user/password_change`, })
body: data
}) })
}) })
}); });
module.exports = base.injectEndpoints({ endpoints }); export const {
useUpdateCredentialsMutation,
usePasswordChangeMutation,
} = extended;

View file

@ -1,44 +0,0 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const { createSlice } = require("@reduxjs/toolkit");
module.exports = createSlice({
name: "oauth",
initialState: {
loginState: 'none',
expectingRedirect: false
},
reducers: {
authorize: (state, { payload }) => {
return payload; // overrides state
},
setToken: (state, { payload }) => {
state.token = `${payload.token_type} ${payload.access_token}`;
state.loginState = "login";
},
remove: (state, { _payload }) => {
delete state.token;
delete state.registration;
state.loginState = "logout";
}
}
});

View file

@ -0,0 +1,89 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
/**
* OAuthToken represents a response
* to an OAuth token request.
*/
export interface OAuthToken {
/**
* Most likely to be 'Bearer'
* but may be something else.
*/
token_type: string;
/**
* The actual token. Can be passed in to
* authenticate further requests using the
* Authorization header and the token type.
*/
access_token: string;
}
export interface OAuthApp {
client_id: string;
client_secret: string;
}
export interface OAuthState {
instanceUrl?: string;
loginState: "none" | "callback" | "login" | "logout";
expectingRedirect: boolean;
/**
* Token stored in easy-to-use format.
* Will look something like:
* "Authorization: Bearer BLAHBLAHBLAH"
*/
token?: string;
app?: OAuthApp;
}
const initialState: OAuthState = {
loginState: 'none',
expectingRedirect: false,
};
export const oauthSlice = createSlice({
name: "oauth",
initialState: initialState,
reducers: {
authorize: (_state, action: PayloadAction<OAuthState>) => {
// Overrides state with payload.
return action.payload;
},
setToken: (state, action: PayloadAction<OAuthToken>) => {
// Mark us as logged in by storing token.
state.token = `${action.payload.token_type} ${action.payload.access_token}`;
state.loginState = "login";
},
remove: (state) => {
// Mark us as logged out by clearing auth.
delete state.token;
delete state.app;
state.loginState = "logout";
}
}
});
export const {
authorize,
setToken,
remove,
} = oauthSlice.actions;

View file

@ -17,11 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict"; import { combineReducers } from "redux";
import { configureStore } from "@reduxjs/toolkit";
const { combineReducers } = require("redux"); import {
const { configureStore } = require("@reduxjs/toolkit");
const {
persistStore, persistStore,
persistReducer, persistReducer,
FLUSH, FLUSH,
@ -30,14 +28,14 @@ const {
PERSIST, PERSIST,
PURGE, PURGE,
REGISTER, REGISTER,
} = require("redux-persist"); } from "redux-persist";
const query = require("../lib/query/base"); import { oauthSlice } from "./oauth";
const { Promise } = require("bluebird"); import { gtsApi } from "../lib/query/gts-api";
const combinedReducers = combineReducers({ const combinedReducers = combineReducers({
oauth: require("./oauth").reducer, [gtsApi.reducerPath]: gtsApi.reducer,
[query.reducerPath]: query.reducer oauth: oauthSlice.reducer,
}); });
const persistedReducer = persistReducer({ const persistedReducer = persistReducer({
@ -45,27 +43,41 @@ const persistedReducer = persistReducer({
storage: require("redux-persist/lib/storage").default, storage: require("redux-persist/lib/storage").default,
stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel1").default, stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel1").default,
whitelist: ["oauth"], whitelist: ["oauth"],
migrate: (state) => { migrate: async (state) => {
return Promise.try(() => { if (state == undefined) {
if (state?.oauth != undefined) {
state.oauth.expectingRedirect = false;
}
return state; return state;
}); }
// This is a cheeky workaround for
// redux-persist being a stickler.
let anyState = state as any;
if (anyState?.oauth != undefined) {
anyState.oauth.expectingRedirect = false;
}
return anyState;
} }
}, combinedReducers); }, combinedReducers);
const store = configureStore({ export const store = configureStore({
reducer: persistedReducer, reducer: persistedReducer,
middleware: (getDefaultMiddleware) => { middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware({ return getDefaultMiddleware({
serializableCheck: { serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER] ignoredActions: [
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
]
} }
}).concat(query.middleware); }).concat(gtsApi.middleware);
} }
}); });
const persistor = persistStore(store); export const persistor = persistStore(store);
module.exports = { store, persistor }; export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../lib/query"); const query = require("../lib/query");

View file

@ -17,8 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
"use strict";
const React = require("react"); const React = require("react");
const query = require("../lib/query"); const query = require("../lib/query");

109
web/source/tsconfig.json Normal file
View file

@ -0,0 +1,109 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react" , /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
"noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": false, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

File diff suppressed because it is too large Load diff