forked from Ivasoft/mattermost-mobile
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:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user