Integrate react-native-network-client (#5499)

* fix: handle NSMutableData

* feat: integrate react-native-network-client

* fix: typos

* fix: semicolon

* fix: rename to urlVersion

* fix: add returnDataOnly arg

* fix: configure network client

* fix: headers

* fix: handling of serverVersion

* fix: rename requests to actions

* fix: action imports

* fix: no need to stringify body

* fix: sso flow

* fix: address PR feedback

* fix: invalidate client on logout

* fix: address PR feedback take 2

* fix: address PR feedback take 3

* fix: tsc issues

* fix: get csrf token during client creation

* fix: linter

* fix: invalidate client onLogout

* fix: event emitter

* fix: unit tests

* fix: apply linter fixes

* fix lint

* Modify actions to add / update database values

* Rename clien4.d.ts to client.d.ts

* fix empty & missing translations

* cleanup api client

* Cleanup init & squash some TODO's

* Emit certificate errors in NetworkManager

* cleanup user actions

* Fix NetworkManager invalidate client

* Invalidate client when server screen appears

* Update kotlin to 1.4.30 required by network-client

* patch react-native-keychain to remove cached credential

* update react-native-network-client

* Use app.db instead of default.db in native code

* fix use of rnnc on Android

* Init PushNotifications

* No need to reset serverVersion on logout

* fix logout action

* fix deleteServerDatabase

* fix schedule expired session notification

* use safeParseJSON for db json fields

* unsubscribe when database component unmounts

* cleanup init

* session type

* pass launchprops to entire login flow

* Properly remove third party cookies after SSO login

* recreate network client if sso with redirect fails

* add missing launch props from server screen

* use query prefix for database queries

* Add temporary logout function to channel screen

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Miguel Alatzar
2021-07-06 08:16:35 -07:00
committed by GitHub
parent 7ff119fdc1
commit 134c4a49c5
96 changed files with 2539 additions and 2753 deletions

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ComponentType, useState} from 'react';
import React, {ComponentType, useEffect, useState} from 'react';
import {Database} from '@nozbe/watermelondb';
import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider';
@@ -15,30 +15,36 @@ export function withServerDatabase<T>(
Component: ComponentType<T>,
): ComponentType<T> {
return function ServerDatabaseComponent(props) {
const [database, setDatabase] = useState<Database|unknown>();
const [database, setDatabase] = useState<Database|undefined>();
const db = DatabaseManager.appDatabase?.database;
const observer = async (servers: Servers[]) => {
const server = servers.reduce((a, b) => (b.lastActiveAt > a.lastActiveAt ? b : a));
const serverDatabase = DatabaseManager.serverDatabases[server.url].database;
if (serverDatabase) {
setDatabase(serverDatabase);
}
const serverDatabase = DatabaseManager.serverDatabases[server.url]?.database;
setDatabase(serverDatabase);
};
db?.collections.
get(SERVERS).
query().
observeWithColumns(['last_active_at']).
subscribe(observer);
useEffect(() => {
const subscription = db?.collections.
get(SERVERS).
query().
observeWithColumns(['last_active_at']).
subscribe(observer);
return () => {
subscription?.unsubscribe();
};
}, []);
if (!database) {
return null;
}
return (
<DatabaseProvider database={(database as Database)}>
<DatabaseProvider
database={(database as Database)}
>
<Component {...props}/>
</DatabaseProvider>
);

View File

@@ -22,7 +22,7 @@ import {Channel, ChannelInfo, ChannelMembership, CustomEmoji, Draft, File,
TermsOfService, User,
} from '@database/models/server';
import {serverSchema} from '@database/schema/server';
import {getActiveServer, getServer} from '@queries/app/servers';
import {queryActiveServer, queryServer} from '@queries/app/servers';
import {deleteIOSDatabase} from '@utils/mattermost_managed';
import {hashCode} from '@utils/security';
@@ -184,7 +184,7 @@ class DatabaseManager {
private isServerPresent = async (serverUrl: string): Promise<boolean> => {
if (this.appDatabase?.database) {
const server = await getServer(this.appDatabase.database, serverUrl);
const server = await queryServer(this.appDatabase.database, serverUrl);
return Boolean(server);
}
@@ -194,7 +194,7 @@ class DatabaseManager {
public getActiveServerUrl = async (): Promise<string|null|undefined> => {
const database = this.appDatabase?.database;
if (database) {
const server = await getActiveServer(database);
const server = await queryActiveServer(database);
return server?.url;
}
@@ -204,7 +204,7 @@ class DatabaseManager {
public getActiveServerDatabase = async (): Promise<Database|undefined> => {
const database = this.appDatabase?.database;
if (database) {
const server = await getActiveServer(database);
const server = await queryActiveServer(database);
if (server?.url) {
return this.serverDatabases[server.url].database;
}
@@ -230,10 +230,10 @@ class DatabaseManager {
public deleteServerDatabase = async (serverUrl: string): Promise<void> => {
if (this.appDatabase?.database) {
const database = this.appDatabase?.database;
const server = await getServer(database, serverUrl);
const server = await queryServer(database, serverUrl);
if (server) {
database.action(() => {
server.update((record) => {
database.action(async () => {
await server.update((record) => {
record.lastActiveAt = 0;
});
});
@@ -247,7 +247,7 @@ class DatabaseManager {
public destroyServerDatabase = async (serverUrl: string): Promise<void> => {
if (this.appDatabase?.database) {
const database = this.appDatabase?.database;
const server = await getServer(database, serverUrl);
const server = await queryServer(database, serverUrl);
if (server) {
database.action(async () => {
await server.destroyPermanently();

View File

@@ -21,7 +21,7 @@ import {Channel, ChannelInfo, ChannelMembership, CustomEmoji, Draft, File,
TermsOfService, User,
} from '@database/models/server';
import {serverSchema} from '@database/schema/server';
import {getActiveServer, getServer} from '@queries/app/servers';
import {queryActiveServer, queryServer} from '@queries/app/servers';
import {deleteIOSDatabase, getIOSAppGroupDetails} from '@utils/mattermost_managed';
import {hashCode} from '@utils/security';
@@ -114,7 +114,7 @@ class DatabaseManager {
if (serverUrl) {
try {
const databaseName = hashCode(dbName);
const databaseName = hashCode(serverUrl);
const databaseFilePath = this.getDatabaseFilePath(databaseName);
const migrations = ServerDatabaseMigrations;
const modelClasses = this.serverModels;
@@ -202,7 +202,7 @@ class DatabaseManager {
*/
private isServerPresent = async (serverUrl: string): Promise<boolean> => {
if (this.appDatabase?.database) {
const server = await getServer(this.appDatabase.database, serverUrl);
const server = await queryServer(this.appDatabase.database, serverUrl);
return Boolean(server);
}
@@ -216,7 +216,7 @@ class DatabaseManager {
public getActiveServerUrl = async (): Promise<string|null|undefined> => {
const database = this.appDatabase?.database;
if (database) {
const server = await getActiveServer(database);
const server = await queryActiveServer(database);
return server?.url;
}
@@ -230,7 +230,7 @@ class DatabaseManager {
public getActiveServerDatabase = async (): Promise<Database|undefined> => {
const database = this.appDatabase?.database;
if (database) {
const server = await getActiveServer(database);
const server = await queryActiveServer(database);
if (server?.url) {
return this.serverDatabases[server.url].database;
}
@@ -268,10 +268,10 @@ class DatabaseManager {
public deleteServerDatabase = async (serverUrl: string): Promise<void> => {
if (this.appDatabase?.database) {
const database = this.appDatabase?.database;
const server = await getServer(database, serverUrl);
const server = await queryServer(database, serverUrl);
if (server) {
database.action(() => {
server.update((record) => {
database.action(async () => {
await server.update((record) => {
record.lastActiveAt = 0;
});
});
@@ -291,7 +291,7 @@ class DatabaseManager {
public destroyServerDatabase = async (serverUrl: string): Promise<void> => {
if (this.appDatabase?.database) {
const database = this.appDatabase?.database;
const server = await getServer(database, serverUrl);
const server = await queryServer(database, serverUrl);
if (server) {
database.action(async () => {
await server.destroyPermanently();

View File

@@ -5,6 +5,7 @@ import {Model} from '@nozbe/watermelondb';
import {json} from '@nozbe/watermelondb/decorators';
import {MM_TABLES} from '@constants/database';
import {safeParseJSON} from '@utils/helpers';
const {GLOBAL} = MM_TABLES.APP;
@@ -19,5 +20,5 @@ export default class Global extends Model {
static table = GLOBAL;
/** value : The value part of the key-value combination and whose key will be the id column */
@json('value', (rawJson) => rawJson) value!: any;
@json('value', safeParseJSON) value!: any;
}

View File

@@ -5,6 +5,7 @@ import Model, {Associations} from '@nozbe/watermelondb/Model';
import {field, json} from '@nozbe/watermelondb/decorators';
import {MM_TABLES} from '@constants/database';
import {safeParseJSON} from '@utils/helpers';
const {CHANNEL, DRAFT, POST} = MM_TABLES.SERVER;
@@ -35,5 +36,5 @@ export default class Draft extends Model {
@field('root_id') rootId!: string;
/** files : The files field will hold an array of file objects that have not yet been uploaded and persisted within the FILE table */
@json('files', (rawJson) => rawJson) files!: FileInfo[];
@json('files', safeParseJSON) files!: FileInfo[];
}

View File

@@ -5,8 +5,10 @@ import {Relation} from '@nozbe/watermelondb';
import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
import Model, {Associations} from '@nozbe/watermelondb/Model';
import Channel from '@typings/database/models/servers/channel';
import {MM_TABLES} from '@constants/database';
import {safeParseJSON} from '@utils/helpers';
import type Channel from '@typings/database/models/servers/channel';
const {CHANNEL, MY_CHANNEL_SETTINGS} = MM_TABLES.SERVER;
@@ -29,7 +31,7 @@ export default class MyChannelSettings extends Model {
@field('channel_id') channelId!: string;
/** notify_props : Configurations with regards to this channel */
@json('notify_props', (rawJson) => rawJson) notifyProps!: NotifyProps;
@json('notify_props', safeParseJSON) notifyProps!: NotifyProps;
/** channel : The relation pointing to the CHANNEL table */
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<Channel>;

View File

@@ -6,13 +6,15 @@ import {children, field, immutableRelation, json, lazy} from '@nozbe/watermelond
import Model, {Associations} from '@nozbe/watermelondb/Model';
import {MM_TABLES} from '@constants/database';
import Channel from '@typings/database/models/servers/channel';
import Draft from '@typings/database/models/servers/draft';
import File from '@typings/database/models/servers/file';
import PostInThread from '@typings/database/models/servers/posts_in_thread';
import PostMetadata from '@typings/database/models/servers/post_metadata';
import Reaction from '@typings/database/models/servers/reaction';
import User from '@typings/database/models/servers/user';
import {safeParseJSON} from '@utils/helpers';
import type Channel from '@typings/database/models/servers/channel';
import type Draft from '@typings/database/models/servers/draft';
import type File from '@typings/database/models/servers/file';
import type PostInThread from '@typings/database/models/servers/posts_in_thread';
import type PostMetadata from '@typings/database/models/servers/post_metadata';
import type Reaction from '@typings/database/models/servers/reaction';
import type User from '@typings/database/models/servers/user';
const {CHANNEL, DRAFT, FILE, POST, POSTS_IN_THREAD, POST_METADATA, REACTION, USER} = MM_TABLES.SERVER;
@@ -88,7 +90,7 @@ export default class Post extends Model {
@field('user_id') userId!: string;
/** props : Additional attributes for this props */
@json('props', (rawJson) => rawJson) props!: object;
@json('props', safeParseJSON) props!: object;
// A draft can be associated with this post for as long as this post is a parent post
@lazy draft = this.collections.get(DRAFT).query(Q.on(POST, 'id', this.id)) as Query<Draft>;

View File

@@ -6,8 +6,10 @@ import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
import Model, {Associations} from '@nozbe/watermelondb/Model';
import {MM_TABLES} from '@constants/database';
import {PostMetadataData, PostMetadataType} from '@typings/database/database';
import Post from '@typings/database/models/servers/post';
import {safeParseJSON} from '@utils/helpers';
import type {PostMetadataData, PostMetadataType} from '@typings/database/database';
import type Post from '@typings/database/models/servers/post';
const {POST, POST_METADATA} = MM_TABLES.SERVER;
@@ -32,7 +34,7 @@ export default class PostMetadata extends Model {
@field('type') type!: PostMetadataType;
/** data : Different types of data ranging from embeds to images. */
@json('data', (rawJson) => rawJson) data!: PostMetadataData;
@json('data', safeParseJSON) data!: PostMetadataData;
/** post: The record representing the POST parent. */
@immutableRelation(POST, 'post_id') post!: Relation<Post>;

View File

@@ -5,6 +5,7 @@ import {Model} from '@nozbe/watermelondb';
import {field, json} from '@nozbe/watermelondb/decorators';
import {MM_TABLES} from '@constants/database';
import {safeParseJSON} from '@utils/helpers';
const {ROLE} = MM_TABLES.SERVER;
@@ -17,6 +18,6 @@ export default class Role extends Model {
@field('name') name!: string;
/** permissions : The different permissions associated to that role */
@json('permissions', (rawJson) => rawJson) permissions!: string[];
@json('permissions', safeParseJSON) permissions!: string[];
}

View File

@@ -5,6 +5,7 @@ import {Model} from '@nozbe/watermelondb';
import {json} from '@nozbe/watermelondb/decorators';
import {MM_TABLES} from '@constants/database';
import {safeParseJSON} from '@utils/helpers';
const {SYSTEM} = MM_TABLES.SERVER;
@@ -18,5 +19,5 @@ export default class System extends Model {
static table = SYSTEM;
/** value : The value for that config/information and whose key will be the id column */
@json('value', (rawJson) => rawJson) value!: any;
@json('value', safeParseJSON) value!: any;
}

View File

@@ -6,7 +6,9 @@ import {field, immutableRelation, json} from '@nozbe/watermelondb/decorators';
import Model, {Associations} from '@nozbe/watermelondb/Model';
import {MM_TABLES} from '@constants/database';
import Team from '@typings/database/models/servers/team';
import {safeParseJSON} from '@utils/helpers';
import type Team from '@typings/database/models/servers/team';
const {TEAM, TEAM_CHANNEL_HISTORY} = MM_TABLES.SERVER;
@@ -29,7 +31,7 @@ export default class TeamChannelHistory extends Model {
@field('team_id') teamId!: string;
/** channel_ids : An array containing the last 5 channels visited within this team order by recency */
@json('channel_ids', (rawJson) => rawJson) channelIds!: string[];
@json('channel_ids', safeParseJSON) channelIds!: string[];
/** team : The related record from the parent Team model */
@immutableRelation(TEAM, 'team_id') team!: Relation<Team>;

View File

@@ -5,13 +5,15 @@ import {children, field, json} from '@nozbe/watermelondb/decorators';
import Model, {Associations} from '@nozbe/watermelondb/Model';
import {MM_TABLES} from '@constants/database';
import Channel from '@typings/database/models/servers/channel';
import ChannelMembership from '@typings/database/models/servers/channel_membership';
import GroupMembership from '@typings/database/models/servers/group_membership';
import Post from '@typings/database/models/servers/post';
import Preference from '@typings/database/models/servers/preference';
import Reaction from '@typings/database/models/servers/reaction';
import TeamMembership from '@typings/database/models/servers/team_membership';
import {safeParseJSON} from '@utils/helpers';
import type Channel from '@typings/database/models/servers/channel';
import type ChannelMembership from '@typings/database/models/servers/channel_membership';
import type GroupMembership from '@typings/database/models/servers/group_membership';
import type Post from '@typings/database/models/servers/post';
import type Preference from '@typings/database/models/servers/preference';
import type Reaction from '@typings/database/models/servers/reaction';
import type TeamMembership from '@typings/database/models/servers/team_membership';
const {
CHANNEL,
@@ -103,15 +105,15 @@ export default class User extends Model {
@field('username') username!: string;
/** notify_props : Notification preferences/configurations */
@json('notify_props', (rawJson) => rawJson) notifyProps!: NotifyProps;
@json('notify_props', safeParseJSON) notifyProps!: NotifyProps;
/** props : Custom objects ( e.g. custom status) can be stored in there. Its type definition is known as
* 'excess property check' in Typescript land. We keep using it till we build up the final shape of this object.
*/
@json('props', (rawJson) => rawJson) props!: UserProps;
@json('props', safeParseJSON) props!: UserProps;
/** timezone : The timezone for this user */
@json('timezone', (rawJson) => rawJson) timezone!: Timezone;
@json('timezone', safeParseJSON) timezone!: Timezone;
/** channelsCreated : All the channels that this user created */
@children(CHANNEL) channelsCreated!: Channel[];