diff --git a/package-lock.json b/package-lock.json
index 470f139..a26919a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,8 +11,9 @@
"@react-navigation/native": "5.1.1",
"@react-navigation/stack": "5.2.3",
"expo": "^38.0.9",
- "expo-linking": "^1.0.3",
+ "expo-linking": "^1.0.7",
"expo-status-bar": "^1.0.2",
+ "expo-web-browser": "~8.3.1",
"react": "~16.11.0",
"react-dom": "~16.11.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
@@ -2867,6 +2868,17 @@
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
},
+ "node_modules/compare-urls": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/compare-urls/-/compare-urls-2.0.0.tgz",
+ "integrity": "sha512-eCJcWn2OYFEIqbm70ta7LQowJOOZZqq1a2YbbFCFI1uwSvj+TWMwXVn7vPR1ceFNcAIt5RSTDbwdlX82gYLTkA==",
+ "dependencies": {
+ "normalize-url": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/compare-versions": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
@@ -3610,13 +3622,25 @@
}
},
"node_modules/expo-linking": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-1.0.3.tgz",
- "integrity": "sha512-Bzm8qVlSRBmoQcnveBBLO4ea/AEW4t10WFiJ18m/w8OnEXbsP7WT39UyhTuFsrnjYSLMjW4/JXkfmswBOhISuA==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-1.0.7.tgz",
+ "integrity": "sha512-AhvntaU1PItf73Tm5J7tUVEgCjz/7SeuXg4vLCQgFsEywU0E4Z/jgfQ+30omrrvalBKL3L18cHG9Y4pWmROqgg==",
"dependencies": {
- "expo-constants": "~9.1.1",
+ "expo-constants": "~9.2.0",
"qs": "^6.5.0",
"url-parse": "^1.4.4"
+ },
+ "peerDependencies": {
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-linking/node_modules/expo-constants": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-9.2.0.tgz",
+ "integrity": "sha512-WKwiEMvBgPrEPEyZKm21UUB2KWQux9OCWf6ZDORLTln7kO3rsbaJEprfWUWTP7AxyaLMYfN+/0WFHjZc25SZWQ==",
+ "dependencies": {
+ "fbjs": "1.0.0",
+ "uuid": "^3.3.2"
}
},
"node_modules/expo-location": {
@@ -3654,6 +3678,18 @@
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.0.2.tgz",
"integrity": "sha512-5313u744GcLzCadxIPXyTkYw77++UXv1dXCuhYDxDbtsEf93iMra7WSvzyE8a7mRQLIIPRuGnBOdrL/V1C7EOQ=="
},
+ "node_modules/expo-web-browser": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-8.3.1.tgz",
+ "integrity": "sha512-mDxSNpc/Ww/RX6MhmPRUWo2xNi8HGZ1TDMqIjTvUzrL7pGG9VerX0EDMhfLgo6c7KVOY1ngbTyybApZTXgPCOQ==",
+ "dependencies": {
+ "compare-urls": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo/node_modules/babel-preset-expo": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-8.2.3.tgz",
@@ -4658,6 +4694,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -6203,6 +6247,40 @@
"node": ">=0.10.0"
}
},
+ "node_modules/normalize-url": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
+ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==",
+ "dependencies": {
+ "prepend-http": "^2.0.0",
+ "query-string": "^5.0.1",
+ "sort-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/normalize-url/node_modules/query-string": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
+ "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
+ "dependencies": {
+ "decode-uri-component": "^0.2.0",
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-url/node_modules/strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -6716,6 +6794,14 @@
"resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz",
"integrity": "sha1-/mOhfal3YRq+98uAJssalVP9g1k="
},
+ "node_modules/prepend-http": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/pretty-format": {
"version": "23.6.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
@@ -7980,6 +8066,17 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
+ "node_modules/sort-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+ "dependencies": {
+ "is-plain-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -11522,6 +11619,14 @@
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
},
+ "compare-urls": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/compare-urls/-/compare-urls-2.0.0.tgz",
+ "integrity": "sha512-eCJcWn2OYFEIqbm70ta7LQowJOOZZqq1a2YbbFCFI1uwSvj+TWMwXVn7vPR1ceFNcAIt5RSTDbwdlX82gYLTkA==",
+ "requires": {
+ "normalize-url": "^2.0.1"
+ }
+ },
"compare-versions": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
@@ -12249,13 +12354,24 @@
}
},
"expo-linking": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-1.0.3.tgz",
- "integrity": "sha512-Bzm8qVlSRBmoQcnveBBLO4ea/AEW4t10WFiJ18m/w8OnEXbsP7WT39UyhTuFsrnjYSLMjW4/JXkfmswBOhISuA==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-1.0.7.tgz",
+ "integrity": "sha512-AhvntaU1PItf73Tm5J7tUVEgCjz/7SeuXg4vLCQgFsEywU0E4Z/jgfQ+30omrrvalBKL3L18cHG9Y4pWmROqgg==",
"requires": {
- "expo-constants": "~9.1.1",
+ "expo-constants": "~9.2.0",
"qs": "^6.5.0",
"url-parse": "^1.4.4"
+ },
+ "dependencies": {
+ "expo-constants": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-9.2.0.tgz",
+ "integrity": "sha512-WKwiEMvBgPrEPEyZKm21UUB2KWQux9OCWf6ZDORLTln7kO3rsbaJEprfWUWTP7AxyaLMYfN+/0WFHjZc25SZWQ==",
+ "requires": {
+ "fbjs": "1.0.0",
+ "uuid": "^3.3.2"
+ }
+ }
}
},
"expo-location": {
@@ -12293,6 +12409,14 @@
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.0.2.tgz",
"integrity": "sha512-5313u744GcLzCadxIPXyTkYw77++UXv1dXCuhYDxDbtsEf93iMra7WSvzyE8a7mRQLIIPRuGnBOdrL/V1C7EOQ=="
},
+ "expo-web-browser": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-8.3.1.tgz",
+ "integrity": "sha512-mDxSNpc/Ww/RX6MhmPRUWo2xNi8HGZ1TDMqIjTvUzrL7pGG9VerX0EDMhfLgo6c7KVOY1ngbTyybApZTXgPCOQ==",
+ "requires": {
+ "compare-urls": "^2.0.0"
+ }
+ },
"extend-shallow": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
@@ -13034,6 +13158,11 @@
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
+ },
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -14322,6 +14451,33 @@
"remove-trailing-separator": "^1.0.1"
}
},
+ "normalize-url": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
+ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==",
+ "requires": {
+ "prepend-http": "^2.0.0",
+ "query-string": "^5.0.1",
+ "sort-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "query-string": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
+ "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
+ "requires": {
+ "decode-uri-component": "^0.2.0",
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
+ }
+ }
+ },
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -14708,6 +14864,11 @@
"resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz",
"integrity": "sha1-/mOhfal3YRq+98uAJssalVP9g1k="
},
+ "prepend-http": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
+ },
"pretty-format": {
"version": "23.6.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
@@ -15789,6 +15950,14 @@
}
}
},
+ "sort-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+ "requires": {
+ "is-plain-obj": "^1.0.0"
+ }
+ },
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
diff --git a/package.json b/package.json
index fa0ed34..ede1137 100644
--- a/package.json
+++ b/package.json
@@ -14,8 +14,9 @@
"@react-navigation/native": "5.1.1",
"@react-navigation/stack": "5.2.3",
"expo": "^38.0.9",
- "expo-linking": "^1.0.3",
+ "expo-linking": "^1.0.7",
"expo-status-bar": "^1.0.2",
+ "expo-web-browser": "~8.3.1",
"react": "~16.11.0",
"react-dom": "~16.11.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
diff --git a/src/components/pages/authenticate.js b/src/components/pages/authenticate.js
index aa48eee..5a665cf 100644
--- a/src/components/pages/authenticate.js
+++ b/src/components/pages/authenticate.js
@@ -10,41 +10,101 @@ import {
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
+import * as Linking from "expo-linking";
+import * as WebBrowser from "expo-web-browser";
+import Constants from "expo-constants";
-const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
-const TEST_PROFILE = {
- username: "njms",
- acct: "njms",
- display_name: "Nat🔆",
- locked: false,
- bot: false,
- note: "Yeah heart emoji.",
- avatar: TEST_IMAGE,
- followers_count: "1 jillion",
- statuses_count: 334,
- fields: [
- {
- name: "Blog",
- value: "https://njms.ca",
- verified_at: "some time"
- },
- {
- name: "Github",
- value: "https://github.com/natjms",
- verified_at: null
- }
- ]
-};
+async function postForm(url, data, token = false) {
+ // Send a POST request with data formatted with FormData returning JSON
+ let form = new FormData();
+ for (let key in data) {
+ form.append(key, data[key]);
+ }
+
+ const response = await fetch(url, {
+ method: "POST",
+ body: form,
+ headers: token
+ ? { "Authorization": `Bearer ${token}`, }
+ : {},
+ });
+
+ return response.json();
+}
+
+async function get(url, token = false) {
+ const response = await fetch(url, {
+ method: "GET",
+ headers: token
+ ? { "Authorization": `Bearer ${token}`, }
+ : {},
+ });
+ return response.json();
+}
const AuthenticateJsx = ({navigation}) => {
+ const REDIRECT_URI = Linking.makeUrl("authenticate");
const [state, setState] = useState({
- acct: "",
- password: "",
+ instance: "",
authChecked: false,
});
+ const _handleUrl = async ({ url }) => {
+ // When the app is foregrounded after authorizing the app from their
+ // instance's website...
+ if (Constants.platform.ios) {
+ WebBrowser.dismissBrowser();
+ } else {
+ Linking.removeEventListener("url", _handleUrl)
+ }
+
+ const { path, queryParams } = Linking.parse(url);
+
+ const instance = await AsyncStorage.getItem("@user_instance");
+ const api = `https://${instance}`;
+ const app = JSON.parse(
+ await AsyncStorage.getItem("@app_object")
+ );
+
+ // Fetch the access token
+ const tokenRequestBody = {
+ client_id: app.client_id,
+ client_secret: app.client_secret,
+ redirect_uri: REDIRECT_URI,
+ grant_type: "authorization_code",
+ code: queryParams.code,
+ scope: "read write follow push",
+ };
+
+ const token = await postForm(`${api}/oauth/token`, tokenRequestBody);
+
+ // Store the token
+ AsyncStorage.setItem("@user_token", JSON.stringify(token));
+
+ const profile = await get(
+ `${api}/api/v1/accounts/verify_credentials`,
+ token.access_token
+ );
+
+ await AsyncStorage.multiSet([
+ [ "@user_profile", JSON.stringify(profile), ],
+ [ // TODO: Enable storing notifications
+ "@user_notifications",
+ JSON.stringify({
+ unread: false,
+ memory: []
+ }),
+ ],
+ ]);
+
+ navigation.navigate("Feed");
+ };
+
useEffect(() => {
- AsyncStorage.getItem("@user_profile").then((profile) => {
+ Linking.addEventListener("url", _handleUrl);
+ AsyncStorage
+ .getItem("@user_profile")
+ .then(profile => {
if (profile) {
navigation.navigate("Feed");
} else {
@@ -53,21 +113,44 @@ const AuthenticateJsx = ({navigation}) => {
});
}, []);
- const loginCallback = () => {
- const initialization = [
- [ "@user_profile", JSON.stringify(TEST_PROFILE) ],
- [
- "@user_notifications",
- JSON.stringify({
- unread: false,
- memory: [{ id: 1 }, { id: 2 }],
- })
- ]
- ];
+ const _login = async () => {
+ const url = `https://${state.instance}`;
- AsyncStorage.multiSet(initialization).then(() => {
- navigation.navigate("Feed");
- });
+ let appJSON = await AsyncStorage.getItem("@app_object");
+ let app;
+
+ // Ensure the app has been created
+ if (appJSON == null) {
+ // Register app: https://docs.joinmastodon.org/methods/apps/#create-an-application
+ app = await postForm(`${url}/api/v1/apps`, {
+ client_name: "Resin",
+ redirect_uris: REDIRECT_URI,
+ scopes: "read write follow push",
+ website: "https://github.com/natjms/resin",
+ });
+
+ await AsyncStorage
+ .setItem("@app_object", JSON.stringify(app))
+ } else {
+ // The app has already been registered
+ app = JSON.parse(appJSON);
+ }
+
+ // Store the domain name of the instance for use in
+ // the _handleUrl callback
+ // NOTE: state.instance is not accessible from _handleUrl; this
+ // probably has something to do with the fact that the app loses
+ // focus when WebBrowser.openAuthSessionAsync gets called.
+ await AsyncStorage.setItem("@user_instance", state.instance);
+
+ // Get the user to authorize the app
+ await WebBrowser.openAuthSessionAsync(
+ `${url}/oauth/authorize`
+ + `?client_id=${app.client_id}`
+ + `&scope=read+write+follow+push`
+ + `&redirect_uri=${REDIRECT_URI}`
+ + `&response_type=code`
+ );
};
return (
@@ -81,25 +164,18 @@ const AuthenticateJsx = ({navigation}) => {
resizeMode = { "contain" }
source = { require("assets/logo/logo-standalone.png") }/>
- Account name
+ Instance domain name
setState({ ...state, acct: value })
+ value => setState({ ...state, instance: value })
}/>
- Password
- setState({ ...state, password: value })
- }/>
+ onPress = { _login }>
Login