forked from Ivasoft/mattermost-mobile
Compare commits
10 Commits
release-2.
...
test1.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| d37bc4c14a | |||
| 2bb144042b | |||
| b960c2f036 | |||
| 2aa1265762 | |||
| 565c19ca64 | |||
| b4acbef5f5 | |||
| 0a766bfb65 | |||
| b7cc411c3b | |||
| 04b35959a3 | |||
| e639e69d46 |
617
.circleci/config.yml
Normal file
617
.circleci/config.yml
Normal file
@@ -0,0 +1,617 @@
|
||||
version: 2.1
|
||||
orbs:
|
||||
owasp: entur/owasp@0.0.10
|
||||
node: circleci/node@5.0.3
|
||||
|
||||
executors:
|
||||
android:
|
||||
parameters:
|
||||
resource_class:
|
||||
default: xlarge
|
||||
type: string
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=12000
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
docker:
|
||||
- image: cimg/android:2022.09.2-node
|
||||
working_directory: ~/mattermost-mobile
|
||||
resource_class: <<parameters.resource_class>>
|
||||
|
||||
ios:
|
||||
parameters:
|
||||
resource_class:
|
||||
default: medium
|
||||
type: string
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=12000
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
macos:
|
||||
xcode: "14.0.0"
|
||||
working_directory: ~/mattermost-mobile
|
||||
shell: /bin/bash --login -o pipefail
|
||||
resource_class: <<parameters.resource_class>>
|
||||
|
||||
commands:
|
||||
checkout-private:
|
||||
description: "Checkout the private repo with build env vars"
|
||||
steps:
|
||||
- add_ssh_keys:
|
||||
fingerprints:
|
||||
- "03:1c:a7:07:35:bc:57:e4:1d:6c:e1:2c:4b:be:09:6d"
|
||||
- run:
|
||||
name: Clone the mobile private repo
|
||||
command: git clone git@github.com:mattermost/mattermost-mobile-private.git ~/mattermost-mobile-private
|
||||
|
||||
fastlane-dependencies:
|
||||
description: "Get Fastlane dependencies"
|
||||
parameters:
|
||||
for:
|
||||
type: string
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore Fastlane cache
|
||||
key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Download Fastlane dependencies
|
||||
command: bundle install --path vendor/bundle
|
||||
- save_cache:
|
||||
name: Save Fastlane cache
|
||||
key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- fastlane/vendor/bundle
|
||||
|
||||
gradle-dependencies:
|
||||
description: "Get Gradle dependencies"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore Gradle cache
|
||||
key: v1-gradle-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
|
||||
- run:
|
||||
working_directory: android
|
||||
name: Download Gradle dependencies
|
||||
command: ./gradlew dependencies
|
||||
- save_cache:
|
||||
name: Save Gradle cache
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: v1-gradle-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
|
||||
|
||||
assets:
|
||||
description: "Generate app assets"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore assets cache
|
||||
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
|
||||
- run:
|
||||
name: Generate assets
|
||||
command: node ./scripts/generate-assets.js
|
||||
- run:
|
||||
name: Compass Icons
|
||||
environment:
|
||||
COMPASS_ICONS: "node_modules/@mattermost/compass-icons/font/compass-icons.ttf"
|
||||
command: |
|
||||
cp "$COMPASS_ICONS" "assets/fonts/"
|
||||
cp "$COMPASS_ICONS" "android/app/src/main/assets/fonts"
|
||||
- save_cache:
|
||||
name: Save assets cache
|
||||
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
|
||||
paths:
|
||||
- dist
|
||||
|
||||
npm-dependencies:
|
||||
description: "Get JavaScript dependencies"
|
||||
steps:
|
||||
- node/install:
|
||||
node-version: '18.7.0'
|
||||
- restore_cache:
|
||||
name: Restore npm cache
|
||||
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
- run:
|
||||
name: Getting JavaScript dependencies
|
||||
command: |
|
||||
NODE_ENV=development npm ci --ignore-scripts
|
||||
node node_modules/\@sentry/cli/scripts/install.js
|
||||
node node_modules/react-native-webrtc/tools/downloadWebRTC.js
|
||||
- save_cache:
|
||||
name: Save npm cache
|
||||
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: "Patch dependencies"
|
||||
command: npx patch-package
|
||||
|
||||
pods-dependencies:
|
||||
description: "Get cocoapods dependencies"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore cocoapods specs and pods
|
||||
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
|
||||
- run:
|
||||
name: iOS gems
|
||||
command: npm run ios-gems
|
||||
- run:
|
||||
name: Getting cocoapods dependencies
|
||||
command: npm run pod-install
|
||||
- save_cache:
|
||||
name: Save cocoapods specs and pods cache
|
||||
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- ios/Pods
|
||||
- ~/.cocoapods
|
||||
|
||||
build-android:
|
||||
description: "Build the android app"
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- checkout-private
|
||||
- npm-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: android
|
||||
- gradle-dependencies
|
||||
- run:
|
||||
name: Append Keystore to build Android
|
||||
command: |
|
||||
cp ~/mattermost-mobile-private/android/${STORE_FILE} android/app/${STORE_FILE}
|
||||
echo "" | tee -a android/gradle.properties > /dev/null
|
||||
echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null
|
||||
- run:
|
||||
name: Jetify android libraries
|
||||
command: ./node_modules/.bin/jetify
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build android
|
||||
no_output_timeout: 30m
|
||||
command: export TERM=xterm && bundle exec fastlane android build
|
||||
|
||||
build-ios:
|
||||
description: "Build the iOS app"
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: ios
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build iOS
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
export TERM=xterm && bundle exec fastlane ios build
|
||||
|
||||
deploy-to-store:
|
||||
description: "Deploy build to store"
|
||||
parameters:
|
||||
task:
|
||||
type: string
|
||||
target:
|
||||
type: string
|
||||
file:
|
||||
type: string
|
||||
env:
|
||||
type: string
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
- run:
|
||||
name: <<parameters.task>>
|
||||
working_directory: fastlane
|
||||
command: <<parameters.env>> bundle exec fastlane <<parameters.target>> deploy file:$HOME/mattermost-mobile/<<parameters.file>>
|
||||
|
||||
persist:
|
||||
description: "Persist mattermost-mobile directory"
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- mattermost-mobile*
|
||||
|
||||
save:
|
||||
description: "Save binaries artifacts"
|
||||
parameters:
|
||||
filename:
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: Copying artifacts
|
||||
command: |
|
||||
mkdir /tmp/artifacts;
|
||||
cp ~/mattermost-mobile/<<parameters.filename>> /tmp/artifacts;
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
|
||||
jobs:
|
||||
test:
|
||||
working_directory: ~/mattermost-mobile
|
||||
docker:
|
||||
- image: cimg/node:16.14.2
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- assets
|
||||
- run:
|
||||
name: Check styles
|
||||
command: npm run check
|
||||
- run:
|
||||
name: Running Tests
|
||||
command: npm test
|
||||
- run:
|
||||
name: Check i18n
|
||||
command: ./scripts/precommit/i18n.sh
|
||||
|
||||
check-deps:
|
||||
parameters:
|
||||
cve_data_directory:
|
||||
type: string
|
||||
default: "~/.owasp/dependency-check-data"
|
||||
working_directory: ~/mattermost-mobile
|
||||
executor: owasp/default
|
||||
environment:
|
||||
version_url: "https://jeremylong.github.io/DependencyCheck/current.txt"
|
||||
executable_url: "https://dl.bintray.com/jeremy-long/owasp/dependency-check-VERSION-release.zip"
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
name: Restore npm cache
|
||||
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
- run:
|
||||
name: Checkout config
|
||||
command: cd .. && git clone https://github.com/mattermost/security-automation-config
|
||||
- run:
|
||||
name: Install Go
|
||||
command: sudo apt-get update && sudo apt-get install golang
|
||||
- owasp/with_commandline:
|
||||
steps:
|
||||
# Taken from https://github.com/entur/owasp-orb/blob/master/src/%40orb.yml#L349-L361
|
||||
- owasp/generate_cache_keys:
|
||||
cache_key: commmandline-default-cache-key-v7
|
||||
- owasp/restore_owasp_cache
|
||||
- run:
|
||||
name: Update OWASP Dependency-Check Database
|
||||
command: |
|
||||
if ! ~/.owasp/dependency-check/bin/dependency-check.sh --data << parameters.cve_data_directory >> --updateonly; then
|
||||
# Update failed, probably due to a bad DB version; delete cached DB and try again
|
||||
rm -rv ~/.owasp/dependency-check-data/*.db
|
||||
~/.owasp/dependency-check/bin/dependency-check.sh --data << parameters.cve_data_directory >> --updateonly
|
||||
fi
|
||||
- owasp/store_owasp_cache:
|
||||
cve_data_directory: <<parameters.cve_data_directory>>
|
||||
- run:
|
||||
name: Run OWASP Dependency-Check Analyzer
|
||||
command: |
|
||||
~/.owasp/dependency-check/bin/dependency-check.sh \
|
||||
--data << parameters.cve_data_directory >> --format ALL --noupdate --enableExperimental \
|
||||
--propertyfile ../security-automation-config/dependency-check/dependencycheck.properties \
|
||||
--suppression ../security-automation-config/dependency-check/suppression.xml \
|
||||
--suppression ../security-automation-config/dependency-check/suppression.$CIRCLE_PROJECT_REPONAME.xml \
|
||||
--scan './**/*' || true
|
||||
- owasp/collect_reports:
|
||||
persist_to_workspace: false
|
||||
- run:
|
||||
name: Post results to Mattermost
|
||||
command: go run ../security-automation-config/dependency-check/post_results.go
|
||||
|
||||
build-android-beta:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-release:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-pr:
|
||||
executor: android
|
||||
environment:
|
||||
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
|
||||
steps:
|
||||
- build-android
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-unsigned:
|
||||
executor: android
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: android
|
||||
- gradle-dependencies
|
||||
- run:
|
||||
name: Jetify Android libraries
|
||||
command: ./node_modules/.bin/jetify
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned android
|
||||
no_output_timeout: 30m
|
||||
command: bundle exec fastlane android unsigned
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-ios-beta:
|
||||
executor:
|
||||
name: ios
|
||||
resource_class: large
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-pr:
|
||||
executor: ios
|
||||
environment:
|
||||
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
|
||||
steps:
|
||||
- build-ios
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-unsigned:
|
||||
executor: ios
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: ios
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned iOS
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
bundle exec fastlane ios unsigned
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- mattermost-mobile/*.ipa
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-simulator:
|
||||
executor: ios
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: ios
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned x86_64 iOS app for iPhone simulator
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
bundle exec fastlane ios simulator
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- mattermost-mobile/Mattermost-simulator-x86_64.app.zip
|
||||
- save:
|
||||
filename: "Mattermost-simulator-x86_64.app.zip"
|
||||
|
||||
deploy-android-release:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: "*.apk"
|
||||
env: "SUPPLY_TRACK=beta"
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: "*.apk"
|
||||
env: "SUPPLY_TRACK=alpha"
|
||||
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
env: ""
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
env: ""
|
||||
|
||||
github-release:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
- run:
|
||||
name: Create GitHub release
|
||||
working_directory: fastlane
|
||||
command: bundle exec fastlane github
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- test
|
||||
# - check-deps:
|
||||
# context: sast-webhook
|
||||
# requires:
|
||||
# - test
|
||||
|
||||
- build-android-release:
|
||||
context: mattermost-mobile-android-release
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
- deploy-android-release:
|
||||
context: mattermost-mobile-android-release
|
||||
requires:
|
||||
- build-android-release
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
|
||||
- build-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
- deploy-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- build-android-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
|
||||
- build-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
- deploy-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
- build-ios-release
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
|
||||
- build-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
- deploy-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- build-ios-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
|
||||
- build-android-pr:
|
||||
context: mattermost-mobile-android-pr
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only: /^(build|android)-pr-.*/
|
||||
- build-ios-pr:
|
||||
context: mattermost-mobile-ios-pr
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only: /^(build|ios)-pr-.*/
|
||||
|
||||
- build-android-unsigned:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
- build-ios-unsigned:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
- build-ios-simulator:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-sim-\d+$/
|
||||
|
||||
- github-release:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- build-android-unsigned
|
||||
- build-ios-unsigned
|
||||
filters:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
31
.drone.yml
Normal file
31
.drone.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: permissions
|
||||
image: alpine/git
|
||||
commands:
|
||||
- chmod -R 777 .
|
||||
|
||||
- name: build
|
||||
image: cimg/android:2022.09.2-node
|
||||
environment:
|
||||
CIRCLECI: true
|
||||
NODE_OPTIONS: --max_old_space_size=12000
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
MATTERMOST_RELEASE_STORE_FILE: /root/mattermost.keystore
|
||||
MATTERMOST_RELEASE_KEY_ALIAS: mattermost-google-key
|
||||
MATTERMOST_RELEASE_PASSWORD: 123456
|
||||
commands:
|
||||
- 'npm run build:android'
|
||||
|
||||
- name: gitea_release
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: drone_release
|
||||
base_url: https://git.ivasoft.cz
|
||||
files: '*.apk'
|
||||
when:
|
||||
event: tag
|
||||
@@ -61,7 +61,6 @@
|
||||
"afterColon": true
|
||||
}}],
|
||||
"@typescript-eslint/member-delimiter-style": 2,
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off",
|
||||
"import/order": [
|
||||
2,
|
||||
{
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
name: prepare-android-build
|
||||
description: Action to prepare environment for android build
|
||||
|
||||
inputs:
|
||||
sign:
|
||||
description: Flag to enable android package signing
|
||||
default: "true"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: ci/prepare-mobile-build
|
||||
uses: ./.github/actions/prepare-mobile-build
|
||||
|
||||
# Disable this since we are not caching anything for now
|
||||
# - name: ci/install-gradle-dependencies
|
||||
# shell: bash
|
||||
# working-directory: android
|
||||
# run: |
|
||||
# echo "::group::install-gradle-dependencies"
|
||||
# ./gradlew dependencies
|
||||
# echo "::endgroup::"
|
||||
|
||||
- name: ci/jetify-android-libraries
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::jetify-android-libraries"
|
||||
./node_modules/.bin/jetify
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/checkout-private-repo
|
||||
if: ${{ inputs.sign == 'true' }}
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
with:
|
||||
repository: mattermost/mattermost-mobile-private
|
||||
token: ${{ env.MATTERMOST_BUILD_GH_TOKEN }}
|
||||
path: ${{ github.workspace }}/mattermost-mobile-private
|
||||
|
||||
- name: ci/append-keystore-to-android-build-for-signing
|
||||
if: ${{ inputs.sign == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::append-keystore-to-android-build-for-signing"
|
||||
cp ${{ github.workspace }}/mattermost-mobile-private/android/${STORE_FILE} android/app/${STORE_FILE}
|
||||
echo "" | tee -a android/gradle.properties > /dev/null
|
||||
echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null
|
||||
echo "::endgroup::"
|
||||
16
.github/actions/prepare-ios-build/action.yaml
vendored
16
.github/actions/prepare-ios-build/action.yaml
vendored
@@ -1,16 +0,0 @@
|
||||
name: prepare-ios-build
|
||||
description: Action to prepare environment for ios build
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: ci/prepare-mobile-build
|
||||
uses: ./.github/actions/prepare-mobile-build
|
||||
|
||||
- name: ci/install-pods-dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::install-pods-dependencies"
|
||||
npm run ios-gems
|
||||
npm run pod-install
|
||||
echo "::endgroup::"
|
||||
19
.github/actions/prepare-mobile-build/action.yaml
vendored
19
.github/actions/prepare-mobile-build/action.yaml
vendored
@@ -1,19 +0,0 @@
|
||||
name: prepare-mobile-build
|
||||
description: Action to prepare environment for mobile build
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# The required ruby version is mentioned in '.ruby-version'
|
||||
- uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v 1.171.0
|
||||
|
||||
- name: ci/setup-fastlane-dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::setup-fastlane-dependencies"
|
||||
bundle install
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/prepare-node-deps
|
||||
uses: ./.github/actions/prepare-node-deps
|
||||
46
.github/actions/prepare-node-deps/action.yaml
vendored
46
.github/actions/prepare-node-deps/action.yaml
vendored
@@ -1,46 +0,0 @@
|
||||
name: deps
|
||||
description: Common deps for mobile repo
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: ci/setup-node
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "npm"
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: ci/install-npm-dependencies
|
||||
shell: bash
|
||||
env:
|
||||
NODE_ENV: development
|
||||
run: |
|
||||
echo "::group::install-npm-dependencies"
|
||||
npm ci --ignore-scripts
|
||||
node node_modules/\@sentry/cli/scripts/install.js
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/patch-npm-dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::patch-npm-dependencies"
|
||||
npx patch-package
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/generate-assets
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::generate-assets"
|
||||
node ./scripts/generate-assets.js
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: ci/import-compass-icon
|
||||
shell: bash
|
||||
env:
|
||||
COMPASS_ICONS: "node_modules/@mattermost/compass-icons/font/compass-icons.ttf"
|
||||
run: |
|
||||
echo "::group::import-compass-icon"
|
||||
cp "$COMPASS_ICONS" "assets/fonts/"
|
||||
cp "$COMPASS_ICONS" "android/app/src/main/assets/fonts"
|
||||
echo "::endgroup::"
|
||||
27
.github/actions/test/action.yaml
vendored
27
.github/actions/test/action.yaml
vendored
@@ -1,27 +0,0 @@
|
||||
name: test
|
||||
description: Common tests for mobile repo
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: ci/prepare-node-deps
|
||||
uses: ./.github/actions/prepare-node-deps
|
||||
|
||||
- name: ci/check-styles
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::check-styles"
|
||||
npm run check
|
||||
echo "::endgroup::"
|
||||
- name: ci/run-tests
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::run-tests"
|
||||
npm test
|
||||
echo "::endgroup::"
|
||||
- name: ci/check-i18n
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::check-i18n"
|
||||
./scripts/precommit/i18n.sh
|
||||
echo "::endgroup::"
|
||||
60
.github/workflows/build-android-beta.yml
vendored
60
.github/workflows/build-android-beta.yml
vendored
@@ -1,60 +0,0 @@
|
||||
---
|
||||
name: build-android-beta
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- build-beta-[0-9]+
|
||||
- build-beta-android-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.7.0
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
|
||||
build-and-deploy-android-beta:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-android-build
|
||||
uses: ./.github/actions/prepare-android-build
|
||||
env:
|
||||
STORE_FILE: "${{ secrets.MM_MOBILE_STORE_FILE }}"
|
||||
STORE_ALIAS: "${{ secrets.MM_MOBILE_STORE_ALIAS }}"
|
||||
STORE_PASSWORD: "${{ secrets.MM_MOBILE_STORE_PASSWORD }}"
|
||||
MATTERMOST_BUILD_GH_TOKEN: "${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}"
|
||||
|
||||
- name: ci/build-and-deploy-android-beta
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_BETA_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_BETA_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
SENTRY_AUTH_TOKEN: "${{ secrets.MM_MOBILE_SENTRY_AUTH_TOKEN }}"
|
||||
SENTRY_DSN_ANDROID: ${{ secrets.MM_MOBILE_BETA_SENTRY_DSN_ANDROID }}
|
||||
SUPPLY_JSON_KEY: ${{ github.workspace }}/mattermost-mobile-private/android/mattermost-credentials.json
|
||||
run: |
|
||||
echo "::group::Build"
|
||||
bundle exec fastlane android build --env android.beta
|
||||
echo "::endgroup::"
|
||||
echo "::group::Deploy to Play Store"
|
||||
bundle exec fastlane android deploy file:"${{ github.workspace }}/*.apk" --env android.beta
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-beta-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: android-build-beta-${{ github.run_id }}
|
||||
path: "*.apk"
|
||||
60
.github/workflows/build-android-release.yml
vendored
60
.github/workflows/build-android-release.yml
vendored
@@ -1,60 +0,0 @@
|
||||
---
|
||||
name: build-android-release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- build-release-[0-9]+
|
||||
- build-release-android-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.7.0
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
|
||||
build-and-deploy-android-release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-android-build
|
||||
uses: ./.github/actions/prepare-android-build
|
||||
env:
|
||||
STORE_FILE: "${{ secrets.MM_MOBILE_STORE_FILE }}"
|
||||
STORE_ALIAS: "${{ secrets.MM_MOBILE_STORE_ALIAS }}"
|
||||
STORE_PASSWORD: "${{ secrets.MM_MOBILE_STORE_PASSWORD }}"
|
||||
MATTERMOST_BUILD_GH_TOKEN: "${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}"
|
||||
|
||||
- name: ci/build-and-deploy-android-release
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_RELEASE_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_RELEASE_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_RELEASE_MATTERMOST_WEBHOOK_URL }}"
|
||||
SENTRY_AUTH_TOKEN: "${{ secrets.MM_MOBILE_SENTRY_AUTH_TOKEN }}"
|
||||
SENTRY_DSN_ANDROID: ${{ secrets.MM_MOBILE_RELEASE_SENTRY_DSN_ANDROID }}
|
||||
SUPPLY_JSON_KEY: ${{ github.workspace }}/mattermost-mobile-private/android/mattermost-credentials.json
|
||||
run: |
|
||||
echo "::group::Build"
|
||||
bundle exec fastlane android build --env android.release
|
||||
echo "::endgroup::"
|
||||
echo "::group::Deploy to Play Store"
|
||||
bundle exec fastlane android deploy file:"${{ github.workspace }}/*.apk" --env android.release
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-release-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: android-build-release-${{ github.run_id }}
|
||||
path: "*.apk"
|
||||
99
.github/workflows/build-ios-beta.yml
vendored
99
.github/workflows/build-ios-beta.yml
vendored
@@ -1,99 +0,0 @@
|
||||
---
|
||||
name: build-ios-beta
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- build-beta-[0-9]+
|
||||
- build-beta-ios-[0-9]+
|
||||
- build-beta-sim-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.7.0
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name, 'beta-ios') }}
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
|
||||
- name: ci/build-ios-simulator
|
||||
env:
|
||||
TAG: "${{ github.ref_name }}"
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_BETA_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_BETA_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane ios simulator --env ios.simulator
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-pr-simulator
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
|
||||
build-and-deploy-ios-beta:
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name, 'beta-sim') }}
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
|
||||
- name: ci/build-and-deploy-ios-beta
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_BETA_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_BETA_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
FASTLANE_TEAM_ID: "${{ secrets.MM_MOBILE_FASTLANE_TEAM_ID }}"
|
||||
IOS_API_ISSUER_ID: "${{ secrets.MM_MOBILE_IOS_API_ISSUER_ID }}"
|
||||
IOS_API_KEY: "${{ secrets.MM_MOBILE_IOS_API_KEY }}"
|
||||
IOS_API_KEY_ID: "${{ secrets.MM_MOBILE_IOS_API_KEY_ID }}"
|
||||
MATCH_GIT_URL: "${{ secrets.MM_MOBILE_MATCH_GIT_URL }}"
|
||||
MATCH_PASSWORD: "${{ secrets.MM_MOBILE_MATCH_PASSWORD }}"
|
||||
SENTRY_AUTH_TOKEN: "${{ secrets.MM_MOBILE_SENTRY_AUTH_TOKEN }}"
|
||||
SENTRY_DSN_IOS: "${{ secrets.MM_MOBILE_BETA_SENTRY_DSN_IOS }}"
|
||||
run: |
|
||||
echo "::group::Build"
|
||||
bundle exec fastlane ios build --env ios.beta
|
||||
echo "::endgroup::"
|
||||
echo "::group::Deploy to TestFlight"
|
||||
bundle exec fastlane ios deploy file:"${{ github.workspace }}/*.ipa" --env ios.beta
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-beta-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: ios-build-beta-${{ github.run_id }}
|
||||
path: "*.ipa"
|
||||
99
.github/workflows/build-ios-release.yml
vendored
99
.github/workflows/build-ios-release.yml
vendored
@@ -1,99 +0,0 @@
|
||||
---
|
||||
name: build-ios-release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- build-release-[0-9]+
|
||||
- build-release-ios-[0-9]+
|
||||
- build-release-sim-[0-9]+
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.7.0
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
|
||||
build-and-deploy-ios-release:
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name, 'release-sim') }}
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/build-and-deploy-ios-release
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_RELEASE_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_RELEASE_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_RELEASE_MATTERMOST_WEBHOOK_URL }}"
|
||||
FASTLANE_TEAM_ID: "${{ secrets.MM_MOBILE_FASTLANE_TEAM_ID }}"
|
||||
IOS_API_ISSUER_ID: "${{ secrets.MM_MOBILE_IOS_API_ISSUER_ID }}"
|
||||
IOS_API_KEY: "${{ secrets.MM_MOBILE_IOS_API_KEY }}"
|
||||
IOS_API_KEY_ID: "${{ secrets.MM_MOBILE_IOS_API_KEY_ID }}"
|
||||
MATCH_GIT_URL: "${{ secrets.MM_MOBILE_MATCH_GIT_URL }}"
|
||||
MATCH_PASSWORD: "${{ secrets.MM_MOBILE_MATCH_PASSWORD }}"
|
||||
SENTRY_AUTH_TOKEN: "${{ secrets.MM_MOBILE_SENTRY_AUTH_TOKEN }}"
|
||||
SENTRY_DSN_IOS: ${{ secrets.MM_MOBILE_RELEASE_SENTRY_DSN_IOS }}
|
||||
run: |
|
||||
echo "::group::Build"
|
||||
bundle exec fastlane ios build --env ios.release
|
||||
echo "::endgroup::"
|
||||
echo "::group::Deploy to TestFlight"
|
||||
bundle exec fastlane ios deploy file:"${{ github.workspace }}/*.ipa" --env ios.release
|
||||
echo "::endgroup::"
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-release-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: ios-build-release-${{ github.run_id }}
|
||||
path: "*.ipa"
|
||||
|
||||
build-ios-simulator:
|
||||
runs-on: macos-14-large
|
||||
if: ${{ !contains(github.ref_name , 'release-ios') }}
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
|
||||
- name: ci/build-ios-simulator
|
||||
env:
|
||||
TAG: "${{ github.ref_name }}"
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_BETA_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_BETA_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_BETA_MATTERMOST_WEBHOOK_URL }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane ios simulator --env ios.simulator
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-pr-simulator
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: ios-build-simulator-${{ github.run_id }}
|
||||
path: Mattermost-simulator-x86_64.app.zip
|
||||
100
.github/workflows/build-pr.yml
vendored
100
.github/workflows/build-pr.yml
vendored
@@ -1,100 +0,0 @@
|
||||
---
|
||||
name: build-pr
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- labeled
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.7.0
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.event.label.name == 'Build Apps for PR' || github.event.label.name == 'Build App for iOS' || github.event.label.name == 'Build App for Android' }}
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-pr:
|
||||
runs-on: macos-14-large
|
||||
if: ${{ github.event.label.name == 'Build Apps for PR' || github.event.label.name == 'Build App for iOS' }}
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/build-ios-pr
|
||||
env:
|
||||
BRANCH_TO_BUILD: "${{ github.event.pull_request.head.ref }}"
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_PR_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_PR_AWS_SECRET_ACCESS_KEY }}"
|
||||
FASTLANE_TEAM_ID: "${{ secrets.MM_MOBILE_FASTLANE_TEAM_ID }}"
|
||||
IOS_API_ISSUER_ID: "${{ secrets.MM_MOBILE_IOS_API_ISSUER_ID }}"
|
||||
IOS_API_KEY: "${{ secrets.MM_MOBILE_IOS_API_KEY }}"
|
||||
IOS_API_KEY_ID: "${{ secrets.MM_MOBILE_IOS_API_KEY_ID }}"
|
||||
MATCH_GIT_URL: "${{ secrets.MM_MOBILE_MATCH_GIT_URL }}"
|
||||
MATCH_PASSWORD: "${{ secrets.MM_MOBILE_MATCH_PASSWORD }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_PR_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: bundle exec fastlane ios build --env ios.pr
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-pr-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: ios-build-pr-${{ github.run_id }}
|
||||
path: "*.ipa"
|
||||
|
||||
build-android-pr:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.event.label.name == 'Build Apps for PR' || github.event.label.name == 'Build App for Android' }}
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: ci/prepare-android-build
|
||||
uses: ./.github/actions/prepare-android-build
|
||||
env:
|
||||
STORE_FILE: "${{ secrets.MM_MOBILE_STORE_FILE }}"
|
||||
STORE_ALIAS: "${{ secrets.MM_MOBILE_STORE_ALIAS }}"
|
||||
STORE_PASSWORD: "${{ secrets.MM_MOBILE_STORE_PASSWORD }}"
|
||||
MATTERMOST_BUILD_GH_TOKEN: "${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}"
|
||||
|
||||
- name: ci/build-android-pr
|
||||
env:
|
||||
BRANCH_TO_BUILD: "${{ github.event.pull_request.head.ref }}"
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.MM_MOBILE_PR_AWS_ACCESS_KEY_ID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.MM_MOBILE_PR_AWS_SECRET_ACCESS_KEY }}"
|
||||
MATTERMOST_WEBHOOK_URL: "${{ secrets.MM_MOBILE_PR_MATTERMOST_WEBHOOK_URL }}"
|
||||
run: bundle exec fastlane android build --env android.pr
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-pr-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: android-build-pr-${{ github.run_id }}
|
||||
path: "*.apk"
|
||||
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'release*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.7.0
|
||||
TERM: xterm
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
98
.github/workflows/github-release.yml
vendored
98
.github/workflows/github-release.yml
vendored
@@ -1,98 +0,0 @@
|
||||
---
|
||||
name: github-release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: ci/test
|
||||
uses: ./.github/actions/test
|
||||
|
||||
build-ios-unsigned:
|
||||
runs-on: macos-14-large
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-ios-build
|
||||
uses: ./.github/actions/prepare-ios-build
|
||||
|
||||
- name: ci/output-ssh-private-key
|
||||
shell: bash
|
||||
run: |
|
||||
SSH_KEY_PATH=~/.ssh/id_ed25519
|
||||
mkdir -p ~/.ssh
|
||||
echo -e '${{ secrets.MM_MOBILE_PRIVATE_DEPLOY_KEY }}' > ${SSH_KEY_PATH}
|
||||
chmod 0600 ${SSH_KEY_PATH}
|
||||
ssh-keygen -y -f ${SSH_KEY_PATH} > ${SSH_KEY_PATH}.pub
|
||||
|
||||
- name: ci/build-ios-unsigned
|
||||
env:
|
||||
TAG: "${{ github.ref_name }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane ios unsigned
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-ios-unsigned
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
path: Mattermost-unsigned.ipa
|
||||
name: Mattermost-unsigned.ipa
|
||||
|
||||
build-android-unsigned:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: ci/prepare-android-build
|
||||
uses: ./.github/actions/prepare-android-build
|
||||
with:
|
||||
sign: false
|
||||
|
||||
- name: ci/build-android-beta
|
||||
env:
|
||||
TAG: "${{ github.ref_name }}"
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane android unsigned
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/upload-android-unsigned-build
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
path: Mattermost-unsigned.apk
|
||||
name: Mattermost-unsigned.apk
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build-ios-unsigned
|
||||
- build-android-unsigned
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v1.171.0
|
||||
|
||||
- name: release/setup-fastlane-dependencies
|
||||
run: bundle install
|
||||
working-directory: ./fastlane
|
||||
|
||||
- name: ci/download-artifacts
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
|
||||
- name: release/create-github-release
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||||
run: bundle exec fastlane github
|
||||
working-directory: ./fastlane
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -103,7 +103,6 @@ detox/detox_pixel_*
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
.bundle
|
||||
|
||||
#editor-settings
|
||||
.vscode
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
18.17
|
||||
@@ -1 +0,0 @@
|
||||
3.0.6
|
||||
22
.solidarity
22
.solidarity
@@ -4,11 +4,29 @@
|
||||
"output" : "moderate"
|
||||
},
|
||||
"requirements": {
|
||||
"Node": [
|
||||
{
|
||||
"rule": "cli",
|
||||
"binary": "node",
|
||||
"semver": ">=16.0.0",
|
||||
"error": "install node using nvm https://github.com/nvm-sh/nvm#installing-and-updating"
|
||||
},
|
||||
{
|
||||
"rule": "cli",
|
||||
"binary": "npm",
|
||||
"semver": ">=8.5.5 <9.0.0",
|
||||
"error": "install npm 8.5.5 `npm i -g npm@8.5.5"
|
||||
}
|
||||
],
|
||||
"Android": [
|
||||
{
|
||||
"rule": "cli",
|
||||
"binary": "emulator"
|
||||
},
|
||||
{
|
||||
"rule": "cli",
|
||||
"binary": "android"
|
||||
},
|
||||
{
|
||||
"rule": "env",
|
||||
"variable": "ANDROID_HOME",
|
||||
@@ -32,14 +50,14 @@
|
||||
{
|
||||
"rule": "cli",
|
||||
"binary": "ruby",
|
||||
"semver": ">=3.0.0",
|
||||
"semver": ">=2.7.1 <3.0.0",
|
||||
"error": "visit rvm install https://rvm.io/rvm/install",
|
||||
"platform": "darwin"
|
||||
},
|
||||
{
|
||||
"rule": "cli",
|
||||
"binary": "pod",
|
||||
"semver": "1.14.3",
|
||||
"semver": "1.11.3",
|
||||
"platform": "darwin"
|
||||
}
|
||||
],
|
||||
|
||||
247
NOTICE.txt
247
NOTICE.txt
@@ -34,18 +34,6 @@ A spec-compliant polyfill/ponyfill for Intl.getCanonicalLocales tested by the of
|
||||
* LICENSE: MIT
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @formatjs/intl-listformat
|
||||
|
||||
This product contains '@formatjs/intl-listformat' by FormatJS.
|
||||
|
||||
This repository is the home of FormatJS and related libraries.
|
||||
|
||||
* HOMEPAGE: https://github.com/formatjs/formatjs
|
||||
|
||||
* LICENSE MIT
|
||||
|
||||
---
|
||||
|
||||
## @formatjs/intl-locale
|
||||
@@ -136,19 +124,6 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @mattermost/calls
|
||||
|
||||
This product contains '@mattermost/calls' by Mattermost.
|
||||
|
||||
Calls enables voice calling and screen sharing functionality in Mattermost channels.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mattermost/calls-common
|
||||
|
||||
* LICENSE: Apache License
|
||||
|
||||
---
|
||||
|
||||
## @mattermost/compass-icons
|
||||
|
||||
This product contains '@mattermost/compass-icons' by Mattermost.
|
||||
@@ -258,6 +233,41 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @mattermost/react-native-turbo-mailer
|
||||
|
||||
This product contains '@mattermost/react-native-turbo-mailer' by Avinash Lingaloo.
|
||||
|
||||
An adaptation of react-native-mail that supports Turbo Module
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mattermost/react-native-turbo-mailer#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Mattermost
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @msgpack/msgpack
|
||||
@@ -314,6 +324,41 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @nozbe/with-observables
|
||||
|
||||
This product contains '@nozbe/with-observables' by Nozbe.
|
||||
|
||||
A higher-order component for connecting RxJS Observables to React components.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/Nozbe/withObservables
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Nozbe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-camera-roll/camera-roll
|
||||
@@ -531,6 +576,42 @@ Stack navigator component for iOS and Android with animated transitions and gest
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @rudderstack/rudder-sdk-react-native
|
||||
|
||||
This product contains '@rudderstack/rudder-sdk-react-native' by RudderStack.
|
||||
|
||||
Rudder React Native SDK
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/rudderlabs/rudder-sdk-reactnative#readme
|
||||
|
||||
* LICENSE: Apache-2.0
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 RudderStack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @sentry/react-native
|
||||
@@ -602,51 +683,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @tsconfig/react-native
|
||||
|
||||
This product contains a modified version of '@tsconfig/react-native' by TSC Base.
|
||||
|
||||
Hosts TSConfigs for you to extend in your apps, tuned to a particular runtime environment.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/tsconfig/bases
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
---
|
||||
|
||||
## @voximplant/react-native-foreground-service
|
||||
|
||||
This product contains a modified version of '@voximplant/react-native-foreground-service' by Voximplant.
|
||||
|
||||
A foreground service performs some operation that is noticeable to the user.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/voximplant/react-native-foreground-service
|
||||
|
||||
* LICENSE: MIT License
|
||||
|
||||
Copyright (c) 2019 Zingaya, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
@@ -1333,41 +1369,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## path-to-regexp
|
||||
|
||||
This product contains 'path-to-regexp' by lastuniverse.
|
||||
|
||||
Turn a path string such as /user/:id or /user/:id(\d+) into a regular expression
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/lastuniverse/path-to-regex
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Roman Surmanidze (kapa6a3er@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react
|
||||
@@ -1532,6 +1533,42 @@ Open Android settings from your React Native app
|
||||
* LICENSE: ISC
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react-native-animated-numbers
|
||||
|
||||
This product contains 'react-native-animated-numbers' by Lake (Yeongsu Han).
|
||||
|
||||
Library showing animation of number changes in react-native
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/heyman333/react-native-animated-numbers
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Yeongsu Han
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react-native-background-timer
|
||||
|
||||
16
README.md
16
README.md
@@ -1,12 +1,12 @@
|
||||
# Mattermost Mobile v2
|
||||
|
||||
- **Minimum Server versions:** Current ESR version (8.1.0+)
|
||||
- **Supported iOS versions:** 12.4+
|
||||
- **Minimum Server versions:** Current ESR version (7.1.0+)
|
||||
- **Supported iOS versions:** 12.1+
|
||||
- **Supported Android versions:** 7.0+
|
||||
|
||||
Mattermost is an open source Slack-alternative used by thousands of companies around the world in 21 languages. Learn more at [https://mattermost.com](https://mattermost.com).
|
||||
Mattermost is an open source Slack-alternative used by thousands of companies around the world in 21 languages. Learn more at [https://about.mattermost.com](https://about.mattermost.com).
|
||||
|
||||
You can download our apps from the [App Store](https://mattermost.com/mattermost-ios-app/) or [Google Play Store](https://mattermost.com/mattermost-android-app/), or [build them yourself](https://developers.mattermost.com/contribute/mobile/build-your-own/).
|
||||
You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or [build them yourself](https://developers.mattermost.com/contribute/mobile/build-your-own/).
|
||||
|
||||
We plan on releasing monthly updates with new features - check the [changelog](https://github.com/mattermost/mattermost-mobile/blob/master/CHANGELOG.md) for what features are currently supported!
|
||||
|
||||
@@ -27,8 +27,8 @@ To help with testing app updates before they're released, you can:
|
||||
- Repro steps
|
||||
- Observed behavior (including screenshot / video when possible)
|
||||
- Expected behavior
|
||||
4. (Optional) [Sign up for our team site](https://community.mattermost.com/signup_user_complete/?id=codoy5s743rq5mk18i7u5ksz7e&md=link&sbr=su)
|
||||
- Join the [Native Mobile Apps channel](https://community.mattermost.com/core/channels/native-mobile-apps) to see what's new and discuss feedback with other contributors and the core team
|
||||
4. (Optional) [Sign up for our team site](https://pre-release.mattermost.com/signup_user_complete/?id=f1924a8db44ff3bb41c96424cdc20676)
|
||||
- Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) to see what's new and discuss feedback with other contributors and the core team
|
||||
|
||||
You can leave the Beta testing program at any time:
|
||||
- On Android, [click this link](https://play.google.com/apps/testing/com.mattermost.rnbeta) while logged in with your Google Play email address used to opt-in for the Beta program, then click **Leave the program**.
|
||||
@@ -39,7 +39,7 @@ You can leave the Beta testing program at any time:
|
||||
1. Look in [GitHub issues](https://mattermost.com/pl/help-wanted-mattermost-mobile) for issues marked as [Help Wanted]
|
||||
2. Comment to let people know you’re working on it
|
||||
3. Follow [these instructions](https://developers.mattermost.com/contribute/mobile/developer-setup/) to set up your developer environment
|
||||
4. Join the [Native Mobile Apps channel](https://community.mattermost.com/core/channels/native-mobile-apps) on our team site to ask questions
|
||||
4. Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) on our team site to ask questions
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ You can still access it! We have moved the code from master to the [v1 branch](h
|
||||
|
||||
### I keep getting a message "Cannot connect to the server. Please check your server URL and internet connection."
|
||||
|
||||
This sometimes appears when there is an issue with the SSL certificate configuration.
|
||||
This sometimes appears when there is an issue with the SSL certitificate configuration.
|
||||
|
||||
To check that your SSL certificate is set up correctly, test the SSL certificate by visiting a site such as https://www.ssllabs.com/ssltest/index.html. If there’s an error about the missing chain or certificate path, there is likely an intermediate certificate missing that needs to be included.
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ apply plugin: 'kotlin-android'
|
||||
// root = file("../")
|
||||
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
|
||||
// reactNativeDir = file("../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../node_modules/@react-native/codegen")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
|
||||
// codegenDir = file("../node_modules/react-native-codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
|
||||
// cliFile = file("../node_modules/react-native/cli.js")
|
||||
/* Variants */
|
||||
@@ -98,7 +98,6 @@ def reactNativeArchitectures() {
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
namespace "com.mattermost.rnbeta"
|
||||
|
||||
@@ -111,8 +110,8 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 512
|
||||
versionName "2.15.0"
|
||||
versionCode 461
|
||||
versionName "2.1.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
@@ -191,6 +190,8 @@ dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
@@ -206,7 +207,6 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
|
||||
implementation 'io.reactivex.rxjava3:rxjava:3.1.6'
|
||||
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
|
||||
implementation 'androidx.window:window-core:1.1.0'
|
||||
implementation 'androidx.window:window-rxjava3:1.0.0'
|
||||
implementation 'androidx.window:window:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
@@ -216,6 +216,7 @@ dependencies {
|
||||
|
||||
androidTestImplementation('com.wix:detox:+')
|
||||
implementation project(':reactnativenotifications')
|
||||
implementation project(':watermelondb')
|
||||
implementation project(':watermelondb-jsi')
|
||||
}
|
||||
|
||||
@@ -223,22 +224,25 @@ configurations.all {
|
||||
resolutionStrategy {
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.name == 'play-services-base') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.2.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.1.0'
|
||||
}
|
||||
if (details.requested.name == 'play-services-tasks') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.0.2'
|
||||
}
|
||||
if (details.requested.name == 'play-services-stats') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '17.0.3'
|
||||
}
|
||||
if (details.requested.name == 'play-services-basement') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.2.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '18.1.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.10.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp-tls') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.10.0'
|
||||
}
|
||||
if (details.requested.name == 'okhttp-urlconnection') {
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.12.0'
|
||||
details.useTarget group: details.requested.group, name: details.requested.name, version: '4.10.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -7,5 +7,10 @@
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning"/>
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity
|
||||
android:name="com.facebook.react.devsupport.DevSettingsActivity"
|
||||
android:exported="false"
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<!-- Request legacy Bluetooth permissions on older devices. -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||
@@ -103,8 +102,5 @@
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- For Calls microphone to work in the background -->
|
||||
<service android:name="com.voximplant.foregroundservice.VIForegroundService"/>
|
||||
</application>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -29,7 +29,7 @@ import androidx.core.app.RemoteInput;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import com.mattermost.rnbeta.*;
|
||||
import com.nozbe.watermelondb.WMDatabase;
|
||||
import com.nozbe.watermelondb.Database;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
@@ -52,7 +52,6 @@ public class CustomPushNotificationHelper {
|
||||
public static final String PUSH_TYPE_MESSAGE = "message";
|
||||
public static final String PUSH_TYPE_CLEAR = "clear";
|
||||
public static final String PUSH_TYPE_SESSION = "session";
|
||||
public static final String CATEGORY_CAN_REPLY = "CAN_REPLY";
|
||||
|
||||
private static NotificationChannel mHighImportanceChannel;
|
||||
private static NotificationChannel mMinImportanceChannel;
|
||||
@@ -81,7 +80,7 @@ public class CustomPushNotificationHelper {
|
||||
.setKey(senderId)
|
||||
.setName(senderName);
|
||||
|
||||
if (serverUrl != null && type != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
||||
if (avatar != null) {
|
||||
@@ -133,9 +132,8 @@ public class CustomPushNotificationHelper {
|
||||
private static void addNotificationReplyAction(Context context, NotificationCompat.Builder notification, Bundle bundle, int notificationId) {
|
||||
String postId = bundle.getString("post_id");
|
||||
String serverUrl = bundle.getString("server_url");
|
||||
boolean canReply = bundle.containsKey("category") && Objects.equals(bundle.getString("category"), CATEGORY_CAN_REPLY);
|
||||
|
||||
if (android.text.TextUtils.isEmpty(postId) || serverUrl == null || !canReply) {
|
||||
if (android.text.TextUtils.isEmpty(postId) || serverUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,7 +181,7 @@ public class CustomPushNotificationHelper {
|
||||
String rootId = bundle.getString("root_id");
|
||||
int notificationId = postId != null ? postId.hashCode() : MESSAGE_NOTIFICATION_ID;
|
||||
|
||||
boolean is_crt_enabled = bundle.containsKey("is_crt_enabled") && Objects.equals(bundle.getString("is_crt_enabled"), "true");
|
||||
boolean is_crt_enabled = bundle.containsKey("is_crt_enabled") && bundle.getString("is_crt_enabled").equals("true");
|
||||
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
|
||||
|
||||
addNotificationExtras(notification, bundle);
|
||||
@@ -275,7 +273,7 @@ public class CustomPushNotificationHelper {
|
||||
.setKey(senderId)
|
||||
.setName("Me");
|
||||
|
||||
if (serverUrl != null && type != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, "me", urlOverride);
|
||||
if (avatar != null) {
|
||||
@@ -418,7 +416,7 @@ public class CustomPushNotificationHelper {
|
||||
} else {
|
||||
DatabaseHelper dbHelper = DatabaseHelper.Companion.getInstance();
|
||||
if (dbHelper != null) {
|
||||
WMDatabase db = getDatabaseForServer(dbHelper, context, serverUrl);
|
||||
Database db = getDatabaseForServer(dbHelper, context, serverUrl);
|
||||
if (db != null) {
|
||||
lastUpdateAt = getLastPictureUpdate(db, userId);
|
||||
if (lastUpdateAt == null) {
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
package com.mattermost.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
typealias QueryArgs = Array<Any?>
|
||||
|
||||
class DatabaseHelper {
|
||||
var defaultDatabase: WMDatabase? = null
|
||||
var defaultDatabase: Database? = null
|
||||
|
||||
val onlyServerUrl: String?
|
||||
get() {
|
||||
@@ -43,7 +39,7 @@ class DatabaseHelper {
|
||||
private fun setDefaultDatabase(context: Context) {
|
||||
val databaseName = "app.db"
|
||||
val databasePath = Uri.fromFile(context.filesDir).toString() + "/" + databaseName
|
||||
defaultDatabase = WMDatabase.getInstance(databasePath, context)
|
||||
defaultDatabase = Database(databasePath, context)
|
||||
}
|
||||
|
||||
internal fun JSONObject.toMap(): Map<String, Any?> = keys().asSequence().associateWith { it ->
|
||||
@@ -77,15 +73,3 @@ class DatabaseHelper {
|
||||
private set
|
||||
}
|
||||
}
|
||||
|
||||
fun WritableMap.mapCursor(cursor: Cursor) {
|
||||
for (i in 0 until cursor.columnCount) {
|
||||
when (cursor.getType(i)) {
|
||||
Cursor.FIELD_TYPE_NULL -> putNull(cursor.getColumnName(i))
|
||||
Cursor.FIELD_TYPE_INTEGER -> putDouble(cursor.getColumnName(i), cursor.getDouble(i))
|
||||
Cursor.FIELD_TYPE_FLOAT -> putDouble(cursor.getColumnName(i), cursor.getDouble(i))
|
||||
Cursor.FIELD_TYPE_STRING -> putString(cursor.getColumnName(i), cursor.getString(i))
|
||||
else -> putString(cursor.getColumnName(i), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class Network {
|
||||
private static final Promise emptyPromise = new ResolvePromise();
|
||||
|
||||
public static void init(Context context) {
|
||||
final ReactApplicationContext reactContext = (APIClientModule.context == null) ? new ReactApplicationContext(context) : APIClientModule.context;
|
||||
final ReactApplicationContext reactContext = new ReactApplicationContext(context);
|
||||
clientModule = new APIClientModule(reactContext);
|
||||
createClientOptions();
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
fun insertCategory(db: WMDatabase, category: ReadableMap) {
|
||||
fun insertCategory(db: Database, category: ReadableMap) {
|
||||
try {
|
||||
val id = category.getString("id") ?: return
|
||||
val collapsed = false
|
||||
@@ -31,7 +31,7 @@ fun insertCategory(db: WMDatabase, category: ReadableMap) {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertCategoryChannels(db: WMDatabase, categoryId: String, teamId: String, channelIds: ReadableArray) {
|
||||
fun insertCategoryChannels(db: Database, categoryId: String, teamId: String, channelIds: ReadableArray) {
|
||||
try {
|
||||
for (i in 0 until channelIds.size()) {
|
||||
val channelId = channelIds.getString(i)
|
||||
@@ -50,7 +50,7 @@ fun insertCategoryChannels(db: WMDatabase, categoryId: String, teamId: String, c
|
||||
}
|
||||
}
|
||||
|
||||
fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
||||
fun insertCategoriesWithChannels(db: Database, orderCategories: ReadableMap) {
|
||||
val categories = orderCategories.getArray("categories") ?: return
|
||||
for (i in 0 until categories.size()) {
|
||||
val category = categories.getMap(i)
|
||||
@@ -64,7 +64,7 @@ fun insertCategoriesWithChannels(db: WMDatabase, orderCategories: ReadableMap) {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertChannelToDefaultCategory(db: WMDatabase, categoryChannels: ReadableArray) {
|
||||
fun insertChannelToDefaultCategory(db: Database, categoryChannels: ReadableArray) {
|
||||
try {
|
||||
for (i in 0 until categoryChannels.size()) {
|
||||
val cc = categoryChannels.getMap(i)
|
||||
|
||||
@@ -3,11 +3,11 @@ package com.mattermost.helpers.database_extension
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.DatabaseHelper
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
fun findChannel(db: WMDatabase?, channelId: String): Boolean {
|
||||
fun findChannel(db: Database?, channelId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "Channel", channelId)
|
||||
return team != null
|
||||
@@ -15,7 +15,7 @@ fun findChannel(db: WMDatabase?, channelId: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun findMyChannel(db: WMDatabase?, channelId: String): Boolean {
|
||||
fun findMyChannel(db: Database?, channelId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "MyChannel", channelId)
|
||||
return team != null
|
||||
@@ -23,7 +23,7 @@ fun findMyChannel(db: WMDatabase?, channelId: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
internal fun handleChannel(db: WMDatabase, channel: ReadableMap) {
|
||||
internal fun handleChannel(db: Database, channel: ReadableMap) {
|
||||
try {
|
||||
val exists = channel.getString("id")?.let { findChannel(db, it) } ?: false
|
||||
if (!exists) {
|
||||
@@ -37,7 +37,7 @@ internal fun handleChannel(db: WMDatabase, channel: ReadableMap) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun DatabaseHelper.handleMyChannel(db: WMDatabase, myChannel: ReadableMap, postsData: ReadableMap?, receivingThreads: Boolean) {
|
||||
internal fun DatabaseHelper.handleMyChannel(db: Database, myChannel: ReadableMap, postsData: ReadableMap?, receivingThreads: Boolean) {
|
||||
try {
|
||||
val json = ReadableMapUtils.toJSONObject(myChannel)
|
||||
val exists = myChannel.getString("id")?.let { findMyChannel(db, it) } ?: false
|
||||
@@ -71,7 +71,7 @@ internal fun DatabaseHelper.handleMyChannel(db: WMDatabase, myChannel: ReadableM
|
||||
}
|
||||
}
|
||||
|
||||
fun insertChannel(db: WMDatabase, channel: JSONObject): Boolean {
|
||||
fun insertChannel(db: Database, channel: JSONObject): Boolean {
|
||||
val id = try { channel.getString("id") } catch (e: JSONException) { return false }
|
||||
val createAt = try { channel.getDouble("create_at") } catch (e: JSONException) { 0 }
|
||||
val deleteAt = try { channel.getDouble("delete_at") } catch (e: JSONException) { 0 }
|
||||
@@ -104,7 +104,7 @@ fun insertChannel(db: WMDatabase, channel: JSONObject): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertChannelInfo(db: WMDatabase, channel: JSONObject) {
|
||||
fun insertChannelInfo(db: Database, channel: JSONObject) {
|
||||
val id = try { channel.getString("id") } catch (e: JSONException) { return }
|
||||
val header = try { channel.getString("header") } catch (e: JSONException) { "" }
|
||||
val purpose = try { channel.getString("purpose") } catch (e: JSONException) { "" }
|
||||
@@ -123,7 +123,7 @@ fun insertChannelInfo(db: WMDatabase, channel: JSONObject) {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertMyChannel(db: WMDatabase, myChanel: JSONObject): Boolean {
|
||||
fun insertMyChannel(db: Database, myChanel: JSONObject): Boolean {
|
||||
return try {
|
||||
val id = try { myChanel.getString("id") } catch (e: JSONException) { return false }
|
||||
val roles = try { myChanel.getString("roles") } catch (e: JSONException) { "" }
|
||||
@@ -156,7 +156,7 @@ fun insertMyChannel(db: WMDatabase, myChanel: JSONObject): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertMyChannelSettings(db: WMDatabase, myChanel: JSONObject) {
|
||||
fun insertMyChannelSettings(db: Database, myChanel: JSONObject) {
|
||||
try {
|
||||
val id = try { myChanel.getString("id") } catch (e: JSONException) { return }
|
||||
val notifyProps = try { myChanel.getString("notify_props") } catch (e: JSONException) { return }
|
||||
@@ -173,7 +173,7 @@ fun insertMyChannelSettings(db: WMDatabase, myChanel: JSONObject) {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertChannelMember(db: WMDatabase, myChanel: JSONObject) {
|
||||
fun insertChannelMember(db: Database, myChanel: JSONObject) {
|
||||
try {
|
||||
val userId = queryCurrentUserId(db) ?: return
|
||||
val channelId = try { myChanel.getString("id") } catch (e: JSONException) { return }
|
||||
@@ -193,7 +193,7 @@ fun insertChannelMember(db: WMDatabase, myChanel: JSONObject) {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMyChannel(db: WMDatabase, myChanel: JSONObject) {
|
||||
fun updateMyChannel(db: Database, myChanel: JSONObject) {
|
||||
try {
|
||||
val id = try { myChanel.getString("id") } catch (e: JSONException) { return }
|
||||
val msgCount = try { myChanel.getInt("message_count") } catch (e: JSONException) { 0 }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import org.json.JSONArray
|
||||
|
||||
internal fun insertCustomEmojis(db: WMDatabase, customEmojis: JSONArray) {
|
||||
internal fun insertCustomEmojis(db: Database, customEmojis: JSONArray) {
|
||||
for (i in 0 until customEmojis.length()) {
|
||||
try {
|
||||
val emoji = customEmojis.getJSONObject(i)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
|
||||
internal fun insertFiles(db: WMDatabase, files: JSONArray) {
|
||||
internal fun insertFiles(db: Database, files: JSONArray) {
|
||||
try {
|
||||
for (i in 0 until files.length()) {
|
||||
val file = files.getJSONObject(i)
|
||||
|
||||
@@ -5,13 +5,13 @@ import android.text.TextUtils
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.DatabaseHelper
|
||||
import com.mattermost.helpers.QueryArgs
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.QueryArgs
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import java.util.*
|
||||
import kotlin.Exception
|
||||
|
||||
internal fun DatabaseHelper.saveToDatabase(db: WMDatabase, data: ReadableMap, teamId: String?, channelId: String?, receivingThreads: Boolean) {
|
||||
internal fun DatabaseHelper.saveToDatabase(db: Database, data: ReadableMap, teamId: String?, channelId: String?, receivingThreads: Boolean) {
|
||||
db.transaction {
|
||||
val posts = data.getMap("posts")
|
||||
data.getMap("team")?.let { insertTeam(db, it) }
|
||||
@@ -50,14 +50,14 @@ fun DatabaseHelper.getServerUrlForIdentifier(identifier: String): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun DatabaseHelper.getDatabaseForServer(context: Context?, serverUrl: String): WMDatabase? {
|
||||
fun DatabaseHelper.getDatabaseForServer(context: Context?, serverUrl: String): Database? {
|
||||
try {
|
||||
val query = "SELECT db_path FROM Servers WHERE url=?"
|
||||
defaultDatabase!!.rawQuery(query, arrayOf(serverUrl)).use { cursor ->
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val databasePath = cursor.getString(0)
|
||||
return WMDatabase.getInstance(databasePath, context!!)
|
||||
return Database(databasePath, context!!)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -67,7 +67,7 @@ fun DatabaseHelper.getDatabaseForServer(context: Context?, serverUrl: String): W
|
||||
return null
|
||||
}
|
||||
|
||||
fun find(db: WMDatabase, tableName: String, id: String?): ReadableMap? {
|
||||
fun find(db: Database, tableName: String, id: String?): ReadableMap? {
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT * FROM $tableName WHERE id == ? LIMIT 1",
|
||||
@@ -87,7 +87,7 @@ fun find(db: WMDatabase, tableName: String, id: String?): ReadableMap? {
|
||||
}
|
||||
}
|
||||
|
||||
fun findByColumns(db: WMDatabase, tableName: String, columnNames: Array<String>, values: QueryArgs): ReadableMap? {
|
||||
fun findByColumns(db: Database, tableName: String, columnNames: Array<String>, values: QueryArgs): ReadableMap? {
|
||||
try {
|
||||
val whereString = columnNames.joinToString(" AND ") { "$it = ?" }
|
||||
db.rawQuery(
|
||||
@@ -108,7 +108,7 @@ fun findByColumns(db: WMDatabase, tableName: String, columnNames: Array<String>,
|
||||
}
|
||||
}
|
||||
|
||||
fun queryIds(db: WMDatabase, tableName: String, ids: Array<String>): List<String> {
|
||||
fun queryIds(db: Database, tableName: String, ids: Array<String>): List<String> {
|
||||
val list: MutableList<String> = ArrayList()
|
||||
val args = TextUtils.join(",", Arrays.stream(ids).map { "?" }.toArray())
|
||||
try {
|
||||
@@ -129,7 +129,7 @@ fun queryIds(db: WMDatabase, tableName: String, ids: Array<String>): List<String
|
||||
return list
|
||||
}
|
||||
|
||||
fun queryByColumn(db: WMDatabase, tableName: String, columnName: String, values: Array<Any?>): List<String> {
|
||||
fun queryByColumn(db: Database, tableName: String, columnName: String, values: Array<Any?>): List<String> {
|
||||
val list: MutableList<String> = ArrayList()
|
||||
val args = TextUtils.join(",", Arrays.stream(values).map { "?" }.toArray())
|
||||
try {
|
||||
@@ -149,7 +149,7 @@ fun queryByColumn(db: WMDatabase, tableName: String, columnName: String, values:
|
||||
return list
|
||||
}
|
||||
|
||||
fun countByColumn(db: WMDatabase, tableName: String, columnName: String, value: Any?): Int {
|
||||
fun countByColumn(db: Database, tableName: String, columnName: String, value: Any?): Int {
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT COUNT(*) FROM $tableName WHERE $columnName == ? LIMIT 1",
|
||||
|
||||
@@ -3,13 +3,13 @@ package com.mattermost.helpers.database_extension
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.DatabaseHelper
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import kotlin.Exception
|
||||
|
||||
internal fun queryLastPostCreateAt(db: WMDatabase?, channelId: String): Double? {
|
||||
internal fun queryLastPostCreateAt(db: Database?, channelId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
val postsInChannelQuery = "SELECT earliest, latest FROM PostsInChannel WHERE channel_id=? ORDER BY latest DESC LIMIT 1"
|
||||
@@ -35,7 +35,7 @@ internal fun queryLastPostCreateAt(db: WMDatabase?, channelId: String): Double?
|
||||
return null
|
||||
}
|
||||
|
||||
fun queryPostSinceForChannel(db: WMDatabase?, channelId: String): Double? {
|
||||
fun queryPostSinceForChannel(db: Database?, channelId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
val postsInChannelQuery = "SELECT last_fetched_at FROM MyChannel WHERE id=? LIMIT 1"
|
||||
@@ -57,7 +57,7 @@ fun queryPostSinceForChannel(db: WMDatabase?, channelId: String): Double? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun queryLastPostInThread(db: WMDatabase?, rootId: String): Double? {
|
||||
fun queryLastPostInThread(db: Database?, rootId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
val query = "SELECT create_at FROM Post WHERE root_id=? AND delete_at=0 ORDER BY create_at DESC LIMIT 1"
|
||||
@@ -75,7 +75,7 @@ fun queryLastPostInThread(db: WMDatabase?, rootId: String): Double? {
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun insertPost(db: WMDatabase, post: JSONObject) {
|
||||
internal fun insertPost(db: Database, post: JSONObject) {
|
||||
try {
|
||||
val id = try { post.getString("id") } catch (e: JSONException) { return }
|
||||
val channelId = try { post.getString("channel_id") } catch (e: JSONException) { return }
|
||||
@@ -86,7 +86,6 @@ internal fun insertPost(db: WMDatabase, post: JSONObject) {
|
||||
val editAt = try { post.getDouble("edit_at") } catch (e: JSONException) { 0 }
|
||||
val isPinned = try { post.getBoolean("is_pinned") } catch (e: JSONException) { false }
|
||||
val message = try { post.getString("message") } catch (e: JSONException) { "" }
|
||||
val messageSource = try { post.getString("message_source") } catch (e: JSONException) { "" }
|
||||
val metadata = try { post.getJSONObject("metadata") } catch (e: JSONException) { JSONObject() }
|
||||
val originalId = try { post.getString("original_id") } catch (e: JSONException) { "" }
|
||||
val pendingId = try { post.getString("pending_post_id") } catch (e: JSONException) { "" }
|
||||
@@ -101,13 +100,13 @@ internal fun insertPost(db: WMDatabase, post: JSONObject) {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO Post
|
||||
(id, channel_id, create_at, delete_at, update_at, edit_at, is_pinned, message, message_source, metadata, original_id, pending_post_id,
|
||||
(id, channel_id, create_at, delete_at, update_at, edit_at, is_pinned, message, metadata, original_id, pending_post_id,
|
||||
previous_post_id, root_id, type, user_id, props, _changed, _status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', 'created')
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', 'created')
|
||||
""".trimIndent(),
|
||||
arrayOf(
|
||||
id, channelId, createAt, deleteAt, updateAt, editAt,
|
||||
isPinned, message, messageSource, metadata.toString(),
|
||||
isPinned, message, metadata.toString(),
|
||||
originalId, pendingId, prevId, rootId,
|
||||
type, userId, props
|
||||
)
|
||||
@@ -129,7 +128,7 @@ internal fun insertPost(db: WMDatabase, post: JSONObject) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updatePost(db: WMDatabase, post: JSONObject) {
|
||||
internal fun updatePost(db: Database, post: JSONObject) {
|
||||
try {
|
||||
val id = try { post.getString("id") } catch (e: JSONException) { return }
|
||||
val channelId = try { post.getString("channel_id") } catch (e: JSONException) { return }
|
||||
@@ -140,7 +139,6 @@ internal fun updatePost(db: WMDatabase, post: JSONObject) {
|
||||
val editAt = try { post.getDouble("edit_at") } catch (e: JSONException) { 0 }
|
||||
val isPinned = try { post.getBoolean("is_pinned") } catch (e: JSONException) { false }
|
||||
val message = try { post.getString("message") } catch (e: JSONException) { "" }
|
||||
val messageSource = try { post.getString("message_source") } catch (e: JSONException) { "" }
|
||||
val metadata = try { post.getJSONObject("metadata") } catch (e: JSONException) { JSONObject() }
|
||||
val originalId = try { post.getString("original_id") } catch (e: JSONException) { "" }
|
||||
val pendingId = try { post.getString("pending_post_id") } catch (e: JSONException) { "" }
|
||||
@@ -156,13 +154,13 @@ internal fun updatePost(db: WMDatabase, post: JSONObject) {
|
||||
db.execute(
|
||||
"""
|
||||
UPDATE Post SET channel_id = ?, create_at = ?, delete_at = ?, update_at =?, edit_at =?,
|
||||
is_pinned = ?, message = ?, message_source = ?, metadata = ?, original_id = ?, pending_post_id = ?, previous_post_id = ?,
|
||||
is_pinned = ?, message = ?, metadata = ?, original_id = ?, pending_post_id = ?, previous_post_id = ?,
|
||||
root_id = ?, type = ?, user_id = ?, props = ?, _status = 'updated'
|
||||
WHERE id = ?
|
||||
""".trimIndent(),
|
||||
arrayOf(
|
||||
channelId, createAt, deleteAt, updateAt, editAt,
|
||||
isPinned, message, messageSource, metadata.toString(),
|
||||
isPinned, message, metadata.toString(),
|
||||
originalId, pendingId, prevId, rootId,
|
||||
type, userId, props,
|
||||
id,
|
||||
@@ -182,7 +180,7 @@ internal fun updatePost(db: WMDatabase, post: JSONObject) {
|
||||
}
|
||||
}
|
||||
|
||||
fun DatabaseHelper.handlePosts(db: WMDatabase, postsData: ReadableMap?, channelId: String, receivingThreads: Boolean) {
|
||||
fun DatabaseHelper.handlePosts(db: Database, postsData: ReadableMap?, channelId: String, receivingThreads: Boolean) {
|
||||
// Posts, PostInChannel, PostInThread, Reactions, Files, CustomEmojis, Users
|
||||
try {
|
||||
if (postsData != null) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.RandomId
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
|
||||
internal fun findPostInChannel(chunks: ReadableArray, earliest: Double, latest: Double): ReadableMap? {
|
||||
for (i in 0 until chunks.size()) {
|
||||
@@ -18,7 +18,7 @@ internal fun findPostInChannel(chunks: ReadableArray, earliest: Double, latest:
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun insertPostInChannel(db: WMDatabase, channelId: String, earliest: Double, latest: Double): ReadableMap? {
|
||||
internal fun insertPostInChannel(db: Database, channelId: String, earliest: Double, latest: Double): ReadableMap? {
|
||||
return try {
|
||||
val id = RandomId.generate()
|
||||
db.execute(
|
||||
@@ -41,7 +41,7 @@ internal fun insertPostInChannel(db: WMDatabase, channelId: String, earliest: Do
|
||||
}
|
||||
}
|
||||
|
||||
internal fun mergePostsInChannel(db: WMDatabase, existingChunks: ReadableArray, newChunk: ReadableMap) {
|
||||
internal fun mergePostsInChannel(db: Database, existingChunks: ReadableArray, newChunk: ReadableMap) {
|
||||
for (i in 0 until existingChunks.size()) {
|
||||
try {
|
||||
val chunk = existingChunks.getMap(i)
|
||||
@@ -56,7 +56,7 @@ internal fun mergePostsInChannel(db: WMDatabase, existingChunks: ReadableArray,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun handlePostsInChannel(db: WMDatabase, channelId: String, earliest: Double, latest: Double) {
|
||||
internal fun handlePostsInChannel(db: Database, channelId: String, earliest: Double, latest: Double) {
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT id, channel_id, earliest, latest FROM PostsInChannel WHERE channel_id = ?",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
|
||||
fun getTeammateDisplayNameSetting(db: WMDatabase): String {
|
||||
fun getTeammateDisplayNameSetting(db: Database): String {
|
||||
val configSetting = queryConfigDisplayNameSetting(db)
|
||||
if (configSetting != null) {
|
||||
return configSetting
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.mattermost.helpers.RandomId
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import org.json.JSONArray
|
||||
|
||||
internal fun insertReactions(db: WMDatabase, reactions: JSONArray) {
|
||||
internal fun insertReactions(db: Database, reactions: JSONArray) {
|
||||
for (i in 0 until reactions.length()) {
|
||||
try {
|
||||
val reaction = reactions.getJSONObject(i)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import org.json.JSONObject
|
||||
|
||||
fun queryCurrentUserId(db: WMDatabase): String? {
|
||||
fun queryCurrentUserId(db: Database): String? {
|
||||
val result = find(db, "System", "currentUserId")
|
||||
return result?.getString("value")?.removeSurrounding("\"")
|
||||
}
|
||||
|
||||
fun queryCurrentTeamId(db: WMDatabase): String? {
|
||||
fun queryCurrentTeamId(db: Database): String? {
|
||||
val result = find(db, "System", "currentTeamId")
|
||||
return result?.getString("value")?.removeSurrounding("\"")
|
||||
}
|
||||
|
||||
fun queryConfigDisplayNameSetting(db: WMDatabase): String? {
|
||||
fun queryConfigDisplayNameSetting(db: Database): String? {
|
||||
val license = find(db, "System", "license")
|
||||
val lockDisplayName = find(db, "Config", "LockTeammateNameDisplay")
|
||||
val displayName = find(db, "Config", "TeammateNameDisplay")
|
||||
|
||||
@@ -3,10 +3,10 @@ package com.mattermost.helpers.database_extension
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.NoSuchKeyException
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
|
||||
fun findTeam(db: WMDatabase?, teamId: String): Boolean {
|
||||
fun findTeam(db: Database?, teamId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "Team", teamId)
|
||||
return team != null
|
||||
@@ -14,7 +14,7 @@ fun findTeam(db: WMDatabase?, teamId: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun findMyTeam(db: WMDatabase?, teamId: String): Boolean {
|
||||
fun findMyTeam(db: Database?, teamId: String): Boolean {
|
||||
if (db != null) {
|
||||
val team = find(db, "MyTeam", teamId)
|
||||
return team != null
|
||||
@@ -22,7 +22,7 @@ fun findMyTeam(db: WMDatabase?, teamId: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun queryMyTeams(db: WMDatabase?): ArrayList<ReadableMap>? {
|
||||
fun queryMyTeams(db: Database?): ArrayList<ReadableMap>? {
|
||||
db?.rawQuery("SELECT * FROM MyTeam")?.use { cursor ->
|
||||
val results = ArrayList<ReadableMap>()
|
||||
if (cursor.count > 0) {
|
||||
@@ -38,7 +38,7 @@ fun queryMyTeams(db: WMDatabase?): ArrayList<ReadableMap>? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun insertTeam(db: WMDatabase, team: ReadableMap): Boolean {
|
||||
fun insertTeam(db: Database, team: ReadableMap): Boolean {
|
||||
val id = try { team.getString("id") } catch (e: Exception) { return false }
|
||||
val deleteAt = try {team.getDouble("delete_at") } catch (e: Exception) { 0 }
|
||||
if (deleteAt.toInt() > 0) {
|
||||
@@ -78,7 +78,7 @@ fun insertTeam(db: WMDatabase, team: ReadableMap): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertMyTeam(db: WMDatabase, myTeam: ReadableMap): Boolean {
|
||||
fun insertMyTeam(db: Database, myTeam: ReadableMap): Boolean {
|
||||
val currentUserId = queryCurrentUserId(db) ?: return false
|
||||
val id = try { myTeam.getString("id") } catch (e: NoSuchKeyException) { return false }
|
||||
val roles = try { myTeam.getString("roles") } catch (e: NoSuchKeyException) { "" }
|
||||
|
||||
@@ -5,11 +5,11 @@ import com.facebook.react.bridge.NoSuchKeyException
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.RandomId
|
||||
import com.mattermost.helpers.mapCursor
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import com.nozbe.watermelondb.mapCursor
|
||||
import org.json.JSONObject
|
||||
|
||||
internal fun insertThread(db: WMDatabase, thread: ReadableMap) {
|
||||
internal fun insertThread(db: Database, thread: ReadableMap) {
|
||||
// These fields are not present when we extract threads from posts
|
||||
try {
|
||||
val id = try { thread.getString("id") } catch (e: NoSuchKeyException) { return }
|
||||
@@ -36,7 +36,7 @@ internal fun insertThread(db: WMDatabase, thread: ReadableMap) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updateThread(db: WMDatabase, thread: ReadableMap, existingRecord: ReadableMap) {
|
||||
internal fun updateThread(db: Database, thread: ReadableMap, existingRecord: ReadableMap) {
|
||||
try {
|
||||
// These fields are not present when we extract threads from posts
|
||||
val id = try { thread.getString("id") } catch (e: NoSuchKeyException) { return }
|
||||
@@ -63,7 +63,7 @@ internal fun updateThread(db: WMDatabase, thread: ReadableMap, existingRecord: R
|
||||
}
|
||||
}
|
||||
|
||||
internal fun insertThreadParticipants(db: WMDatabase, threadId: String, participants: ReadableArray) {
|
||||
internal fun insertThreadParticipants(db: Database, threadId: String, participants: ReadableArray) {
|
||||
for (i in 0 until participants.size()) {
|
||||
try {
|
||||
val participant = participants.getMap(i)
|
||||
@@ -82,7 +82,7 @@ internal fun insertThreadParticipants(db: WMDatabase, threadId: String, particip
|
||||
}
|
||||
}
|
||||
|
||||
fun insertTeamThreadsSync(db: WMDatabase, teamId: String, earliest: Double, latest: Double) {
|
||||
fun insertTeamThreadsSync(db: Database, teamId: String, earliest: Double, latest: Double) {
|
||||
try {
|
||||
val query = """
|
||||
INSERT INTO TeamThreadsSync (id, _changed, _status, earliest, latest)
|
||||
@@ -94,7 +94,7 @@ fun insertTeamThreadsSync(db: WMDatabase, teamId: String, earliest: Double, late
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTeamThreadsSync(db: WMDatabase, teamId: String, earliest: Double, latest: Double, existingRecord: ReadableMap) {
|
||||
fun updateTeamThreadsSync(db: Database, teamId: String, earliest: Double, latest: Double, existingRecord: ReadableMap) {
|
||||
try {
|
||||
val storeEarliest = minOf(earliest, existingRecord.getDouble("earliest"))
|
||||
val storeLatest = maxOf(latest, existingRecord.getDouble("latest"))
|
||||
@@ -105,7 +105,7 @@ fun updateTeamThreadsSync(db: WMDatabase, teamId: String, earliest: Double, late
|
||||
}
|
||||
}
|
||||
|
||||
fun syncParticipants(db: WMDatabase, thread: ReadableMap) {
|
||||
fun syncParticipants(db: Database, thread: ReadableMap) {
|
||||
try {
|
||||
val threadId = thread.getString("id")
|
||||
val participants = thread.getArray("participants")
|
||||
@@ -121,7 +121,7 @@ fun syncParticipants(db: WMDatabase, thread: ReadableMap) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun handlePostsInThread(db: WMDatabase, postsInThread: Map<String, List<JSONObject>>) {
|
||||
internal fun handlePostsInThread(db: Database, postsInThread: Map<String, List<JSONObject>>) {
|
||||
postsInThread.forEach { (key, list) ->
|
||||
try {
|
||||
val sorted = list.sortedBy { it.getDouble("create_at") }
|
||||
@@ -161,7 +161,7 @@ internal fun handlePostsInThread(db: WMDatabase, postsInThread: Map<String, List
|
||||
}
|
||||
}
|
||||
|
||||
fun handleThreads(db: WMDatabase, threads: ArrayList<ReadableMap>, teamId: String?) {
|
||||
fun handleThreads(db: Database, threads: ArrayList<ReadableMap>, teamId: String?) {
|
||||
val teamIds = ArrayList<String>()
|
||||
if (teamId.isNullOrEmpty()) {
|
||||
val myTeams = queryMyTeams(db)
|
||||
@@ -186,7 +186,7 @@ fun handleThreads(db: WMDatabase, threads: ArrayList<ReadableMap>, teamId: Strin
|
||||
handleTeamThreadsSync(db, threads, teamIds)
|
||||
}
|
||||
|
||||
fun handleThread(db: WMDatabase, thread: ReadableMap, teamIds: ArrayList<String>) {
|
||||
fun handleThread(db: Database, thread: ReadableMap, teamIds: ArrayList<String>) {
|
||||
// Insert/Update the thread
|
||||
val threadId = thread.getString("id")
|
||||
val isFollowing = thread.getBoolean("is_following")
|
||||
@@ -207,7 +207,7 @@ fun handleThread(db: WMDatabase, thread: ReadableMap, teamIds: ArrayList<String>
|
||||
}
|
||||
}
|
||||
|
||||
fun handleThreadInTeam(db: WMDatabase, thread: ReadableMap, teamId: String) {
|
||||
fun handleThreadInTeam(db: Database, thread: ReadableMap, teamId: String) {
|
||||
val threadId = thread.getString("id") ?: return
|
||||
val existingRecord = findByColumns(
|
||||
db,
|
||||
@@ -229,7 +229,7 @@ fun handleThreadInTeam(db: WMDatabase, thread: ReadableMap, teamId: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleTeamThreadsSync(db: WMDatabase, threadList: ArrayList<ReadableMap>, teamIds: ArrayList<String>) {
|
||||
fun handleTeamThreadsSync(db: Database, threadList: ArrayList<ReadableMap>, teamIds: ArrayList<String>) {
|
||||
val sortedList = threadList.filter{ it.getBoolean("is_following") }
|
||||
.sortedBy { it.getDouble("last_reply_at") }
|
||||
.map { it.getDouble("last_reply_at") }
|
||||
|
||||
@@ -4,9 +4,9 @@ import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.NoSuchKeyException
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
fun getLastPictureUpdate(db: WMDatabase?, userId: String): Double? {
|
||||
fun getLastPictureUpdate(db: Database?, userId: String): Double? {
|
||||
try {
|
||||
if (db != null) {
|
||||
var id = userId
|
||||
@@ -28,7 +28,7 @@ fun getLastPictureUpdate(db: WMDatabase?, userId: String): Double? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun getCurrentUserLocale(db: WMDatabase): String {
|
||||
fun getCurrentUserLocale(db: Database): String {
|
||||
try {
|
||||
val currentUserId = queryCurrentUserId(db) ?: return "en"
|
||||
val userQuery = "SELECT locale FROM User WHERE id=?"
|
||||
@@ -45,7 +45,7 @@ fun getCurrentUserLocale(db: WMDatabase): String {
|
||||
return "en"
|
||||
}
|
||||
|
||||
fun handleUsers(db: WMDatabase, users: ReadableArray) {
|
||||
fun handleUsers(db: Database, users: ReadableArray) {
|
||||
for (i in 0 until users.size()) {
|
||||
val user = users.getMap(i)
|
||||
val roles = user.getString("roles") ?: ""
|
||||
|
||||
@@ -7,9 +7,9 @@ import com.mattermost.helpers.PushNotificationDataRunnable
|
||||
import com.mattermost.helpers.database_extension.findByColumns
|
||||
import com.mattermost.helpers.database_extension.queryCurrentUserId
|
||||
import com.mattermost.helpers.database_extension.queryMyTeams
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyTeamCategories(db: WMDatabase, serverUrl: String, teamId: String): ReadableMap? {
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyTeamCategories(db: Database, serverUrl: String, teamId: String): ReadableMap? {
|
||||
return try {
|
||||
val userId = queryCurrentUserId(db)
|
||||
val categories = fetch(serverUrl, "/api/v4/users/$userId/teams/$teamId/channels/categories")
|
||||
@@ -20,7 +20,7 @@ suspend fun PushNotificationDataRunnable.Companion.fetchMyTeamCategories(db: WMD
|
||||
}
|
||||
}
|
||||
|
||||
fun PushNotificationDataRunnable.Companion.addToDefaultCategoryIfNeeded(db: WMDatabase, channel: ReadableMap): ReadableArray? {
|
||||
fun PushNotificationDataRunnable.Companion.addToDefaultCategoryIfNeeded(db: Database, channel: ReadableMap): ReadableArray? {
|
||||
val channelId = channel.getString("id") ?: return null
|
||||
val channelType = channel.getString("type")
|
||||
val categoryChannels = Arguments.createArray()
|
||||
@@ -44,7 +44,7 @@ fun PushNotificationDataRunnable.Companion.addToDefaultCategoryIfNeeded(db: WMDa
|
||||
return categoryChannels
|
||||
}
|
||||
|
||||
private fun categoryChannelForTeam(db: WMDatabase, channelId: String, teamId: String?, type: String): ReadableMap? {
|
||||
private fun categoryChannelForTeam(db: Database, channelId: String, teamId: String?, type: String): ReadableMap? {
|
||||
teamId?.let { id ->
|
||||
val category = findByColumns(db, "Category", arrayOf("type", "team_id"), arrayOf(type, id))
|
||||
val categoryId = category?.getString("id")
|
||||
|
||||
@@ -8,11 +8,12 @@ import com.mattermost.helpers.database_extension.findChannel
|
||||
import com.mattermost.helpers.database_extension.getCurrentUserLocale
|
||||
import com.mattermost.helpers.database_extension.getTeammateDisplayNameSetting
|
||||
import com.mattermost.helpers.database_extension.queryCurrentUserId
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: WMDatabase, serverUrl: String, channelId: String, isCRTEnabled: Boolean): Triple<ReadableMap?, ReadableMap?, ReadableArray?> {
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchMyChannel(db: Database, serverUrl: String, channelId: String, isCRTEnabled: Boolean): Triple<ReadableMap?, ReadableMap?, ReadableArray?> {
|
||||
val channel = fetch(serverUrl, "/api/v4/channels/$channelId")
|
||||
var channelData = channel?.getMap("data")
|
||||
val myChannelData = channelData?.let { fetchMyChannelData(serverUrl, channelId, isCRTEnabled, it) }
|
||||
@@ -108,7 +109,7 @@ private suspend fun PushNotificationDataRunnable.Companion.fetchMyChannelData(se
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun PushNotificationDataRunnable.Companion.fetchProfileInChannel(db: WMDatabase, serverUrl: String, channelId: String): ReadableArray? {
|
||||
private suspend fun PushNotificationDataRunnable.Companion.fetchProfileInChannel(db: Database, serverUrl: String, channelId: String): ReadableArray? {
|
||||
return try {
|
||||
val currentUserId = queryCurrentUserId(db)
|
||||
val profilesInChannel = fetch(serverUrl, "/api/v4/users?in_channel=${channelId}&page=0&per_page=8&sort=")
|
||||
|
||||
@@ -9,10 +9,10 @@ import com.mattermost.helpers.PushNotificationDataRunnable
|
||||
import com.mattermost.helpers.ReadableArrayUtils
|
||||
import com.mattermost.helpers.ReadableMapUtils
|
||||
import com.mattermost.helpers.database_extension.*
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
||||
db: WMDatabase, serverUrl: String, channelId: String, isCRTEnabled: Boolean,
|
||||
db: Database, serverUrl: String, channelId: String, isCRTEnabled: Boolean,
|
||||
rootId: String?, loadedProfiles: ReadableArray?
|
||||
): ReadableMap? {
|
||||
return try {
|
||||
@@ -66,20 +66,6 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
||||
}
|
||||
}
|
||||
|
||||
fun findNeededUsernames(text: String?) {
|
||||
if (text == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val matchResults = regex.findAll(text)
|
||||
matchResults.iterator().forEach {
|
||||
val username = it.value.removePrefix("@")
|
||||
if (!usernames.contains(username) && currentUsername != username && !specialMentions.contains(username)) {
|
||||
usernames.add(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (iterator.hasNextKey()) {
|
||||
val key = iterator.nextKey()
|
||||
val post = posts.getMap(key)
|
||||
@@ -87,22 +73,17 @@ internal suspend fun PushNotificationDataRunnable.Companion.fetchPosts(
|
||||
if (userId != null && userId != currentUserId && !userIdsAlreadyLoaded.contains(userId) && !userIds.contains(userId)) {
|
||||
userIds.add(userId)
|
||||
}
|
||||
|
||||
val message = post?.getString("message")
|
||||
findNeededUsernames(message)
|
||||
val props = post?.getMap("props")
|
||||
val attachments = props?.getArray("attachments")
|
||||
if (attachments != null) {
|
||||
for (i in 0 until attachments.size()) {
|
||||
val attachment = attachments.getMap(i)
|
||||
val pretext = attachment.getString("pretext")
|
||||
val text = attachment.getString("text")
|
||||
findNeededUsernames(pretext)
|
||||
findNeededUsernames(text)
|
||||
if (message != null) {
|
||||
val matchResults = regex.findAll(message)
|
||||
matchResults.iterator().forEach {
|
||||
val username = it.value.removePrefix("@")
|
||||
if (!usernames.contains(username) && currentUsername != username && !specialMentions.contains(username)) {
|
||||
usernames.add(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isCRTEnabled) {
|
||||
// Add root post as a thread
|
||||
val threadId = post?.getString("root_id")
|
||||
|
||||
@@ -4,9 +4,9 @@ import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.PushNotificationDataRunnable
|
||||
import com.mattermost.helpers.database_extension.findMyTeam
|
||||
import com.mattermost.helpers.database_extension.findTeam
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchTeamIfNeeded(db: WMDatabase, serverUrl: String, teamId: String): Pair<ReadableMap?, ReadableMap?> {
|
||||
suspend fun PushNotificationDataRunnable.Companion.fetchTeamIfNeeded(db: Database, serverUrl: String, teamId: String): Pair<ReadableMap?, ReadableMap?> {
|
||||
return try {
|
||||
var team: ReadableMap? = null
|
||||
var myTeam: ReadableMap? = null
|
||||
|
||||
@@ -3,9 +3,9 @@ package com.mattermost.helpers.push_notification
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.mattermost.helpers.PushNotificationDataRunnable
|
||||
import com.mattermost.helpers.database_extension.*
|
||||
import com.nozbe.watermelondb.WMDatabase
|
||||
import com.nozbe.watermelondb.Database
|
||||
|
||||
internal suspend fun PushNotificationDataRunnable.Companion.fetchThread(db: WMDatabase, serverUrl: String, threadId: String, teamId: String?): ReadableMap? {
|
||||
internal suspend fun PushNotificationDataRunnable.Companion.fetchThread(db: Database, serverUrl: String, threadId: String, teamId: String?): ReadableMap? {
|
||||
val currentUserId = queryCurrentUserId(db) ?: return null
|
||||
val threadTeamId = (if (teamId.isNullOrEmpty()) queryCurrentTeamId(db) else teamId) ?: return null
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package com.mattermost.rnbeta
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.window.core.layout.WindowHeightSizeClass
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import androidx.window.layout.FoldingFeature
|
||||
import androidx.window.layout.WindowInfoTracker
|
||||
import androidx.window.layout.WindowLayoutInfo
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import androidx.window.rxjava3.layout.windowLayoutInfoObservable
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
@@ -16,23 +12,6 @@ import io.reactivex.rxjava3.disposables.Disposable
|
||||
class FoldableObserver(private val activity: Activity) {
|
||||
private var disposable: Disposable? = null
|
||||
private lateinit var observable: Observable<WindowLayoutInfo>
|
||||
public var isDeviceFolded: Boolean = false
|
||||
|
||||
companion object {
|
||||
private var instance: FoldableObserver? = null
|
||||
|
||||
fun getInstance(activity: Activity): FoldableObserver {
|
||||
if (instance == null) {
|
||||
instance = FoldableObserver(activity)
|
||||
}
|
||||
|
||||
return instance!!
|
||||
}
|
||||
|
||||
fun getInstance(): FoldableObserver? {
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
fun onCreate() {
|
||||
observable = WindowInfoTracker.getOrCreate(activity)
|
||||
@@ -46,8 +25,20 @@ class FoldableObserver(private val activity: Activity) {
|
||||
disposable = observable.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { layoutInfo ->
|
||||
val splitViewModule = SplitViewModule.getInstance()
|
||||
setIsDeviceFolded(layoutInfo)
|
||||
splitViewModule?.setDeviceFolded()
|
||||
val foldingFeature = layoutInfo.displayFeatures
|
||||
.filterIsInstance<FoldingFeature>()
|
||||
.firstOrNull()
|
||||
when {
|
||||
foldingFeature?.state === FoldingFeature.State.FLAT ->
|
||||
splitViewModule?.setDeviceFolded(false)
|
||||
isTableTopPosture(foldingFeature) ->
|
||||
splitViewModule?.setDeviceFolded(false)
|
||||
isBookPosture(foldingFeature) ->
|
||||
splitViewModule?.setDeviceFolded(false)
|
||||
else -> {
|
||||
splitViewModule?.setDeviceFolded(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,31 +46,6 @@ class FoldableObserver(private val activity: Activity) {
|
||||
disposable?.dispose()
|
||||
}
|
||||
|
||||
private fun setIsDeviceFolded(layoutInfo: WindowLayoutInfo) {
|
||||
val foldingFeature = layoutInfo.displayFeatures
|
||||
.filterIsInstance<FoldingFeature>()
|
||||
.firstOrNull()
|
||||
isDeviceFolded = when {
|
||||
foldingFeature === null -> isCompactView()
|
||||
foldingFeature.state === FoldingFeature.State.FLAT -> false
|
||||
isTableTopPosture(foldingFeature) -> false
|
||||
isBookPosture(foldingFeature) -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
fun isCompactView(): Boolean {
|
||||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
|
||||
val width = metrics.bounds.width()
|
||||
val height = metrics.bounds.height()
|
||||
val density = activity.resources.displayMetrics.density
|
||||
val windowSizeClass = WindowSizeClass.compute(width/density, height/density)
|
||||
val widthWindowSizeClass = windowSizeClass.windowWidthSizeClass
|
||||
val heightWindowSizeClass = windowSizeClass.windowHeightSizeClass
|
||||
|
||||
return widthWindowSizeClass === WindowWidthSizeClass.COMPACT || heightWindowSizeClass === WindowHeightSizeClass.COMPACT
|
||||
}
|
||||
|
||||
private fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
|
||||
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
|
||||
foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.Objects;
|
||||
|
||||
public class MainActivity extends NavigationActivity {
|
||||
private boolean HWKeyboardConnected = false;
|
||||
private final FoldableObserver foldableObserver = FoldableObserver.Companion.getInstance(this);
|
||||
private final FoldableObserver foldableObserver = new FoldableObserver(this);
|
||||
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
@@ -36,7 +36,10 @@ public class MainActivity extends NavigationActivity {
|
||||
this,
|
||||
Objects.requireNonNull(getMainComponentName()),
|
||||
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled());
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
|
||||
// If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
|
||||
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEm
|
||||
import com.learnium.RNDeviceInfo.resolver.DeviceTypeResolver
|
||||
|
||||
class SplitViewModule(private var reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
||||
private var isDeviceFolded: Boolean = false
|
||||
private var listenerCount = 0
|
||||
|
||||
companion object {
|
||||
@@ -38,11 +39,7 @@ class SplitViewModule(private var reactContext: ReactApplicationContext) : React
|
||||
if (currentActivity != null) {
|
||||
val deviceResolver = DeviceTypeResolver(this.reactContext)
|
||||
val map = Arguments.createMap()
|
||||
var isSplitView = folded;
|
||||
if (currentActivity?.isInMultiWindowMode == true) {
|
||||
isSplitView = FoldableObserver.getInstance()?.isCompactView() == true
|
||||
}
|
||||
map.putBoolean("isSplitView", isSplitView)
|
||||
map.putBoolean("isSplitView", currentActivity!!.isInMultiWindowMode || folded)
|
||||
map.putBoolean("isTablet", deviceResolver.isTablet)
|
||||
return map
|
||||
}
|
||||
@@ -50,16 +47,17 @@ class SplitViewModule(private var reactContext: ReactApplicationContext) : React
|
||||
return null
|
||||
}
|
||||
|
||||
fun setDeviceFolded() {
|
||||
val map = getSplitViewResults(FoldableObserver.getInstance()?.isDeviceFolded == true)
|
||||
if (listenerCount > 0) {
|
||||
fun setDeviceFolded(folded: Boolean) {
|
||||
val map = getSplitViewResults(folded)
|
||||
if (listenerCount > 0 && isDeviceFolded != folded) {
|
||||
sendEvent(map)
|
||||
}
|
||||
isDeviceFolded = folded
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
fun isRunningInSplitView(): WritableMap? {
|
||||
return getSplitViewResults(FoldableObserver.getInstance()?.isDeviceFolded == true)
|
||||
@ReactMethod
|
||||
fun isRunningInSplitView(promise: Promise) {
|
||||
promise.resolve(getSplitViewResults(isDeviceFolded))
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:insetTop="@dimen/abc_edit_text_inset_top_material"
|
||||
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
|
||||
<selector>
|
||||
<!--
|
||||
<!--
|
||||
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
||||
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
||||
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
||||
|
||||
@@ -7,10 +7,10 @@ buildscript {
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
supportLibVersion = "33.0.0"
|
||||
kotlinVersion = "1.8.21"
|
||||
kotlin_version = kotlinVersion
|
||||
kotlinVersion = "1.7.21"
|
||||
kotlin_version = "1.7.21"
|
||||
firebaseVersion = "23.1.1"
|
||||
RNNKotlinVersion = kotlinVersion
|
||||
firebaseVersion = "23.3.1"
|
||||
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
@@ -21,9 +21,9 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.android.tools.build:gradle:7.3.1")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath('com.google.gms:google-services:4.4.0')
|
||||
classpath('com.google.gms:google-services:4.3.15')
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
@@ -29,7 +29,7 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.182.0
|
||||
FLIPPER_VERSION=0.177.0
|
||||
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,6 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
|
||||
20
android/gradlew
vendored
20
android/gradlew
vendored
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,11 +80,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
@@ -143,16 +143,12 @@ fi
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -209,12 +205,6 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
@@ -241,4 +231,4 @@ eval "set -- $(
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
exec "$JAVACMD" "$@"
|
||||
17
android/gradlew.bat
vendored
17
android/gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,8 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -76,17 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:omega
|
||||
@@ -5,6 +5,8 @@ project(':reactnativenotifications').projectDir = new File(rootProject.projectDi
|
||||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
include ':react-native-video'
|
||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
|
||||
include ':watermelondb'
|
||||
project(':watermelondb').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')
|
||||
include ':watermelondb-jsi'
|
||||
project(':watermelondb-jsi').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android-jsi')
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
includeBuild('../node_modules/react-native-gradle-plugin')
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import {Tutorial} from '@constants';
|
||||
import {GLOBAL_IDENTIFIERS} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getActiveServerUrl} from '@init/credentials';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
export const storeGlobal = async (id: string, value: unknown, prepareRecordsOnly = false) => {
|
||||
@@ -51,37 +50,3 @@ export const storeLastAskForReview = async (prepareRecordsOnly = false) => {
|
||||
export const storeFirstLaunch = async (prepareRecordsOnly = false) => {
|
||||
return storeGlobal(GLOBAL_IDENTIFIERS.FIRST_LAUNCH, Date.now(), prepareRecordsOnly);
|
||||
};
|
||||
|
||||
export const storeLastViewedChannelIdAndServer = async (channelId: string) => {
|
||||
const currentServerUrl = await getActiveServerUrl();
|
||||
|
||||
return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_CHANNEL, {
|
||||
server_url: currentServerUrl,
|
||||
channel_id: channelId,
|
||||
}, false);
|
||||
};
|
||||
|
||||
export const storeLastViewedThreadIdAndServer = async (threadId: string) => {
|
||||
const currentServerUrl = await getActiveServerUrl();
|
||||
|
||||
return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_THREAD, {
|
||||
server_url: currentServerUrl,
|
||||
thread_id: threadId,
|
||||
}, false);
|
||||
};
|
||||
|
||||
export const removeLastViewedChannelIdAndServer = async () => {
|
||||
return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_CHANNEL, null, false);
|
||||
};
|
||||
|
||||
export const removeLastViewedThreadIdAndServer = async () => {
|
||||
return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_THREAD, null, false);
|
||||
};
|
||||
|
||||
export const storePushDisabledInServerAcknowledged = async (serverUrl: string) => {
|
||||
return storeGlobal(`${GLOBAL_IDENTIFIERS.PUSH_DISABLED_ACK}${serverUrl}`, 'true', false);
|
||||
};
|
||||
|
||||
export const removePushDisabledInServerAcknowledged = async (serverUrl: string) => {
|
||||
return storeGlobal(`${GLOBAL_IDENTIFIERS.PUSH_DISABLED_ACK}${serverUrl}`, null, false);
|
||||
};
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
||||
import {handleConvertedGMCategories} from './category';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
describe('handleConvertedGMCategories', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const channelId = 'channel_id_1';
|
||||
const teamId1 = 'team_id_1';
|
||||
const teamId2 = 'team_id_2';
|
||||
const team: Team = {
|
||||
id: teamId1,
|
||||
} as Team;
|
||||
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeEach(async () => {
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
});
|
||||
|
||||
it('base case', async () => {
|
||||
await operator.handleTeam({teams: [team], prepareRecordsOnly: false});
|
||||
|
||||
const defaultCategory: Category = {
|
||||
id: 'default_category_id',
|
||||
team_id: teamId1,
|
||||
type: 'channels',
|
||||
} as Category;
|
||||
|
||||
const customCategory: Category = {
|
||||
id: 'custom_category_id',
|
||||
team_id: teamId2,
|
||||
type: 'custom',
|
||||
} as Category;
|
||||
|
||||
const dmCategory: Category = {
|
||||
id: 'dm_category_id',
|
||||
team_id: teamId1,
|
||||
type: 'direct_messages',
|
||||
} as Category;
|
||||
|
||||
await operator.handleCategories({categories: [defaultCategory, customCategory, dmCategory], prepareRecordsOnly: false});
|
||||
|
||||
const dmCategoryChannel: CategoryChannel = {
|
||||
id: 'dm_category_channel_id',
|
||||
category_id: 'dm_category_id',
|
||||
channel_id: channelId,
|
||||
sort_order: 1,
|
||||
};
|
||||
|
||||
const customCategoryChannel: CategoryChannel = {
|
||||
id: 'custom_category_channel_id',
|
||||
category_id: 'dm_category_id',
|
||||
channel_id: channelId,
|
||||
sort_order: 1,
|
||||
};
|
||||
await operator.handleCategoryChannels({categoryChannels: [dmCategoryChannel, customCategoryChannel], prepareRecordsOnly: false});
|
||||
|
||||
const {models, error} = await handleConvertedGMCategories(serverUrl, channelId, teamId1, true);
|
||||
expect(error).toBeUndefined();
|
||||
expect(models).toBeDefined();
|
||||
expect(models!.length).toBe(3); // two for removing channel for a custom and a DM category, and one for adding it to default channels category
|
||||
});
|
||||
});
|
||||
@@ -3,13 +3,12 @@
|
||||
|
||||
import {CHANNELS_CATEGORY, DMS_CATEGORY} from '@constants/categories';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {prepareCategoryChannels, queryCategoriesByTeamIds, getCategoryById, prepareCategoriesAndCategoriesChannels, queryCategoryChannelsByChannelId} from '@queries/servers/categories';
|
||||
import {prepareCategoryChannels, queryCategoriesByTeamIds, getCategoryById, prepareCategoriesAndCategoriesChannels} from '@queries/servers/categories';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import {queryMyTeams} from '@queries/servers/team';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {logDebug, logError} from '@utils/log';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
import type {Database, Model} from '@nozbe/watermelondb';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
export const deleteCategory = async (serverUrl: string, categoryId: string) => {
|
||||
@@ -92,8 +91,11 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
|
||||
categoriesWithChannels.push(cwc);
|
||||
}
|
||||
} else {
|
||||
const cwc = await prepareAddNonGMDMChannelToDefaultCategory(database, teamId, channel.id);
|
||||
if (cwc) {
|
||||
const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch();
|
||||
const channelCategory = categories.find((c) => c.type === CHANNELS_CATEGORY);
|
||||
if (channelCategory) {
|
||||
const cwc = await channelCategory.toCategoryWithChannels();
|
||||
cwc.channel_ids.unshift(channel.id);
|
||||
categoriesWithChannels.push(cwc);
|
||||
}
|
||||
}
|
||||
@@ -106,62 +108,6 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
|
||||
|
||||
return {models};
|
||||
} catch (error) {
|
||||
logError('Failed to add channel to default category', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareAddNonGMDMChannelToDefaultCategory(database: Database, teamId: string, channelId: string): Promise<CategoryWithChannels | undefined> {
|
||||
const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch();
|
||||
const channelCategory = categories.find((category) => category.type === CHANNELS_CATEGORY);
|
||||
if (channelCategory) {
|
||||
const cwc = await channelCategory.toCategoryWithChannels();
|
||||
if (cwc.channel_ids.indexOf(channelId) < 0) {
|
||||
cwc.channel_ids.unshift(channelId);
|
||||
return cwc;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function handleConvertedGMCategories(serverUrl: string, channelId: string, targetTeamID: string, prepareRecordsOnly = false) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const categoryChannels = await queryCategoryChannelsByChannelId(database, channelId).fetch();
|
||||
|
||||
const categories = await queryCategoriesByTeamIds(database, [targetTeamID]).fetch();
|
||||
const channelCategory = categories.find((category) => category.type === CHANNELS_CATEGORY);
|
||||
|
||||
if (!channelCategory) {
|
||||
const error = 'Failed to find default category when handling category of converted GM';
|
||||
logError(error);
|
||||
return {error};
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
categoryChannels.forEach((categoryChannel) => {
|
||||
if (categoryChannel.categoryId !== channelCategory.id) {
|
||||
models.push(categoryChannel.prepareDestroyPermanently());
|
||||
}
|
||||
});
|
||||
|
||||
const cwc = await prepareAddNonGMDMChannelToDefaultCategory(database, targetTeamID, channelId);
|
||||
if (cwc) {
|
||||
const model = await prepareCategoryChannels(operator, [cwc]);
|
||||
models.push(...model);
|
||||
} else {
|
||||
logDebug('handleConvertedGMCategories: could not find channel category of target team');
|
||||
}
|
||||
|
||||
if (models.length > 0 && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models, 'putGMInCorrectCategory');
|
||||
}
|
||||
|
||||
return {models};
|
||||
} catch (error) {
|
||||
logError('Failed to handle category update for GM converted to channel', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getMyChannel, getChannelById, queryUsersOnChannel, queryUserChannelsByTypes,
|
||||
} from '@queries/servers/channel';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {prepareCommonSystemValues, type PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system';
|
||||
import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system';
|
||||
import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team';
|
||||
import {getCurrentUser, queryUsersById} from '@queries/servers/user';
|
||||
import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation';
|
||||
@@ -32,7 +32,7 @@ export async function switchToChannel(serverUrl: string, channelId: string, team
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
let models: Model[] = [];
|
||||
const dt = Date.now();
|
||||
const isTabletDevice = isTablet();
|
||||
const isTabletDevice = await isTablet();
|
||||
const system = await getCommonSystemValues(database);
|
||||
const member = await getMyChannel(database, channelId);
|
||||
|
||||
|
||||
@@ -155,37 +155,3 @@ export const removeDraft = async (serverUrl: string, channelId: string, rootId =
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export async function updateDraftPriority(serverUrl: string, channelId: string, rootId: string, postPriority: PostPriority, prepareRecordsOnly = false) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const draft = await getDraft(database, channelId, rootId);
|
||||
if (!draft) {
|
||||
const newDraft: Draft = {
|
||||
channel_id: channelId,
|
||||
root_id: rootId,
|
||||
metadata: {
|
||||
priority: postPriority,
|
||||
},
|
||||
};
|
||||
|
||||
return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly});
|
||||
}
|
||||
|
||||
draft.prepareUpdate((d) => {
|
||||
d.metadata = {
|
||||
...d.metadata,
|
||||
priority: postPriority,
|
||||
};
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([draft], 'updateDraftPriority');
|
||||
}
|
||||
|
||||
return {draft};
|
||||
} catch (error) {
|
||||
logError('Failed updateDraftPriority', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {fetchPostAuthors} from '@actions/remote/post';
|
||||
import {ActionType, Post} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {countUsersFromMentions, getPostById, prepareDeletePost, queryPostsById} from '@queries/servers/post';
|
||||
import {getPostById, prepareDeletePost, queryPostsById} from '@queries/servers/post';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
|
||||
import {generateId} from '@utils/general';
|
||||
@@ -15,7 +15,6 @@ import {getPostIdsForCombinedUserActivityPost} from '@utils/post_list';
|
||||
|
||||
import {updateLastPostAt, updateMyChannelLastFetchedAt} from './channel';
|
||||
|
||||
import type {Q} from '@nozbe/watermelondb';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
@@ -158,7 +157,6 @@ export async function markPostAsDeleted(serverUrl: string, post: Post, prepareRe
|
||||
const model = dbPost.prepareUpdate((p) => {
|
||||
p.deleteAt = Date.now();
|
||||
p.message = '';
|
||||
p.messageSource = '';
|
||||
p.metadata = null;
|
||||
p.props = undefined;
|
||||
});
|
||||
@@ -241,81 +239,15 @@ export async function storePostsForChannel(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPosts(serverUrl: string, ids: string[], sort?: Q.SortOrder) {
|
||||
export async function getPosts(serverUrl: string, ids: string[]) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
return queryPostsById(database, ids, sort).fetch();
|
||||
return queryPostsById(database, ids).fetch();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function addPostAcknowledgement(serverUrl: string, postId: string, userId: string, acknowledgedAt: number, prepareRecordsOnly = false) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const post = await getPostById(database, postId);
|
||||
if (!post) {
|
||||
throw new Error('Post not found');
|
||||
}
|
||||
|
||||
// Check if the post has already been acknowledged by the user
|
||||
const isAckd = post.metadata?.acknowledgements?.find((a) => a.user_id === userId);
|
||||
if (isAckd) {
|
||||
return {error: false};
|
||||
}
|
||||
|
||||
const acknowledgements = [...(post.metadata?.acknowledgements || []), {
|
||||
user_id: userId,
|
||||
acknowledged_at: acknowledgedAt,
|
||||
post_id: postId,
|
||||
}];
|
||||
|
||||
const model = post.prepareUpdate((p) => {
|
||||
p.metadata = {
|
||||
...p.metadata,
|
||||
acknowledgements,
|
||||
};
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([model], 'addPostAcknowledgement');
|
||||
}
|
||||
|
||||
return {model};
|
||||
} catch (error) {
|
||||
logError('Failed addPostAcknowledgement', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function removePostAcknowledgement(serverUrl: string, postId: string, userId: string, prepareRecordsOnly = false) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const post = await getPostById(database, postId);
|
||||
if (!post) {
|
||||
throw new Error('Post not found');
|
||||
}
|
||||
|
||||
const model = post.prepareUpdate((record) => {
|
||||
record.metadata = {
|
||||
...post.metadata,
|
||||
acknowledgements: post.metadata?.acknowledgements?.filter(
|
||||
(a) => a.user_id !== userId,
|
||||
) || [],
|
||||
};
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([model], 'removePostAcknowledgement');
|
||||
}
|
||||
|
||||
return {model};
|
||||
} catch (error) {
|
||||
logError('Failed removePostAcknowledgement', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function deletePosts(serverUrl: string, postIds: string[]) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
@@ -343,12 +275,3 @@ export async function deletePosts(serverUrl: string, postIds: string[]) {
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export function getUsersCountFromMentions(serverUrl: string, mentions: string[]): Promise<number> {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
return countUsersFromMentions(database, mentions);
|
||||
} catch (error) {
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import DatabaseManager from '@database/manager';
|
||||
import {getServerCredentials} from '@init/credentials';
|
||||
import {queryAllChannelsForTeam} from '@queries/servers/channel';
|
||||
import {getConfig, getLicense, getGlobalDataRetentionPolicy, getGranularDataRetentionPolicies, getLastGlobalDataRetentionRun, getIsDataRetentionEnabled} from '@queries/servers/system';
|
||||
import PostModel from '@typings/database/models/servers/post';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
import {deletePosts} from './post';
|
||||
|
||||
import type {DataRetentionPoliciesRequest} from '@actions/remote/systems';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const {SERVER: {POST}} = MM_TABLES;
|
||||
|
||||
@@ -127,6 +127,11 @@ export async function dataRetentionCleanup(serverUrl: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const isDataRetentionEnabled = await getIsDataRetentionEnabled(database);
|
||||
if (!isDataRetentionEnabled) {
|
||||
return {error: undefined};
|
||||
}
|
||||
|
||||
const lastRunAt = await getLastGlobalDataRetentionRun(database);
|
||||
const lastCleanedToday = new Date(lastRunAt).toDateString() === new Date().toDateString();
|
||||
|
||||
@@ -135,25 +140,6 @@ export async function dataRetentionCleanup(serverUrl: string) {
|
||||
return {error: undefined};
|
||||
}
|
||||
|
||||
const isDataRetentionEnabled = await getIsDataRetentionEnabled(database);
|
||||
const result = await (isDataRetentionEnabled ? dataRetentionPolicyCleanup(serverUrl) : dataRetentionWithoutPolicyCleanup(serverUrl));
|
||||
|
||||
if (!result.error) {
|
||||
await updateLastDataRetentionRun(serverUrl);
|
||||
}
|
||||
|
||||
await database.unsafeVacuum();
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logError('An error occurred while performing data retention cleanup', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
async function dataRetentionPolicyCleanup(serverUrl: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const globalPolicy = await getGlobalDataRetentionPolicy(database);
|
||||
const granularPoliciesData = await getGranularDataRetentionPolicies(database);
|
||||
|
||||
@@ -210,48 +196,30 @@ async function dataRetentionPolicyCleanup(serverUrl: string) {
|
||||
Q.unsafeSqlQuery(`SELECT * FROM ${POST} where ${conditions.join(' OR ')}`),
|
||||
).fetchIds();
|
||||
|
||||
return dataRetentionCleanPosts(serverUrl, postIds);
|
||||
} catch (error) {
|
||||
logError('An error occurred while performing data retention policy cleanup', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
async function dataRetentionWithoutPolicyCleanup(serverUrl: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const cutoff = getDataRetentionPolicyCutoff(14); // 14 days
|
||||
|
||||
const postIds = await database.get<PostModel>(POST).query(
|
||||
Q.where('create_at', Q.lt(cutoff)),
|
||||
).fetchIds();
|
||||
|
||||
return dataRetentionCleanPosts(serverUrl, postIds);
|
||||
} catch (error) {
|
||||
logError('An error occurred while performing data retention without policy cleanup', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
async function dataRetentionCleanPosts(serverUrl: string, postIds: string[]) {
|
||||
if (postIds.length) {
|
||||
const batchSize = 1000;
|
||||
const deletePromises = [];
|
||||
for (let i = 0; i < postIds.length; i += batchSize) {
|
||||
const batch = postIds.slice(i, batchSize);
|
||||
deletePromises.push(
|
||||
deletePosts(serverUrl, batch),
|
||||
);
|
||||
}
|
||||
const deleteResult = await Promise.all(deletePromises);
|
||||
for (const {error} of deleteResult) {
|
||||
if (error) {
|
||||
return {error};
|
||||
if (postIds.length) {
|
||||
const batchSize = 1000;
|
||||
const deletePromises = [];
|
||||
for (let i = 0; i < postIds.length; i += batchSize) {
|
||||
const batch = postIds.slice(i, batchSize);
|
||||
deletePromises.push(
|
||||
deletePosts(serverUrl, batch),
|
||||
);
|
||||
}
|
||||
const deleteResult = await Promise.all(deletePromises);
|
||||
for (const {error} of deleteResult) {
|
||||
if (error) {
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {error: undefined};
|
||||
await updateLastDataRetentionRun(serverUrl);
|
||||
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
logError('An error occurred while performing data retention cleanup', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
// Returns cutoff time based on the policy's post_duration
|
||||
|
||||
@@ -8,11 +8,11 @@ import DatabaseManager from '@database/manager';
|
||||
import {getTranslations, t} from '@i18n';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getPostById} from '@queries/servers/post';
|
||||
import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, type PrepareCommonSystemValuesArgs, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, PrepareCommonSystemValuesArgs, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {addChannelToTeamHistory, addTeamToTeamHistory} from '@queries/servers/team';
|
||||
import {getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread';
|
||||
import {getIsCRTEnabled, getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlays, goToScreen} from '@screens/navigation';
|
||||
import {dismissAllModalsAndPopToRoot, goToScreen} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
@@ -43,7 +43,7 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string,
|
||||
await operator.batchRecords(models, 'switchToGlobalThreads');
|
||||
}
|
||||
|
||||
const isTabletDevice = isTablet();
|
||||
const isTabletDevice = await isTablet();
|
||||
if (isTabletDevice) {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATION_HOME, Screens.GLOBAL_THREADS);
|
||||
} else {
|
||||
@@ -75,27 +75,20 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
||||
}
|
||||
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
const isTabletDevice = isTablet();
|
||||
const isTabletDevice = await isTablet();
|
||||
const teamId = channel.teamId || currentTeamId;
|
||||
const currentThreadId = EphemeralStore.getCurrentThreadId();
|
||||
|
||||
EphemeralStore.setCurrentThreadId(rootId);
|
||||
if (isFromNotification) {
|
||||
if (currentThreadId && currentThreadId === rootId && NavigationStore.getScreensInStack().includes(Screens.THREAD)) {
|
||||
await dismissAllModals();
|
||||
await dismissAllOverlays();
|
||||
return {};
|
||||
let switchingTeams = false;
|
||||
if (currentTeamId === teamId) {
|
||||
const models = await prepareCommonSystemValues(operator, {
|
||||
currentChannelId: channel.id,
|
||||
});
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models, 'switchToThread');
|
||||
}
|
||||
|
||||
await dismissAllModalsAndPopToRoot();
|
||||
await NavigationStore.waitUntilScreenIsTop(Screens.HOME);
|
||||
if (currentTeamId !== teamId && isTabletDevice) {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATION_HOME, Screens.GLOBAL_THREADS);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTeamId !== teamId) {
|
||||
} else {
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
switchingTeams = true;
|
||||
modelPromises.push(addTeamToTeamHistory(operator, teamId, true));
|
||||
const commonValues: PrepareCommonSystemValuesArgs = {
|
||||
currentChannelId: channel.id,
|
||||
@@ -108,6 +101,25 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
||||
}
|
||||
}
|
||||
|
||||
// Modal right buttons
|
||||
const rightButtons = [];
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
if (isCRTEnabled) {
|
||||
// CRT: Add follow/following button
|
||||
rightButtons.push({
|
||||
id: 'thread-follow-button',
|
||||
component: {
|
||||
id: post.id,
|
||||
name: Screens.THREAD_FOLLOW_BUTTON,
|
||||
passProps: {
|
||||
teamId: channel.teamId,
|
||||
threadId: post.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Get translation by user locale
|
||||
const translations = getTranslations(user.locale);
|
||||
|
||||
@@ -123,6 +135,15 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
||||
subtitle = subtitle.replace('{channelName}', channel.displayName);
|
||||
}
|
||||
|
||||
EphemeralStore.setCurrentThreadId(rootId);
|
||||
|
||||
if (isFromNotification) {
|
||||
await dismissAllModalsAndPopToRoot();
|
||||
await NavigationStore.waitUntilScreenIsTop(Screens.HOME);
|
||||
if (switchingTeams && isTabletDevice) {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATION_HOME, Screens.GLOBAL_THREADS);
|
||||
}
|
||||
}
|
||||
goToScreen(Screens.THREAD, '', {rootId}, {
|
||||
topBar: {
|
||||
title: {
|
||||
@@ -135,15 +156,14 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
||||
noBorder: true,
|
||||
scrollEdgeAppearance: {
|
||||
noBorder: true,
|
||||
active: true,
|
||||
},
|
||||
rightButtons,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
logError('Failed switchToThread', error);
|
||||
EphemeralStore.setCurrentThreadId('');
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import General from '@constants/general';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getRecentCustomStatuses} from '@queries/servers/system';
|
||||
import {getCurrentUser, getUserById} from '@queries/servers/user';
|
||||
@@ -12,7 +13,7 @@ import {addRecentReaction} from './reactions';
|
||||
import type Model from '@nozbe/watermelondb/Model';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
export async function setCurrentUserStatus(serverUrl: string, status: string) {
|
||||
export async function setCurrentUserStatusOffline(serverUrl: string) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const user = await getCurrentUser(database);
|
||||
@@ -20,14 +21,11 @@ export async function setCurrentUserStatus(serverUrl: string, status: string) {
|
||||
throw new Error(`No current user for ${serverUrl}`);
|
||||
}
|
||||
|
||||
if (user.status !== status) {
|
||||
user.prepareStatus(status);
|
||||
await operator.batchRecords([user], 'setCurrentUserStatus');
|
||||
}
|
||||
|
||||
user.prepareStatus(General.OFFLINE);
|
||||
await operator.batchRecords([user], 'setCurrentUserStatusOffline');
|
||||
return null;
|
||||
} catch (error) {
|
||||
logError('Failed setCurrentUserStatus', error);
|
||||
logError('Failed setCurrentUserStatusOffline', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ import {sendEphemeralPost} from '@actions/local/post';
|
||||
import {AppCallResponseTypes} from '@constants/apps';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {cleanForm, createCallRequest, makeCallErrorResponse} from '@utils/apps';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
@@ -59,9 +58,15 @@ export async function handleBindingClick<Res=unknown>(serverUrl: string, binding
|
||||
return doAppSubmit<Res>(serverUrl, callRequest, intl);
|
||||
}
|
||||
|
||||
export async function doAppSubmit<Res=unknown>(serverUrl: string, inCall: AppCallRequest, intl: IntlShape): Promise<{data: AppCallResponse<Res>} | {error: AppCallResponse<Res>}> {
|
||||
export async function doAppSubmit<Res=unknown>(serverUrl: string, inCall: AppCallRequest, intl: IntlShape) {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: makeCallErrorResponse((error as ClientError).message)};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const call: AppCallRequest = {
|
||||
...inCall,
|
||||
context: {
|
||||
@@ -111,18 +116,23 @@ export async function doAppSubmit<Res=unknown>(serverUrl: string, inCall: AppCal
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errMsg = getFullErrorMessage(error) || intl.formatMessage({
|
||||
const errMsg = (error as ClientError).message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
logDebug('error on doAppSubmit', getFullErrorMessage(error));
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
|
||||
export async function doAppFetchForm<Res=unknown>(serverUrl: string, call: AppCallRequest, intl: IntlShape) {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: makeCallErrorResponse((error as ClientError).message)};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const res = await client.executeAppCall<Res>(call, false);
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
@@ -147,12 +157,11 @@ export async function doAppFetchForm<Res=unknown>(serverUrl: string, call: AppCa
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errMsg = getFullErrorMessage(error) || intl.formatMessage({
|
||||
} catch (error: any) {
|
||||
const errMsg = error.message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
logDebug('error on doAppFetchForm', getFullErrorMessage(error));
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
@@ -161,6 +170,11 @@ export async function doAppLookup<Res=unknown>(serverUrl: string, call: AppCallR
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: makeCallErrorResponse((error as ClientError).message)};
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await client.executeAppCall<Res>(call, false);
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
@@ -179,11 +193,10 @@ export async function doAppLookup<Res=unknown>(serverUrl: string, call: AppCallR
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errMsg = getFullErrorMessage(error) || intl.formatMessage({
|
||||
const errMsg = error.message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
logDebug('error on doAppLookup', getFullErrorMessage(error));
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,26 @@ import NetworkManager from '@managers/network_manager';
|
||||
import {getChannelCategory, queryCategoriesByTeamIds} from '@queries/servers/categories';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getCurrentTeamId} from '@queries/servers/system';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {showFavoriteChannelSnackbar} from '@utils/snack_bar';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
|
||||
export type CategoriesRequest = {
|
||||
categories?: CategoryWithChannels[];
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const fetchCategories = async (serverUrl: string, teamId: string, prune = false, fetchOnly = false): Promise<CategoriesRequest> => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {categories} = await client.getCategories('me', teamId);
|
||||
|
||||
if (!fetchOnly) {
|
||||
@@ -31,17 +37,26 @@ export const fetchCategories = async (serverUrl: string, teamId: string, prune =
|
||||
|
||||
return {categories};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchCategories', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleFavoriteChannel = async (serverUrl: string, channelId: string, showSnackBar = false) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const {database} = operator;
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (!channel) {
|
||||
return {error: 'channel not found'};
|
||||
@@ -94,8 +109,7 @@ export const toggleFavoriteChannel = async (serverUrl: string, channelId: string
|
||||
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logDebug('error on toggleFavoriteChannel', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,29 +14,26 @@ import {getChannelById} from '@queries/servers/channel';
|
||||
import {getConfig, getCurrentTeamId} from '@queries/servers/system';
|
||||
import {showAppForm} from '@screens/navigation';
|
||||
import {handleDeepLink, matchDeepLink} from '@utils/deep_link';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
export const executeCommand = async (serverUrl: string, intl: IntlShape, message: string, channelId: string, rootId?: string): Promise<{data?: CommandResponse; error?: unknown}> => {
|
||||
export const executeCommand = async (serverUrl: string, intl: IntlShape, message: string, channelId: string, rootId?: string): Promise<{data?: CommandResponse; error?: string | {message: string}}> => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
return {error: error as ClientErrorProps};
|
||||
}
|
||||
|
||||
const channel = await getChannelById(database, channelId);
|
||||
const teamId = channel?.teamId || (await getCurrentTeamId(database));
|
||||
const channel = await getChannelById(operator.database, channelId);
|
||||
const teamId = channel?.teamId || (await getCurrentTeamId(operator.database));
|
||||
|
||||
const args: CommandArgs = {
|
||||
channel_id: channelId,
|
||||
@@ -71,8 +68,7 @@ export const executeCommand = async (serverUrl: string, intl: IntlShape, message
|
||||
try {
|
||||
data = await client.executeCommand(msg, args);
|
||||
} catch (error) {
|
||||
logDebug('error on executeCommand', getFullErrorMessage(error));
|
||||
return {error};
|
||||
return {error: error as ClientErrorProps};
|
||||
}
|
||||
|
||||
if (data?.trigger_id) { //eslint-disable-line camelcase
|
||||
@@ -93,14 +89,14 @@ const executeAppCommand = async (serverUrl: string, intl: IntlShape, parser: App
|
||||
}
|
||||
|
||||
const res = await doAppSubmit(serverUrl, creq, intl);
|
||||
if ('error' in res) {
|
||||
const errorResponse = res.error;
|
||||
if (res.error) {
|
||||
const errorResponse = res.error as AppCallResponse;
|
||||
return createErrorMessage(errorResponse.text || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
}));
|
||||
}
|
||||
const callResp = res.data;
|
||||
const callResp = res.data as AppCallResponse;
|
||||
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
@@ -143,7 +139,7 @@ export const handleGotoLocation = async (serverUrl: string, intl: IntlShape, loc
|
||||
const match = matchDeepLink(location, serverUrl, config?.SiteURL);
|
||||
|
||||
if (match) {
|
||||
handleDeepLink(match.url, intl, location);
|
||||
handleDeepLink(match, intl, location);
|
||||
} else {
|
||||
const {formatMessage} = intl;
|
||||
const onError = () => Alert.alert(
|
||||
@@ -162,22 +158,31 @@ export const handleGotoLocation = async (serverUrl: string, intl: IntlShape, loc
|
||||
return {data: true};
|
||||
};
|
||||
|
||||
export const fetchCommands = async (serverUrl: string, teamId: string): Promise<{commands: Command[]} | {error: unknown}> => {
|
||||
export const fetchCommands = async (serverUrl: string, teamId: string) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: error as ClientErrorProps};
|
||||
}
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
return {commands: await client.getCommandsList(teamId)};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchCommands', getFullErrorMessage(error));
|
||||
return {error};
|
||||
return {error: error as ClientErrorProps};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchSuggestions = async (serverUrl: string, term: string, teamId: string, channelId: string, rootId?: string): Promise<{suggestions: AutocompleteSuggestion[]} | {error: unknown}> => {
|
||||
export const fetchSuggestions = async (serverUrl: string, term: string, teamId: string, channelId: string, rootId?: string) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: error as ClientErrorProps};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
return {suggestions: await client.getCommandAutocompleteSuggestionsList(term, teamId, channelId, rootId)};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchSuggestions', getFullErrorMessage(error));
|
||||
return {error};
|
||||
return {error: error as ClientErrorProps};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,14 +7,23 @@ import DatabaseManager from '@database/manager';
|
||||
import {debounce} from '@helpers/api/general';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {queryCustomEmojisByName} from '@queries/servers/custom_emoji';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
|
||||
export const fetchCustomEmojis = async (serverUrl: string, page = 0, perPage = General.PAGE_SIZE_DEFAULT, sort = Emoji.SORT_BY_NAME) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await client.getCustomEmojis(page, perPage, sort);
|
||||
await operator.handleCustomEmojis({
|
||||
emojis: data,
|
||||
@@ -23,20 +32,29 @@ export const fetchCustomEmojis = async (serverUrl: string, page = 0, perPage = G
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchCustomEmojis', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const searchCustomEmojis = async (serverUrl: string, term: string) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const data = await client.searchCustomEmoji(term);
|
||||
if (data.length) {
|
||||
const names = data.map((c) => c.name);
|
||||
const exist = await queryCustomEmojisByName(database, names).fetch();
|
||||
const exist = await queryCustomEmojisByName(operator.database, names).fetch();
|
||||
const existingNames = new Set(exist.map((e) => e.name));
|
||||
const emojis = data.filter((d) => !existingNames.has(d.name));
|
||||
await operator.handleCustomEmojis({
|
||||
@@ -46,22 +64,31 @@ export const searchCustomEmojis = async (serverUrl: string, term: string) => {
|
||||
}
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on searchCustomEmojis', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
const names = new Set<string>();
|
||||
const debouncedFetchEmojiByNames = debounce(async (serverUrl: string) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const promises: Array<Promise<CustomEmoji>> = [];
|
||||
for (const name of names) {
|
||||
promises.push(client.getCustomEmojiByName(name));
|
||||
}
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const promises: Array<Promise<CustomEmoji>> = [];
|
||||
for (const name of names) {
|
||||
promises.push(client.getCustomEmojiByName(name));
|
||||
}
|
||||
|
||||
try {
|
||||
const emojisResult = await Promise.allSettled(promises);
|
||||
const emojis = emojisResult.reduce<CustomEmoji[]>((result, e) => {
|
||||
if (e.status === 'fulfilled') {
|
||||
@@ -72,9 +99,8 @@ const debouncedFetchEmojiByNames = debounce(async (serverUrl: string) => {
|
||||
if (emojis.length) {
|
||||
await operator.handleCustomEmojis({emojis, prepareRecordsOnly: false});
|
||||
}
|
||||
return {};
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
logDebug('error on debouncedFetchEmojiByNames', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
}, 200, false, () => {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, handleKickFromChannel, type MyChannelsRequest} from '@actions/remote/channel';
|
||||
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, handleKickFromChannel, MyChannelsRequest} from '@actions/remote/channel';
|
||||
import {fetchGroupsForMember} from '@actions/remote/groups';
|
||||
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
|
||||
import {type MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference';
|
||||
import {MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference';
|
||||
import {fetchRoles} from '@actions/remote/role';
|
||||
import {fetchConfigAndLicense, fetchDataRetentionPolicy} from '@actions/remote/systems';
|
||||
import {fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, handleKickFromTeam, type MyTeamsRequest, updateCanJoinTeams} from '@actions/remote/team';
|
||||
import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import {fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, handleKickFromTeam, MyTeamsRequest, updateCanJoinTeams} from '@actions/remote/team';
|
||||
import {syncTeamThreads} from '@actions/remote/thread';
|
||||
import {fetchMe, type MyUserRequest, updateAllUsersSince, autoUpdateTimezone} from '@actions/remote/user';
|
||||
import {fetchMe, MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
|
||||
import {General, Preferences, Screens} from '@constants';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_RESPONSE_UNKNOWN, PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_UNKNOWN, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
|
||||
@@ -19,18 +19,18 @@ import {selectDefaultTeam} from '@helpers/api/team';
|
||||
import {DEFAULT_LOCALE} from '@i18n';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getDeviceToken} from '@queries/app/global';
|
||||
import {getChannelById, queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel';
|
||||
import {queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel';
|
||||
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
|
||||
import {getHasCRTChanged} from '@queries/servers/preference';
|
||||
import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {getConfig, getCurrentChannelId, getCurrentTeamId, getPushVerificationStatus, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {deleteMyTeams, getAvailableTeamIds, getTeamChannelHistory, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {isDMorGM, sortChannelsByDisplayName} from '@utils/channel';
|
||||
import {getFullErrorMessage, isErrorWithStatusCode} from '@utils/errors';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {processIsCRTEnabled} from '@utils/thread';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {Database, Model} from '@nozbe/watermelondb';
|
||||
|
||||
export type AppEntryData = {
|
||||
@@ -42,12 +42,10 @@ export type AppEntryData = {
|
||||
removeTeamIds?: string[];
|
||||
removeChannelIds?: string[];
|
||||
isCRTEnabled: boolean;
|
||||
initialChannelId?: string;
|
||||
gmConverted: boolean;
|
||||
}
|
||||
|
||||
export type AppEntryError = {
|
||||
error: unknown;
|
||||
error: Error | ClientError | string;
|
||||
}
|
||||
|
||||
export type EntryResponse = {
|
||||
@@ -58,7 +56,6 @@ export type EntryResponse = {
|
||||
teamData: MyTeamsRequest;
|
||||
chData?: MyChannelsRequest;
|
||||
meData?: MyUserRequest;
|
||||
gmConverted: boolean;
|
||||
} | {
|
||||
error: unknown;
|
||||
}
|
||||
@@ -66,43 +63,19 @@ export type EntryResponse = {
|
||||
const FETCH_MISSING_DM_TIMEOUT = 2500;
|
||||
export const FETCH_UNREADS_TIMEOUT = 2500;
|
||||
|
||||
export const entry = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const result = entryRest(serverUrl, teamId, channelId, since);
|
||||
|
||||
// Fetch data retention policies
|
||||
const isDataRetentionEnabled = await getIsDataRetentionEnabled(database);
|
||||
if (isDataRetentionEnabled) {
|
||||
fetchDataRetentionPolicy(serverUrl);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export async function deferredAppEntryActions(
|
||||
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
|
||||
config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
|
||||
initialTeamId?: string, initialChannelId?: string) {
|
||||
const result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
|
||||
|
||||
autoUpdateTimezone(serverUrl);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const getRemoveTeamIds = async (database: Database, teamData: MyTeamsRequest) => {
|
||||
export const getRemoveTeamIds = async (database: Database, teamData: MyTeamsRequest) => {
|
||||
const myTeams = await queryMyTeams(database).fetch();
|
||||
const joinedTeams = new Set(teamData.memberships?.filter((m) => m.delete_at === 0).map((m) => m.team_id));
|
||||
return myTeams.filter((m) => !joinedTeams.has(m.id)).map((m) => m.id);
|
||||
};
|
||||
|
||||
const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
|
||||
export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return [];
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
const {database} = operator;
|
||||
if (removeTeamIds?.length) {
|
||||
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
|
||||
const removeMyTeams = await queryMyTeamsByIds(database, removeTeamIds).fetch();
|
||||
@@ -117,23 +90,24 @@ const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
|
||||
export const entryRest = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
const lastDisconnectedAt = since || await getWebSocketLastDisconnected(database);
|
||||
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, teamId, channelId);
|
||||
const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, teamId);
|
||||
if ('error' in fetchedData) {
|
||||
return {error: fetchedData.error};
|
||||
}
|
||||
|
||||
const {initialTeamId, initialChannelId: fetchedChannelId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds, isCRTEnabled, gmConverted} = fetchedData;
|
||||
const chError = chData?.error;
|
||||
if (isErrorWithStatusCode(chError) && chError.status_code === 403) {
|
||||
const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds, isCRTEnabled} = fetchedData;
|
||||
const chError = chData?.error as ClientError | undefined;
|
||||
if (chError?.status_code === 403) {
|
||||
// if the user does not have appropriate permissions, which means the user those not belong to the team,
|
||||
// we set it as there is no errors, so that the teams and others can be properly handled
|
||||
chData!.error = undefined;
|
||||
@@ -145,7 +119,7 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string,
|
||||
|
||||
const rolesData = await fetchRoles(serverUrl, teamData.memberships, chData?.memberships, meData.user, true);
|
||||
|
||||
const initialChannelId = await entryInitialChannelId(database, fetchedChannelId, teamId, initialTeamId, meData?.user?.locale || '', chData?.channels, chData?.memberships);
|
||||
const initialChannelId = await entryInitialChannelId(database, channelId, teamId, initialTeamId, meData?.user?.locale || '', chData?.channels, chData?.memberships);
|
||||
|
||||
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
|
||||
|
||||
@@ -161,10 +135,10 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string,
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
|
||||
return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData, gmConverted};
|
||||
return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData};
|
||||
};
|
||||
|
||||
const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeamId = '', channelId?: string): Promise<AppEntryData | AppEntryError> => {
|
||||
export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTeamId = ''): Promise<AppEntryData | AppEntryError> => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -191,45 +165,16 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeam
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch in parallel teams / team membership / user preferences / user
|
||||
const promises: [Promise<MyTeamsRequest>, Promise<MyUserRequest>] = [
|
||||
// Fetch in parallel teams / team membership / channels for current team / user preferences / user
|
||||
const promises: [Promise<MyTeamsRequest>, Promise<MyChannelsRequest | undefined>, Promise<MyUserRequest>] = [
|
||||
fetchMyTeams(serverUrl, fetchOnly),
|
||||
initialTeamId ? fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled) : Promise.resolve(undefined),
|
||||
fetchMe(serverUrl, fetchOnly),
|
||||
];
|
||||
|
||||
const resolution = await Promise.all(promises);
|
||||
const [teamData, meData] = resolution;
|
||||
let chData;
|
||||
|
||||
let initialTeamId = onLoadTeamId;
|
||||
let initialChannelId = channelId;
|
||||
let gmConverted = false;
|
||||
|
||||
if (channelId) {
|
||||
const existingChannel = await getChannelById(database, channelId);
|
||||
if (existingChannel && existingChannel.type === General.GM_CHANNEL) {
|
||||
// Okay, so now we know the channel existsin in mobile app's database as a GM.
|
||||
// We now need to also check if channel on server is actually a private channel,
|
||||
// and if so, which team does it belong to now. That team will become the
|
||||
// active team on mobile app after this point.
|
||||
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const serverChannel = await client.getChannel(channelId);
|
||||
|
||||
// Although yon can convert GM only to a pirvate channel, a private channel can furthur be converted to a public channel.
|
||||
// So between the mobile app being on the GM and reconnecting,
|
||||
// it may have become either a public or a private channel. So we need to check for both.
|
||||
if (serverChannel.type === General.PRIVATE_CHANNEL || serverChannel.type === General.OPEN_CHANNEL) {
|
||||
initialTeamId = serverChannel.team_id;
|
||||
initialChannelId = channelId;
|
||||
gmConverted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initialTeamId) {
|
||||
chData = await fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled);
|
||||
}
|
||||
const [teamData, , meData] = resolution;
|
||||
let [, chData] = resolution;
|
||||
|
||||
if (!initialTeamId && teamData.teams?.length && teamData.memberships?.length) {
|
||||
// If no initial team was set in the database but got teams in the response
|
||||
@@ -253,8 +198,6 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeam
|
||||
meData,
|
||||
removeTeamIds,
|
||||
isCRTEnabled,
|
||||
initialChannelId,
|
||||
gmConverted,
|
||||
};
|
||||
|
||||
if (teamData.teams?.length === 0 && !teamData.error) {
|
||||
@@ -266,8 +209,8 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeam
|
||||
}
|
||||
|
||||
const inTeam = teamData.teams?.find((t) => t.id === initialTeamId);
|
||||
const chError = chData?.error;
|
||||
if ((!inTeam && !teamData.error) || (isErrorWithStatusCode(chError) && chError.status_code === 403)) {
|
||||
const chError = chData?.error as ClientError | undefined;
|
||||
if ((!inTeam && !teamData.error) || chError?.status_code === 403) {
|
||||
// User is no longer a member of the current team
|
||||
if (!removeTeamIds.includes(initialTeamId)) {
|
||||
removeTeamIds.push(initialTeamId);
|
||||
@@ -302,7 +245,7 @@ const fetchAppEntryData = async (serverUrl: string, sinceArg: number, onLoadTeam
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchAlternateTeamData = async (
|
||||
export const fetchAlternateTeamData = async (
|
||||
serverUrl: string, availableTeamIds: string[], removeTeamIds: string[],
|
||||
includeDeleted = true, since = 0, fetchOnly = false, isCRTEnabled?: boolean) => {
|
||||
let initialTeamId = '';
|
||||
@@ -311,8 +254,8 @@ const fetchAlternateTeamData = async (
|
||||
for (const teamId of availableTeamIds) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
chData = await fetchMyChannelsForTeam(serverUrl, teamId, includeDeleted, since, fetchOnly, false, isCRTEnabled);
|
||||
const chError = chData.error;
|
||||
if (isErrorWithStatusCode(chError) && chError.status_code === 403) {
|
||||
const chError = chData.error as ClientError | undefined;
|
||||
if (chError?.status_code === 403) {
|
||||
removeTeamIds.push(teamId);
|
||||
} else {
|
||||
initialTeamId = teamId;
|
||||
@@ -327,7 +270,7 @@ const fetchAlternateTeamData = async (
|
||||
return {initialTeamId, removeTeamIds};
|
||||
};
|
||||
|
||||
async function entryInitialChannelId(database: Database, requestedChannelId = '', requestedTeamId = '', initialTeamId: string, locale: string, channels?: Channel[], memberships?: ChannelMember[]) {
|
||||
export async function entryInitialChannelId(database: Database, requestedChannelId = '', requestedTeamId = '', initialTeamId: string, locale: string, channels?: Channel[], memberships?: ChannelMember[]) {
|
||||
const membershipIds = new Set(memberships?.map((m) => m.channel_id));
|
||||
const requestedChannel = channels?.find((c) => (c.id === requestedChannelId) && membershipIds.has(c.id));
|
||||
|
||||
@@ -365,7 +308,7 @@ async function entryInitialChannelId(database: Database, requestedChannelId = ''
|
||||
return myFirstTeamChannel?.id || '';
|
||||
}
|
||||
|
||||
async function restDeferredAppEntryActions(
|
||||
export async function restDeferredAppEntryActions(
|
||||
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
|
||||
config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
|
||||
initialTeamId?: string, initialChannelId?: string) {
|
||||
@@ -414,38 +357,50 @@ async function restDeferredAppEntryActions(
|
||||
}
|
||||
|
||||
export const registerDeviceToken = async (serverUrl: string) => {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
const deviceToken = await getDeviceToken();
|
||||
if (deviceToken) {
|
||||
client.attachDevice(deviceToken);
|
||||
}
|
||||
return {};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on registerDeviceToken', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
const deviceToken = await getDeviceToken();
|
||||
if (deviceToken) {
|
||||
client.attachDevice(deviceToken);
|
||||
}
|
||||
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export async function verifyPushProxy(serverUrl: string) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
const ppVerification = await getPushVerificationStatus(database);
|
||||
if (
|
||||
ppVerification !== PUSH_PROXY_STATUS_UNKNOWN &&
|
||||
ppVerification !== ''
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceId = await getDeviceToken();
|
||||
if (!deviceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ppVerification = await getPushVerificationStatus(database);
|
||||
if (
|
||||
ppVerification !== PUSH_PROXY_STATUS_UNKNOWN &&
|
||||
ppVerification !== ''
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
try {
|
||||
const response = await client.ping(deviceId);
|
||||
const canReceiveNotifications = response?.data?.CanReceiveNotifications;
|
||||
switch (canReceiveNotifications) {
|
||||
@@ -457,9 +412,7 @@ export async function verifyPushProxy(serverUrl: string) {
|
||||
default:
|
||||
operator.handleSystem({systems: [{id: SYSTEM_IDENTIFIERS.PUSH_VERIFICATION_STATUS, value: PUSH_PROXY_STATUS_VERIFIED}], prepareRecordsOnly: false});
|
||||
}
|
||||
} catch (error) {
|
||||
logDebug('error on verifyPushProxy', getFullErrorMessage(error));
|
||||
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -472,7 +425,6 @@ export async function handleEntryAfterLoadNavigation(
|
||||
currentChannelId: string,
|
||||
initialTeamId: string,
|
||||
initialChannelId: string,
|
||||
gmConverted: boolean,
|
||||
) {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
@@ -482,7 +434,7 @@ export async function handleEntryAfterLoadNavigation(
|
||||
const mountedScreens = NavigationStore.getScreensInStack();
|
||||
const isChannelScreenMounted = mountedScreens.includes(Screens.CHANNEL);
|
||||
const isThreadsMounted = mountedScreens.includes(Screens.THREAD);
|
||||
const tabletDevice = isTablet();
|
||||
const tabletDevice = await isTablet();
|
||||
|
||||
if (!currentTeamIdAfterLoad) {
|
||||
// First load or no team
|
||||
@@ -497,11 +449,7 @@ export async function handleEntryAfterLoadNavigation(
|
||||
await handleKickFromTeam(serverUrl, currentTeamIdAfterLoad);
|
||||
}
|
||||
} else if (currentTeamIdAfterLoad !== initialTeamId) {
|
||||
if (gmConverted) {
|
||||
await setCurrentTeamAndChannelId(operator, initialTeamId, currentChannelId);
|
||||
} else {
|
||||
await handleKickFromTeam(serverUrl, currentTeamIdAfterLoad);
|
||||
}
|
||||
await handleKickFromTeam(serverUrl, currentTeamIdAfterLoad);
|
||||
} else if (currentChannelIdAfterLoad !== currentChannelId) {
|
||||
// Switched channels while loading
|
||||
if (!channelMembers.find((m) => m.channel_id === currentChannelIdAfterLoad)) {
|
||||
|
||||
310
app/actions/remote/entry/gql_common.ts
Normal file
310
app/actions/remote/entry/gql_common.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {storeConfigAndLicense} from '@actions/local/systems';
|
||||
import {fetchGroupsForMember} from '@actions/remote/groups';
|
||||
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
|
||||
import {fetchDataRetentionPolicy} from '@actions/remote/systems';
|
||||
import {MyTeamsRequest, updateCanJoinTeams} from '@actions/remote/team';
|
||||
import {syncTeamThreads} from '@actions/remote/thread';
|
||||
import {autoUpdateTimezone, fetchProfilesInGroupChannels, updateAllUsersSince} from '@actions/remote/user';
|
||||
import {gqlEntry, gqlEntryChannels, gqlOtherChannels} from '@client/graphQL/entry';
|
||||
import {General, Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPreferenceValue} from '@helpers/api/preference';
|
||||
import {selectDefaultTeam} from '@helpers/api/team';
|
||||
import {queryAllChannels, queryAllChannelsForTeam} from '@queries/servers/channel';
|
||||
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
|
||||
import {getHasCRTChanged} from '@queries/servers/preference';
|
||||
import {getConfig, getIsDataRetentionEnabled} from '@queries/servers/system';
|
||||
import {filterAndTransformRoles, getMemberChannelsFromGQLQuery, getMemberTeamsFromGQLQuery, gqlToClientChannelMembership, gqlToClientPreference, gqlToClientSidebarCategory, gqlToClientTeamMembership, gqlToClientUser} from '@utils/graphql';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {processIsCRTEnabled} from '@utils/thread';
|
||||
|
||||
import {teamsToRemove, FETCH_UNREADS_TIMEOUT, entryRest, EntryResponse, entryInitialChannelId, restDeferredAppEntryActions, getRemoveTeamIds} from './common';
|
||||
|
||||
import type {MyChannelsRequest} from '@actions/remote/channel';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
const FETCH_MISSING_GM_TIMEOUT = 2500;
|
||||
|
||||
export async function deferredAppEntryGraphQLActions(
|
||||
serverUrl: string,
|
||||
since: number,
|
||||
currentUserId: string,
|
||||
teamData: MyTeamsRequest,
|
||||
chData: MyChannelsRequest | undefined,
|
||||
preferences: PreferenceType[] | undefined,
|
||||
config: ClientConfig,
|
||||
initialTeamId?: string,
|
||||
initialChannelId?: string,
|
||||
) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
setTimeout(() => {
|
||||
if (chData?.channels?.length && chData.memberships?.length) {
|
||||
// defer fetching posts for unread channels on initial team
|
||||
fetchPostsForUnreadChannels(serverUrl, chData.channels, chData.memberships, initialChannelId);
|
||||
}
|
||||
}, FETCH_UNREADS_TIMEOUT);
|
||||
|
||||
if (preferences && processIsCRTEnabled(preferences, config.CollapsedThreads, config.FeatureFlagCollapsedThreads, config.Version)) {
|
||||
if (initialTeamId) {
|
||||
await syncTeamThreads(serverUrl, initialTeamId);
|
||||
}
|
||||
|
||||
if (teamData.teams?.length) {
|
||||
for await (const team of teamData.teams) {
|
||||
if (team.id !== initialTeamId) {
|
||||
// need to await here since GM/DM threads in different teams overlap
|
||||
await syncTeamThreads(serverUrl, team.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initialTeamId) {
|
||||
const result = await getChannelData(serverUrl, initialTeamId, currentUserId, true);
|
||||
if ('error' in result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const removeChannels = await getRemoveChannels(database, result.chData, initialTeamId, false);
|
||||
|
||||
const modelPromises = await prepareModels({operator, removeChannels, chData: result.chData}, true);
|
||||
|
||||
const roles = filterAndTransformRoles(result.roles);
|
||||
if (roles.length) {
|
||||
modelPromises.push(operator.handleRole({roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
const models = (await Promise.all(modelPromises)).flat();
|
||||
operator.batchRecords(models, 'deferredAppEntryActions');
|
||||
|
||||
setTimeout(() => {
|
||||
if (result.chData?.channels?.length && result.chData.memberships?.length) {
|
||||
// defer fetching posts for unread channels on other teams
|
||||
fetchPostsForUnreadChannels(serverUrl, result.chData.channels, result.chData.memberships, initialChannelId);
|
||||
}
|
||||
}, FETCH_UNREADS_TIMEOUT);
|
||||
}
|
||||
|
||||
// Fetch groups for current user
|
||||
fetchGroupsForMember(serverUrl, currentUserId);
|
||||
|
||||
updateCanJoinTeams(serverUrl);
|
||||
updateAllUsersSince(serverUrl, since);
|
||||
|
||||
// defer sidebar GM profiles
|
||||
setTimeout(async () => {
|
||||
const gmIds = chData?.channels?.reduce<Set<string>>((acc, v) => {
|
||||
if (v?.type === General.GM_CHANNEL) {
|
||||
acc.add(v.id);
|
||||
}
|
||||
return acc;
|
||||
}, new Set<string>());
|
||||
if (gmIds?.size) {
|
||||
fetchProfilesInGroupChannels(serverUrl, Array.from(gmIds));
|
||||
}
|
||||
}, FETCH_MISSING_GM_TIMEOUT);
|
||||
|
||||
return {error: undefined};
|
||||
}
|
||||
|
||||
const getRemoveChannels = async (database: Database, chData: MyChannelsRequest | undefined, initialTeamId: string, singleTeam: boolean) => {
|
||||
const removeChannels: ChannelModel[] = [];
|
||||
if (chData?.channels) {
|
||||
const fetchedChannelIds = chData.channels?.map((channel) => channel.id);
|
||||
|
||||
const query = singleTeam ? queryAllChannelsForTeam(database, initialTeamId) : queryAllChannels(database);
|
||||
const channels = await query.fetch();
|
||||
|
||||
for (const channel of channels) {
|
||||
const excludeCondition = singleTeam ? true : channel.teamId !== initialTeamId && channel.teamId !== '';
|
||||
if (excludeCondition && !fetchedChannelIds?.includes(channel.id)) {
|
||||
removeChannels.push(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removeChannels;
|
||||
};
|
||||
|
||||
const getChannelData = async (serverUrl: string, initialTeamId: string, userId: string, exclude: boolean): Promise<{chData: MyChannelsRequest; roles: Array<Partial<GQLRole>|undefined>} | {error: unknown}> => {
|
||||
let response;
|
||||
try {
|
||||
const request = exclude ? gqlOtherChannels : gqlEntryChannels;
|
||||
response = await request(serverUrl, initialTeamId);
|
||||
} catch (error) {
|
||||
return {error: (error as ClientError).message};
|
||||
}
|
||||
|
||||
if ('error' in response) {
|
||||
return {error: response.error};
|
||||
}
|
||||
|
||||
if ('errors' in response && response.errors?.length) {
|
||||
return {error: response.errors[0].message};
|
||||
}
|
||||
|
||||
const channelsFetchedData = response.data;
|
||||
|
||||
const chData = {
|
||||
channels: getMemberChannelsFromGQLQuery(channelsFetchedData),
|
||||
memberships: channelsFetchedData.channelMembers?.map((m) => gqlToClientChannelMembership(m, userId)),
|
||||
categories: channelsFetchedData.sidebarCategories?.map((c) => gqlToClientSidebarCategory(c, '')),
|
||||
};
|
||||
const roles = channelsFetchedData.channelMembers?.map((m) => m.roles).flat() || [];
|
||||
|
||||
return {chData, roles};
|
||||
};
|
||||
|
||||
export const entryGQL = async (serverUrl: string, currentTeamId?: string, currentChannelId?: string): Promise<EntryResponse> => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await gqlEntry(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: (error as ClientError).message};
|
||||
}
|
||||
|
||||
if ('error' in response) {
|
||||
return {error: response.error};
|
||||
}
|
||||
|
||||
if ('errors' in response && response.errors?.length) {
|
||||
return {error: response.errors[0].message};
|
||||
}
|
||||
|
||||
const fetchedData = response.data;
|
||||
|
||||
const config = fetchedData.config || {} as ClientConfig;
|
||||
const license = fetchedData.license || {} as ClientLicense;
|
||||
await storeConfigAndLicense(serverUrl, config, license);
|
||||
|
||||
const meData = {
|
||||
user: gqlToClientUser(fetchedData.user!),
|
||||
};
|
||||
|
||||
const allTeams = getMemberTeamsFromGQLQuery(fetchedData);
|
||||
const allTeamMemberships = fetchedData.teamMembers.map((m) => gqlToClientTeamMembership(m, meData.user.id));
|
||||
|
||||
const [nonArchivedTeams, archivedTeamIds] = allTeams.reduce((acc, t) => {
|
||||
if (t.delete_at) {
|
||||
acc[1].add(t.id);
|
||||
return acc;
|
||||
}
|
||||
return [[...acc[0], t], acc[1]];
|
||||
}, [[], new Set<string>()]);
|
||||
|
||||
const nonArchivedTeamMemberships = allTeamMemberships.filter((m) => !archivedTeamIds.has(m.team_id));
|
||||
|
||||
const teamData = {
|
||||
teams: nonArchivedTeams,
|
||||
memberships: nonArchivedTeamMemberships,
|
||||
};
|
||||
|
||||
const prefData = {
|
||||
preferences: fetchedData.user?.preferences?.map(gqlToClientPreference),
|
||||
};
|
||||
|
||||
if (prefData.preferences) {
|
||||
const crtToggled = await getHasCRTChanged(database, prefData.preferences);
|
||||
if (crtToggled) {
|
||||
const {error} = await truncateCrtRelatedTables(serverUrl);
|
||||
if (error) {
|
||||
return {error: `Resetting CRT on ${serverUrl} failed`};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let initialTeamId = currentTeamId;
|
||||
if (!teamData.teams.length) {
|
||||
initialTeamId = '';
|
||||
} else if (!initialTeamId || !teamData.teams.find((t) => t.id === currentTeamId && t.delete_at === 0)) {
|
||||
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences || [], Preferences.CATEGORIES.TEAMS_ORDER, '', '');
|
||||
initialTeamId = selectDefaultTeam(teamData.teams, meData.user.locale, teamOrderPreference, config.ExperimentalPrimaryTeam)?.id || '';
|
||||
}
|
||||
const gqlRoles = [
|
||||
...fetchedData.user?.roles || [],
|
||||
...fetchedData.teamMembers?.map((m) => m.roles).flat() || [],
|
||||
];
|
||||
|
||||
let chData;
|
||||
if (initialTeamId) {
|
||||
const result = await getChannelData(serverUrl, initialTeamId, meData.user.id, false);
|
||||
if ('error' in result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
chData = result.chData;
|
||||
gqlRoles.push(...result.roles);
|
||||
}
|
||||
|
||||
const roles = filterAndTransformRoles(gqlRoles);
|
||||
|
||||
const initialChannelId = await entryInitialChannelId(database, currentChannelId, currentTeamId, initialTeamId, meData.user.id, chData?.channels, chData?.memberships);
|
||||
const removeChannels = await getRemoveChannels(database, chData, initialTeamId, true);
|
||||
const removeTeamIds = await getRemoveTeamIds(database, teamData);
|
||||
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
|
||||
|
||||
const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData}, true);
|
||||
if (roles.length) {
|
||||
modelPromises.push(operator.handleRole({roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
const models = (await Promise.all(modelPromises)).flat();
|
||||
return {models, initialTeamId, initialChannelId, prefData, teamData, chData, meData};
|
||||
};
|
||||
|
||||
export const entry = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise<EntryResponse> => {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const config = await getConfig(database);
|
||||
let result;
|
||||
if (config?.FeatureFlagGraphQL === 'true') {
|
||||
result = await entryGQL(serverUrl, teamId, channelId);
|
||||
if ('error' in result) {
|
||||
logDebug('Error using GraphQL, trying REST', result.error);
|
||||
result = entryRest(serverUrl, teamId, channelId, since);
|
||||
}
|
||||
} else {
|
||||
result = entryRest(serverUrl, teamId, channelId, since);
|
||||
}
|
||||
|
||||
// Fetch data retention policies
|
||||
const isDataRetentionEnabled = await getIsDataRetentionEnabled(database);
|
||||
if (isDataRetentionEnabled) {
|
||||
fetchDataRetentionPolicy(serverUrl);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export async function deferredAppEntryActions(
|
||||
serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined,
|
||||
config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined,
|
||||
initialTeamId?: string, initialChannelId?: string) {
|
||||
let result;
|
||||
if (config?.FeatureFlagGraphQL === 'true') {
|
||||
result = await deferredAppEntryGraphQLActions(serverUrl, since, currentUserId, teamData, chData, preferences, config, initialTeamId, initialChannelId);
|
||||
if (result.error) {
|
||||
logDebug('Error using GraphQL, trying REST', result.error);
|
||||
result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
|
||||
}
|
||||
} else {
|
||||
result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
|
||||
}
|
||||
|
||||
autoUpdateTimezone(serverUrl);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ type AfterLoginArgs = {
|
||||
serverUrl: string;
|
||||
}
|
||||
|
||||
export async function loginEntry({serverUrl}: AfterLoginArgs): Promise<{error?: unknown}> {
|
||||
export async function loginEntry({serverUrl}: AfterLoginArgs): Promise<{error?: any}> {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
|
||||
@@ -15,25 +15,24 @@ import {getCurrentTeamId} from '@queries/servers/system';
|
||||
import {getMyTeamById} from '@queries/servers/team';
|
||||
import {getIsCRTEnabled} from '@queries/servers/thread';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isErrorWithStatusCode} from '@utils/errors';
|
||||
import {emitNotificationError} from '@utils/notification';
|
||||
import {setThemeDefaults, updateThemeIfNeeded} from '@utils/theme';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type MyTeamModel from '@typings/database/models/servers/my_team';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
export async function pushNotificationEntry(serverUrl: string, notification: NotificationData) {
|
||||
// We only reach this point if we have a channel Id in the notification payload
|
||||
const channelId = notification.channel_id!;
|
||||
const rootId = notification.root_id!;
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
// We only reach this point if we have a channel Id in the notification payload
|
||||
const channelId = notification.channel_id!;
|
||||
const rootId = notification.root_id!;
|
||||
const {database} = operator;
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
const currentServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
|
||||
@@ -66,7 +65,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
||||
if (!myTeam) {
|
||||
const resp = await fetchMyTeam(serverUrl, teamId);
|
||||
if (resp.error) {
|
||||
if (isErrorWithStatusCode(resp.error) && resp.error.status_code === 403) {
|
||||
if ((resp.error as ClientError).status_code === 403) {
|
||||
emitNotificationError('Team');
|
||||
} else {
|
||||
emitNotificationError('Connection');
|
||||
@@ -79,7 +78,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
||||
if (!myChannel) {
|
||||
const resp = await fetchMyChannel(serverUrl, teamId, channelId);
|
||||
if (resp.error) {
|
||||
if (isErrorWithStatusCode(resp.error) && resp.error.status_code === 403) {
|
||||
if ((resp.error as ClientError).status_code === 403) {
|
||||
emitNotificationError('Channel');
|
||||
} else {
|
||||
emitNotificationError('Connection');
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
import {DOWNLOAD_TIMEOUT} from '@constants/network';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {ClientResponse, ClientResponseError} from '@mattermost/react-native-network-client';
|
||||
|
||||
export const downloadFile = (serverUrl: string, fileId: string, desitnation: string) => { // Let it throw and handle it accordingly
|
||||
@@ -30,23 +30,29 @@ export const uploadFile = (
|
||||
onError: (response: ClientResponseError) => void = () => {/*Do Nothing*/},
|
||||
skipBytes = 0,
|
||||
) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
return {cancel: client.uploadPostAttachment(file, channelId, onProgress, onComplete, onError, skipBytes)};
|
||||
} catch (error) {
|
||||
logDebug('error on uploadFile', getFullErrorMessage(error));
|
||||
return {error};
|
||||
logDebug('uploadFile', error);
|
||||
return {error: error as ClientError};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPublicLink = async (serverUrl: string, fileId: string) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: error as ClientError};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const publicLink = await client!.getFilePublicLink(fileId);
|
||||
return publicLink;
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPublicLink', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,8 +8,6 @@ import {t} from '@i18n';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getDeviceToken} from '@queries/app/global';
|
||||
import {getExpandedLinks, getPushVerificationStatus} from '@queries/servers/system';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
@@ -65,12 +63,10 @@ export const doPing = async (serverUrl: string, verifyPushProxy: boolean, timeou
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
logDebug('Server ping returned not ok response', response);
|
||||
NetworkManager.invalidateClient(serverUrl);
|
||||
return {error: {intl: pingError}};
|
||||
}
|
||||
} catch (error) {
|
||||
logDebug('Server ping threw an exception', getFullErrorMessage(error));
|
||||
NetworkManager.invalidateClient(serverUrl);
|
||||
return {error: {intl: pingError}};
|
||||
}
|
||||
@@ -83,19 +79,29 @@ export const doPing = async (serverUrl: string, verifyPushProxy: boolean, timeou
|
||||
canReceiveNotifications = PUSH_PROXY_RESPONSE_VERIFIED;
|
||||
}
|
||||
|
||||
return {canReceiveNotifications};
|
||||
return {canReceiveNotifications, error: undefined};
|
||||
}
|
||||
|
||||
return {};
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export const getRedirectLocation = async (serverUrl: string, link: string) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const expandedLink = await client.getRedirectLocation(link);
|
||||
if (expandedLink?.location) {
|
||||
const storedLinks = await getExpandedLinks(database);
|
||||
const storedLinks = await getExpandedLinks(operator.database);
|
||||
storedLinks[link] = expandedLink.location;
|
||||
const expanded: IdValue = {
|
||||
id: SYSTEM_IDENTIFIERS.EXPANDED_LINKS,
|
||||
@@ -109,8 +115,7 @@ export const getRedirectLocation = async (serverUrl: string, link: string) => {
|
||||
|
||||
return {expandedLink};
|
||||
} catch (error) {
|
||||
logDebug('error on getRedirectLocation', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,23 +4,30 @@
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getLicense} from '@queries/servers/system';
|
||||
import {getTeamById} from '@queries/servers/team';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
|
||||
export const fetchGroup = async (serverUrl: string, id: string, fetchOnly = false) => {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client: Client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
const group = await client.getGroup(id);
|
||||
|
||||
// Save locally
|
||||
return operator.handleGroups({groups: [group], prepareRecordsOnly: fetchOnly});
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGroupsForAutocomplete = async (serverUrl: string, query: string, fetchOnly = false) => {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const license = await getLicense(database);
|
||||
if (!license || license.IsLicensed !== 'true') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client: Client = NetworkManager.getClient(serverUrl);
|
||||
const response = await client.getGroups({query, includeMemberCount: true});
|
||||
|
||||
@@ -30,19 +37,14 @@ export const fetchGroupsForAutocomplete = async (serverUrl: string, query: strin
|
||||
|
||||
return operator.handleGroups({groups: response, prepareRecordsOnly: fetchOnly});
|
||||
} catch (error) {
|
||||
logDebug('error on fetchGroupsForAutocomplete', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGroupsByNames = async (serverUrl: string, names: string[], fetchOnly = false) => {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const license = await getLicense(database);
|
||||
if (!license || license.IsLicensed !== 'true') {
|
||||
return [];
|
||||
}
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const client: Client = NetworkManager.getClient(serverUrl);
|
||||
const promises: Array <Promise<Group[]>> = [];
|
||||
@@ -60,20 +62,14 @@ export const fetchGroupsByNames = async (serverUrl: string, names: string[], fet
|
||||
|
||||
return operator.handleGroups({groups, prepareRecordsOnly: fetchOnly});
|
||||
} catch (error) {
|
||||
logDebug('error on fetchGroupsByNames', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGroupsForChannel = async (serverUrl: string, channelId: string, fetchOnly = false) => {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const license = await getLicense(database);
|
||||
if (!license || license.IsLicensed !== 'true') {
|
||||
return {groups: [], groupChannels: []};
|
||||
}
|
||||
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const response = await client.getAllGroupsAssociatedToChannel(channelId);
|
||||
|
||||
@@ -92,19 +88,14 @@ export const fetchGroupsForChannel = async (serverUrl: string, channelId: string
|
||||
|
||||
return {groups, groupChannels};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchGroupsForChannel', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, fetchOnly = false) => {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const license = await getLicense(database);
|
||||
if (!license || license.IsLicensed !== 'true') {
|
||||
return {groups: [], groupTeams: []};
|
||||
}
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const client: Client = NetworkManager.getClient(serverUrl);
|
||||
const response = await client.getAllGroupsAssociatedToTeam(teamId);
|
||||
@@ -124,18 +115,13 @@ export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, fetc
|
||||
|
||||
return {groups, groupTeams};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchGroupsForTeam', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGroupsForMember = async (serverUrl: string, userId: string, fetchOnly = false) => {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const license = await getLicense(database);
|
||||
if (!license || license.IsLicensed !== 'true') {
|
||||
return {groups: [], groupMemberships: []};
|
||||
}
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const client: Client = NetworkManager.getClient(serverUrl);
|
||||
const response = await client.getAllGroupsAssociatedToMembership(userId);
|
||||
@@ -155,25 +141,36 @@ export const fetchGroupsForMember = async (serverUrl: string, userId: string, fe
|
||||
|
||||
return {groups, groupMemberships};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchGroupsForMember', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchFilteredTeamGroups = async (serverUrl: string, searchTerm: string, teamId: string) => {
|
||||
const res = await fetchGroupsForTeam(serverUrl, teamId);
|
||||
if ('error' in res) {
|
||||
return {error: res.error};
|
||||
try {
|
||||
const groups = await fetchGroupsForTeam(serverUrl, teamId);
|
||||
|
||||
if (groups && Array.isArray(groups)) {
|
||||
return groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
}
|
||||
|
||||
throw groups.error;
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
return res.groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
};
|
||||
|
||||
export const fetchFilteredChannelGroups = async (serverUrl: string, searchTerm: string, channelId: string) => {
|
||||
const res = await fetchGroupsForChannel(serverUrl, channelId);
|
||||
if ('error' in res) {
|
||||
return {error: res.error};
|
||||
try {
|
||||
const groups = await fetchGroupsForChannel(serverUrl, channelId);
|
||||
|
||||
if (groups && Array.isArray(groups)) {
|
||||
return groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
}
|
||||
|
||||
throw groups.error;
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
return res.groups.filter((g) => g.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
};
|
||||
|
||||
export const fetchGroupsForTeamIfConstrained = async (serverUrl: string, teamId: string, fetchOnly = false) => {
|
||||
|
||||
@@ -6,31 +6,45 @@ import DatabaseManager from '@database/manager';
|
||||
import IntegrationsMananger from '@managers/integrations_manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getCurrentChannelId, getCurrentTeamId} from '@queries/servers/system';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
export const submitInteractiveDialog = async (serverUrl: string, submission: DialogSubmission) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
import type {Client} from '@client/rest';
|
||||
|
||||
submission.channel_id = await getCurrentChannelId(database);
|
||||
submission.team_id = await getCurrentTeamId(database);
|
||||
export const submitInteractiveDialog = async (serverUrl: string, submission: DialogSubmission) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
submission.channel_id = await getCurrentChannelId(database);
|
||||
submission.team_id = await getCurrentTeamId(database);
|
||||
|
||||
try {
|
||||
const data = await client.submitInteractiveDialog(submission);
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on submitInteractiveDialog', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const postActionWithCookie = async (serverUrl: string, postId: string, actionId: string, actionCookie: string, selectedOption = '') => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await client.doPostActionWithCookie(postId, actionId, actionCookie, selectedOption);
|
||||
if (data?.trigger_id) {
|
||||
IntegrationsMananger.getManager(serverUrl)?.setTriggerId(data.trigger_id);
|
||||
@@ -38,8 +52,7 @@ export const postActionWithCookie = async (serverUrl: string, postId: string, ac
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on postActionWithCookie', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,15 +25,19 @@ import {processPostsFetched} from '@utils/post';
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
|
||||
const fetchNotificationData = async (serverUrl: string, notification: NotificationWithData, skipEvents = false) => {
|
||||
const channelId = notification.payload?.channel_id;
|
||||
|
||||
if (!channelId) {
|
||||
return {error: 'No chanel Id was specified'};
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const channelId = notification.payload?.channel_id;
|
||||
|
||||
if (!channelId) {
|
||||
return {error: 'No chanel Id was specified'};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
let teamId = notification.payload?.team_id;
|
||||
let isDirectChannel = false;
|
||||
@@ -92,7 +96,7 @@ const fetchNotificationData = async (serverUrl: string, notification: Notificati
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -180,19 +184,16 @@ export const backgroundNotification = async (serverUrl: string, notification: No
|
||||
};
|
||||
|
||||
export const openNotification = async (serverUrl: string, notification: NotificationWithData) => {
|
||||
// Wait for initial launch to kick in if needed
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
if (EphemeralStore.getProcessingNotification() === notification.identifier) {
|
||||
return {};
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
EphemeralStore.setNotificationTapped(true);
|
||||
|
||||
const channelId = notification.payload!.channel_id!;
|
||||
const rootId = notification.payload!.root_id!;
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
EphemeralStore.setNotificationTapped(true);
|
||||
const {database} = operator;
|
||||
const channelId = notification.payload!.channel_id!;
|
||||
const rootId = notification.payload!.root_id!;
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isThreadNotification = isCRTEnabled && Boolean(rootId);
|
||||
@@ -231,7 +232,7 @@ export const openNotification = async (serverUrl: string, notification: Notifica
|
||||
}
|
||||
return switchToChannelById(serverUrl, channelId, teamId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
import {General} from '@constants';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
export const isNPSEnabled = async (serverUrl: string) => {
|
||||
try {
|
||||
@@ -17,7 +15,6 @@ export const isNPSEnabled = async (serverUrl: string) => {
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
logDebug('error on isNPSEnabled', getFullErrorMessage(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -28,7 +25,6 @@ export const giveFeedbackAction = async (serverUrl: string) => {
|
||||
const post = await client.npsGiveFeedbackAction();
|
||||
return {post};
|
||||
} catch (error) {
|
||||
logDebug('error on giveFeedbackAction', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,9 +9,12 @@ import {displayPermalink} from '@utils/permalink';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
export const showPermalink = async (serverUrl: string, teamName: string, postId: string, openAsPermalink = true) => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
let name = teamName;
|
||||
let team: TeamModel | undefined;
|
||||
if (!name || name === DeepLink.Redirect) {
|
||||
@@ -23,7 +26,7 @@ export const showPermalink = async (serverUrl: string, teamName: string, postId:
|
||||
|
||||
await displayPermalink(name, postId, openAsPermalink);
|
||||
|
||||
return {};
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {markChannelAsUnread, updateLastPostAt} from '@actions/local/channel';
|
||||
import {addPostAcknowledgement, removePost, removePostAcknowledgement, storePostsForChannel} from '@actions/local/post';
|
||||
import {removePost, storePostsForChannel} from '@actions/local/post';
|
||||
import {addRecentReaction} from '@actions/local/reactions';
|
||||
import {createThreadFromNewPost} from '@actions/local/thread';
|
||||
import {ActionType, General, Post, ServerErrors} from '@constants';
|
||||
import {ActionType, Events, General, Post, ServerErrors} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {filterPostsInOrderedArray} from '@helpers/api/post';
|
||||
import {getNeededAtMentionedUsernames} from '@helpers/api/user';
|
||||
import {extractRecordsForTable} from '@helpers/database';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getMyChannel, prepareMissingChannelsForAllTeams, queryAllMyChannel} from '@queries/servers/channel';
|
||||
import {queryAllCustomEmojis} from '@queries/servers/custom_emoji';
|
||||
@@ -19,11 +23,10 @@ import {getPostById, getRecentPostsInChannel} from '@queries/servers/post';
|
||||
import {getCurrentUserId, getCurrentChannelId} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
|
||||
import {queryAllUsers} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {setFetchingThreadState} from '@store/fetching_thread_store';
|
||||
import {getValidEmojis, matchEmoticons} from '@utils/emoji/helpers';
|
||||
import {getFullErrorMessage, isServerError} from '@utils/errors';
|
||||
import {logDebug, logError} from '@utils/log';
|
||||
import {isServerError} from '@utils/errors';
|
||||
import {logError} from '@utils/log';
|
||||
import {processPostsFetched} from '@utils/post';
|
||||
import {getPostIdsForCombinedUserActivityPost} from '@utils/post_list';
|
||||
|
||||
@@ -57,7 +60,6 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
@@ -66,6 +68,8 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
|
||||
return {error};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const timestamp = Date.now();
|
||||
const pendingPostId = post.pending_post_id || `${currentUserId}:${timestamp}`;
|
||||
@@ -130,9 +134,8 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
|
||||
|
||||
let created;
|
||||
try {
|
||||
created = await client.createPost({...newPost, create_at: 0});
|
||||
created = await client.createPost(newPost);
|
||||
} catch (error) {
|
||||
logDebug('Error sending a post', getFullErrorMessage(error));
|
||||
const errorPost = {
|
||||
...newPost,
|
||||
id: pendingPostId,
|
||||
@@ -201,7 +204,6 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
@@ -210,6 +212,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
|
||||
try {
|
||||
@@ -252,7 +255,6 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
}
|
||||
await operator.batchRecords(models, 'retryFailedPost - success update');
|
||||
} catch (error) {
|
||||
logDebug('error on retryFailedPost', getFullErrorMessage(error));
|
||||
if (isServerError(error) && (
|
||||
error.server_error_id === ServerErrors.DELETED_ROOT_POST_ERROR ||
|
||||
error.server_error_id === ServerErrors.TOWN_SQUARE_READ_ONLY_ERROR ||
|
||||
@@ -272,7 +274,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {};
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export const fetchPostsForCurrentChannel = async (serverUrl: string) => {
|
||||
@@ -287,9 +289,6 @@ export const fetchPostsForCurrentChannel = async (serverUrl: string) => {
|
||||
|
||||
export async function fetchPostsForChannel(serverUrl: string, channelId: string, fetchOnly = false) {
|
||||
try {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.addLoadingMessagesForChannel(serverUrl, channelId);
|
||||
}
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
let postAction: Promise<PostsRequest>|undefined;
|
||||
let actionType: string|undefined;
|
||||
@@ -308,6 +307,7 @@ export async function fetchPostsForChannel(serverUrl: string, channelId: string,
|
||||
if (data.error) {
|
||||
throw data.error;
|
||||
}
|
||||
|
||||
let authors: UserProfile[] = [];
|
||||
if (data.posts?.length && data.order?.length) {
|
||||
const {authors: fetchedAuthors} = await fetchPostAuthors(serverUrl, data.posts, true);
|
||||
@@ -324,40 +324,39 @@ export async function fetchPostsForChannel(serverUrl: string, channelId: string,
|
||||
|
||||
return {posts: data.posts, order: data.order, authors, actionType, previousPostId: data.previousPostId};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostsForChannel', getFullErrorMessage(error));
|
||||
logError('FetchPostsForChannel', error);
|
||||
return {error};
|
||||
} finally {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.stopLoadingMessagesForChannel(serverUrl, channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchPostsForUnreadChannels = async (serverUrl: string, channels: Channel[], memberships: ChannelMembership[], excludeChannelId?: string) => {
|
||||
const promises = [];
|
||||
for (const member of memberships) {
|
||||
const channel = channels.find((c) => c.id === member.channel_id);
|
||||
if (channel && !channel.delete_at && (channel.total_msg_count - member.msg_count) > 0 && channel.id !== excludeChannelId) {
|
||||
promises.push(fetchPostsForChannel(serverUrl, channel.id));
|
||||
try {
|
||||
const promises = [];
|
||||
for (const member of memberships) {
|
||||
const channel = channels.find((c) => c.id === member.channel_id);
|
||||
if (channel && (channel.total_msg_count - member.msg_count) > 0 && channel.id !== excludeChannelId) {
|
||||
promises.push(fetchPostsForChannel(serverUrl, channel.id));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export async function fetchPosts(serverUrl: string, channelId: string, page = 0, perPage = General.POST_CHUNK_SIZE, fetchOnly = false): Promise<PostsRequest> {
|
||||
try {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.addLoadingMessagesForChannel(serverUrl, channelId);
|
||||
}
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const data = await client.getPosts(channelId, page, perPage, isCRTEnabled, isCRTEnabled);
|
||||
const result = processPostsFetched(data);
|
||||
if (!fetchOnly && result.posts.length) {
|
||||
if (!fetchOnly) {
|
||||
const models = await operator.handlePosts({
|
||||
...result,
|
||||
actionType: ActionType.POSTS.RECEIVED_IN_CHANNEL,
|
||||
actionType: ActionType.POSTS.RECEIVED_SINCE,
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
@@ -376,34 +375,42 @@ export async function fetchPosts(serverUrl: string, channelId: string, page = 0,
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models, 'fetchPosts');
|
||||
}
|
||||
await operator.batchRecords(models, 'fetchPosts');
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPosts', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
} finally {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.stopLoadingMessagesForChannel(serverUrl, channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPostsBefore(serverUrl: string, channelId: string, postId: string, perPage = General.POST_CHUNK_SIZE, fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.addLoadingMessagesForChannel(serverUrl, channelId);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const activeServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
|
||||
try {
|
||||
if (activeServerUrl === serverUrl) {
|
||||
DeviceEventEmitter.emit(Events.LOADING_CHANNEL_POSTS, true);
|
||||
}
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const data = await client.getPostsBefore(channelId, postId, 0, perPage, isCRTEnabled, isCRTEnabled);
|
||||
const result = processPostsFetched(data);
|
||||
|
||||
if (activeServerUrl === serverUrl) {
|
||||
DeviceEventEmitter.emit(Events.LOADING_CHANNEL_POSTS, false);
|
||||
}
|
||||
|
||||
if (result.posts.length && !fetchOnly) {
|
||||
try {
|
||||
const models = await operator.handlePosts({
|
||||
@@ -432,27 +439,32 @@ export async function fetchPostsBefore(serverUrl: string, channelId: string, pos
|
||||
logError('FETCH POSTS BEFORE ERROR', error);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostsBefore', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
} finally {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.stopLoadingMessagesForChannel(serverUrl, channelId);
|
||||
if (activeServerUrl === serverUrl) {
|
||||
DeviceEventEmitter.emit(Events.LOADING_CHANNEL_POSTS, false);
|
||||
}
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPostsSince(serverUrl: string, channelId: string, since: number, fetchOnly = false): Promise<PostsRequest> {
|
||||
try {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.addLoadingMessagesForChannel(serverUrl, channelId);
|
||||
}
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const data = await client.getPostsSince(channelId, since, isCRTEnabled, isCRTEnabled);
|
||||
const result = await processPostsFetched(data);
|
||||
if (!fetchOnly) {
|
||||
@@ -481,42 +493,47 @@ export async function fetchPostsSince(serverUrl: string, channelId: string, sinc
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostsSince', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
} finally {
|
||||
if (!fetchOnly) {
|
||||
EphemeralStore.stopLoadingMessagesForChannel(serverUrl, channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchPostAuthors = async (serverUrl: string, posts: Post[], fetchOnly = false): Promise<AuthorsRequest> => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const users = await queryAllUsers(database).fetch();
|
||||
const existingUserIds = new Set<string>();
|
||||
const existingUserNames = new Set<string>();
|
||||
let excludeUsername;
|
||||
users.forEach((u) => {
|
||||
existingUserIds.add(u.id);
|
||||
existingUserNames.add(u.username);
|
||||
if (u.id === currentUserId) {
|
||||
excludeUsername = u.username;
|
||||
}
|
||||
});
|
||||
|
||||
const usernamesToLoad = getNeededAtMentionedUsernames(existingUserNames, posts, excludeUsername);
|
||||
const userIdsToLoad = new Set<string>();
|
||||
for (const p of posts) {
|
||||
const {user_id} = p;
|
||||
if (user_id !== currentUserId) {
|
||||
userIdsToLoad.add(user_id);
|
||||
}
|
||||
const currentUserId = await getCurrentUserId(operator.database);
|
||||
const users = await queryAllUsers(operator.database).fetch();
|
||||
const existingUserIds = new Set<string>();
|
||||
const existingUserNames = new Set<string>();
|
||||
let excludeUsername;
|
||||
users.forEach((u) => {
|
||||
existingUserIds.add(u.id);
|
||||
existingUserNames.add(u.username);
|
||||
if (u.id === currentUserId) {
|
||||
excludeUsername = u.username;
|
||||
}
|
||||
});
|
||||
|
||||
const usernamesToLoad = getNeededAtMentionedUsernames(existingUserNames, posts, excludeUsername);
|
||||
const userIdsToLoad = new Set<string>();
|
||||
for (const p of posts) {
|
||||
const {user_id} = p;
|
||||
if (user_id !== currentUserId) {
|
||||
userIdsToLoad.add(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const promises: Array<Promise<UserProfile[]>> = [];
|
||||
if (userIdsToLoad.size) {
|
||||
promises.push(client.getProfilesByIds(Array.from(userIdsToLoad)));
|
||||
@@ -548,20 +565,29 @@ export const fetchPostAuthors = async (serverUrl: string, posts: Post[], fetchOn
|
||||
|
||||
return {authors: [] as UserProfile[]};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostAuthors', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
logError('FETCH AUTHORS ERROR', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchPostThread(serverUrl: string, postId: string, options?: FetchPaginatedThreadOptions, fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
setFetchingThreadState(postId, true);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
setFetchingThreadState(postId, true);
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
try {
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
|
||||
// Not doing any version check as server versions below 6.7 will ignore the additional params from the client.
|
||||
const data = await client.getPostThread(postId, {
|
||||
@@ -598,20 +624,28 @@ export async function fetchPostThread(serverUrl: string, postId: string, options
|
||||
await operator.batchRecords(models, 'fetchPostThread');
|
||||
}
|
||||
setFetchingThreadState(postId, false);
|
||||
return {posts: result.posts};
|
||||
return {posts: extractRecordsForTable<PostModel>(posts, MM_TABLES.SERVER.POST)};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostThread', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
setFetchingThreadState(postId, false);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPostsAround(serverUrl: string, channelId: string, postId: string, perPage = General.POST_AROUND_CHUNK_SIZE, isCRTEnabled = false) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const [after, post, before] = await Promise.all<PostsObjectsRequest>([
|
||||
client.getPostsAfter(channelId, postId, 0, perPage, isCRTEnabled, isCRTEnabled),
|
||||
client.getPostThread(postId, {
|
||||
@@ -666,20 +700,29 @@ export async function fetchPostsAround(serverUrl: string, channelId: string, pos
|
||||
await operator.batchRecords(models, 'fetchPostsAround');
|
||||
}
|
||||
|
||||
return {posts: data.posts};
|
||||
return {posts: extractRecordsForTable<PostModel>(posts, MM_TABLES.SERVER.POST)};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostsAround', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
logError('FETCH POSTS AROUND ERROR', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Post[], fetchOnly = false) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const channelIds = new Set(await queryAllMyChannel(database).fetchIds());
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const channelIds = new Set(await queryAllMyChannel(operator.database).fetchIds());
|
||||
const channelPromises: Array<Promise<Channel>> = [];
|
||||
const userPromises: Array<Promise<ChannelMembership>> = [];
|
||||
|
||||
@@ -696,7 +739,7 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po
|
||||
const channelMemberships = await Promise.all(userPromises);
|
||||
|
||||
if (!fetchOnly && channels.length && channelMemberships.length) {
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled);
|
||||
if (modelPromises.length) {
|
||||
const channelModelsArray = await Promise.all(modelPromises);
|
||||
@@ -719,16 +762,25 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po
|
||||
channelMemberships,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchMissingChannelsFromPosts', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPostById(serverUrl: string, postId: string, fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const post = await client.getPost(postId);
|
||||
if (!fetchOnly) {
|
||||
const models: Model[] = [];
|
||||
@@ -749,7 +801,7 @@ export async function fetchPostById(serverUrl: string, postId: string, fetchOnly
|
||||
models.push(...users);
|
||||
}
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
if (isCRTEnabled) {
|
||||
const threadModels = await prepareThreadsFromReceivedPosts(operator, [post], false);
|
||||
if (threadModels?.length) {
|
||||
@@ -762,17 +814,25 @@ export async function fetchPostById(serverUrl: string, postId: string, fetchOnly
|
||||
|
||||
return {post};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPostById', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export const togglePinPost = async (serverUrl: string, postId: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const post = await getPostById(database, postId);
|
||||
if (post) {
|
||||
const isPinned = post.isPinned;
|
||||
@@ -787,15 +847,25 @@ export const togglePinPost = async (serverUrl: string, postId: string) => {
|
||||
}
|
||||
return {post};
|
||||
} catch (error) {
|
||||
logDebug('error on togglePinPost', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePost = async (serverUrl: string, postToDelete: PostModel | Post) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
if (postToDelete.type === Post.POST_TYPES.COMBINED_USER_ACTIVITY && postToDelete.props?.system_post_ids) {
|
||||
const systemPostIds = getPostIdsForCombinedUserActivityPost(postToDelete.id);
|
||||
const promises = systemPostIds.map((id) => client.deletePost(id));
|
||||
@@ -807,17 +877,24 @@ export const deletePost = async (serverUrl: string, postToDelete: PostModel | Po
|
||||
const post = await removePost(serverUrl, postToDelete);
|
||||
return {post};
|
||||
} catch (error) {
|
||||
logDebug('error on deletePost', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const markPostAsUnread = async (serverUrl: string, postId: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const [userId, post] = await Promise.all([getCurrentUserId(database), getPostById(database, postId)]);
|
||||
if (post && userId) {
|
||||
await client.markPostAsUnread(userId, postId);
|
||||
@@ -841,48 +918,67 @@ export const markPostAsUnread = async (serverUrl: string, postId: string) => {
|
||||
|
||||
const messageCount = totalMessages - messages;
|
||||
await markChannelAsUnread(serverUrl, channelId, messageCount, mentionCount, post.createAt);
|
||||
return {post};
|
||||
return {
|
||||
post,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {post};
|
||||
return {
|
||||
post,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on markPostAsUnread', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const editPost = async (serverUrl: string, postId: string, postMessage: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const post = await getPostById(database, postId);
|
||||
if (post) {
|
||||
const {update_at, edit_at, message: updatedMessage, message_source} = await client.patchPost({message: postMessage, id: postId});
|
||||
const {update_at, edit_at, message: updatedMessage} = await client.patchPost({message: postMessage, id: postId});
|
||||
await database.write(async () => {
|
||||
await post.update((p) => {
|
||||
p.updateAt = update_at;
|
||||
p.editAt = edit_at;
|
||||
p.message = updatedMessage;
|
||||
p.messageSource = message_source || '';
|
||||
});
|
||||
});
|
||||
}
|
||||
return {post};
|
||||
return {
|
||||
post,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on editPost', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchSavedPosts(serverUrl: string, teamId?: string, channelId?: string, page?: number, perPage?: number) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
try {
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
const data = await client.getSavedPosts(userId, channelId, teamId, page, perPage);
|
||||
const posts = data.posts || {};
|
||||
const order = data.order || [];
|
||||
@@ -910,7 +1006,7 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
|
||||
}
|
||||
|
||||
if (channels?.length && channelMemberships?.length) {
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled);
|
||||
if (channelPromises.length) {
|
||||
promises.push(...channelPromises);
|
||||
@@ -927,7 +1023,7 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
|
||||
}),
|
||||
);
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
if (isCRTEnabled) {
|
||||
promises.push(prepareThreadsFromReceivedPosts(operator, postsArray, false));
|
||||
}
|
||||
@@ -947,17 +1043,24 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
|
||||
posts: postsArray,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchSavedPosts', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPinnedPosts(serverUrl: string, channelId: string) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await client.getPinnedPosts(channelId);
|
||||
const posts = data.posts || {};
|
||||
const order = data.order || [];
|
||||
@@ -971,6 +1074,7 @@ export async function fetchPinnedPosts(serverUrl: string, channelId: string) {
|
||||
}
|
||||
|
||||
const promises: Array<Promise<Model[]>> = [];
|
||||
const {database} = operator;
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
|
||||
const {authors} = await fetchPostAuthors(serverUrl, postsArray, true);
|
||||
@@ -1020,44 +1124,8 @@ export async function fetchPinnedPosts(serverUrl: string, channelId: string) {
|
||||
order,
|
||||
posts: postsArray,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchPinnedPosts', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function acknowledgePost(serverUrl: string, postId: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
EphemeralStore.setAcknowledgingPost(postId);
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
const {acknowledged_at: acknowledgedAt} = await client.acknowledgePost(postId, userId);
|
||||
|
||||
return addPostAcknowledgement(serverUrl, postId, userId, acknowledgedAt, false);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
} finally {
|
||||
EphemeralStore.unsetAcknowledgingPost(postId);
|
||||
}
|
||||
}
|
||||
|
||||
export async function unacknowledgePost(serverUrl: string, postId: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
EphemeralStore.setUnacknowledgingPost(postId);
|
||||
const userId = await getCurrentUserId(database);
|
||||
await client.unacknowledgePost(postId, userId);
|
||||
|
||||
return removePostAcknowledgement(serverUrl, postId, userId, false);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
} finally {
|
||||
EphemeralStore.unsetUnacknowledgingPost(postId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,63 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {chunk} from 'lodash';
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {handleReconnect} from '@actions/websocket';
|
||||
import {Events, General, Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {queryAllUnreadDMsAndGMsIds, getChannelById} from '@queries/servers/channel';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {truncateCrtRelatedTables} from '@queries/servers/entry';
|
||||
import {queryPreferencesByCategoryAndName, querySavedPostsPreferences} from '@queries/servers/preference';
|
||||
import {querySavedPostsPreferences} from '@queries/servers/preference';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
export type MyPreferencesRequest = {
|
||||
preferences?: PreferenceType[];
|
||||
error?: unknown;
|
||||
};
|
||||
|
||||
export const fetchMyPreferences = async (serverUrl: string, fetchOnly = false): Promise<MyPreferencesRequest> => {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const preferences = await client.getMyPreferences();
|
||||
|
||||
if (!fetchOnly) {
|
||||
await operator.handlePreferences({
|
||||
prepareRecordsOnly: false,
|
||||
preferences,
|
||||
sync: true,
|
||||
});
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator) {
|
||||
await operator.handlePreferences({
|
||||
prepareRecordsOnly: false,
|
||||
preferences,
|
||||
sync: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {preferences};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchMyPreferences', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const saveFavoriteChannel = async (serverUrl: string, channelId: string, isFavorite: boolean) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const userId = await getCurrentUserId(database);
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
const favPref: PreferenceType = {
|
||||
category: Preferences.CATEGORIES.FAVORITE_CHANNEL,
|
||||
name: channelId,
|
||||
@@ -68,10 +71,13 @@ export const saveFavoriteChannel = async (serverUrl: string, channelId: string,
|
||||
};
|
||||
|
||||
export const savePostPreference = async (serverUrl: string, postId: string) => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
try {
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
const pref: PreferenceType = {
|
||||
user_id: userId,
|
||||
category: Preferences.CATEGORIES.SAVED_POST,
|
||||
@@ -84,30 +90,30 @@ export const savePostPreference = async (serverUrl: string, postId: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const savePreference = async (serverUrl: string, preferences: PreferenceType[], prepareRecordsOnly = false) => {
|
||||
export const savePreference = async (serverUrl: string, preferences: PreferenceType[]) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
if (!preferences.length) {
|
||||
return {preferences: []};
|
||||
}
|
||||
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
const chunkSize = 100;
|
||||
const chunks = chunk(preferences, chunkSize);
|
||||
chunks.forEach((c: PreferenceType[]) => {
|
||||
client.savePreferences(userId, c);
|
||||
});
|
||||
const preferenceModels = await operator.handlePreferences({
|
||||
preferences,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
|
||||
return {preferences: preferenceModels};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on savePreference', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
client.savePreferences(userId, preferences);
|
||||
await operator.handlePreferences({
|
||||
preferences,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
return {preferences};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -135,79 +141,18 @@ export const deleteSavedPost = async (serverUrl: string, postId: string) => {
|
||||
preference: pref,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on deleteSavedPost', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const openChannelIfNeeded = async (serverUrl: string, channelId: string) => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (!channel || !isDMorGM(channel)) {
|
||||
return {};
|
||||
}
|
||||
const res = await openChannels(serverUrl, [channel]);
|
||||
return res;
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const openAllUnreadChannels = async (serverUrl: string) => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const channels = await queryAllUnreadDMsAndGMsIds(database).fetch();
|
||||
const res = await openChannels(serverUrl, channels);
|
||||
return res;
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
const openChannels = async (serverUrl: string, channels: ChannelModel[]) => {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const userId = await getCurrentUserId(database);
|
||||
|
||||
const {DIRECT_CHANNEL_SHOW, GROUP_CHANNEL_SHOW} = Preferences.CATEGORIES;
|
||||
const directChannelShowPreferences = await queryPreferencesByCategoryAndName(database, DIRECT_CHANNEL_SHOW).fetch();
|
||||
const groupChannelShowPreferences = await queryPreferencesByCategoryAndName(database, GROUP_CHANNEL_SHOW).fetch();
|
||||
const showPreferences = directChannelShowPreferences.concat(groupChannelShowPreferences);
|
||||
|
||||
const prefs: PreferenceType[] = [];
|
||||
for (const channel of channels) {
|
||||
const category = channel.type === General.DM_CHANNEL ? DIRECT_CHANNEL_SHOW : GROUP_CHANNEL_SHOW;
|
||||
const name = channel.type === General.DM_CHANNEL ? getUserIdFromChannelName(userId, channel.name) : channel.id;
|
||||
const visible = getPreferenceAsBool(showPreferences, category, name, false);
|
||||
if (visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
prefs.push(
|
||||
{
|
||||
user_id: userId,
|
||||
category,
|
||||
name,
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
user_id: userId,
|
||||
category: Preferences.CATEGORIES.CHANNEL_OPEN_TIME,
|
||||
name: channel.id,
|
||||
value: Date.now().toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return savePreference(serverUrl, prefs);
|
||||
};
|
||||
|
||||
export const setDirectChannelVisible = async (serverUrl: string, channelId: string, visible = true) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (channel?.type === General.DM_CHANNEL || channel?.type === General.GM_CHANNEL) {
|
||||
const userId = await getCurrentUserId(database);
|
||||
@@ -223,9 +168,9 @@ export const setDirectChannelVisible = async (serverUrl: string, channelId: stri
|
||||
return savePreference(serverUrl, [pref]);
|
||||
}
|
||||
|
||||
return {};
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,51 +8,30 @@ import {getRecentPostsInChannel, getRecentPostsInThread} from '@queries/servers/
|
||||
import {queryReaction} from '@queries/servers/reaction';
|
||||
import {getCurrentChannelId, getCurrentUserId} from '@queries/servers/system';
|
||||
import {getEmojiFirstAlias} from '@utils/emoji/helpers';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
export async function getIsReactionAlreadyAddedToPost(serverUrl: string, postId: string, emojiName: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const emojiAlias = getEmojiFirstAlias(emojiName);
|
||||
return await queryReaction(database, emojiAlias, postId, currentUserId).fetchCount() > 0;
|
||||
} catch (error) {
|
||||
logDebug('error on getIsReactionAlreadyAddedToPost', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function toggleReaction(serverUrl: string, postId: string, emojiName: string) {
|
||||
try {
|
||||
const isReactionAlreadyAddedToPost = await getIsReactionAlreadyAddedToPost(serverUrl, postId, emojiName);
|
||||
|
||||
if (isReactionAlreadyAddedToPost) {
|
||||
return removeReaction(serverUrl, postId, emojiName);
|
||||
}
|
||||
return addReaction(serverUrl, postId, emojiName);
|
||||
} catch (error) {
|
||||
logDebug('error on toggleReaction', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function addReaction(serverUrl: string, postId: string, emojiName: string) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUserId = await getCurrentUserId(operator.database);
|
||||
const emojiAlias = getEmojiFirstAlias(emojiName);
|
||||
const reacted = await queryReaction(database, emojiAlias, postId, currentUserId).fetchCount() > 0;
|
||||
const reacted = await queryReaction(operator.database, emojiAlias, postId, currentUserId).fetchCount() > 0;
|
||||
if (!reacted) {
|
||||
const reaction = await client.addReaction(currentUserId, postId, emojiAlias);
|
||||
const models: Model[] = [];
|
||||
@@ -85,17 +64,25 @@ export async function addReaction(serverUrl: string, postId: string, emojiName:
|
||||
} as Reaction,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on addReaction', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export const removeReaction = async (serverUrl: string, postId: string, emojiName: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const emojiAlias = getEmojiFirstAlias(emojiName);
|
||||
await client.removeReaction(currentUserId, postId, emojiAlias);
|
||||
@@ -111,21 +98,24 @@ export const removeReaction = async (serverUrl: string, postId: string, emojiNam
|
||||
|
||||
return {reaction};
|
||||
} catch (error) {
|
||||
logDebug('error on removeReaction', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const handleReactionToLatestPost = async (serverUrl: string, emojiName: string, add: boolean, rootId?: string) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
let posts: PostModel[];
|
||||
if (rootId) {
|
||||
posts = await getRecentPostsInThread(database, rootId);
|
||||
posts = await getRecentPostsInThread(operator.database, rootId);
|
||||
} else {
|
||||
const channelId = await getCurrentChannelId(database);
|
||||
posts = await getRecentPostsInChannel(database, channelId);
|
||||
const channelId = await getCurrentChannelId(operator.database);
|
||||
posts = await getRecentPostsInChannel(operator.database, channelId);
|
||||
}
|
||||
|
||||
if (add) {
|
||||
|
||||
@@ -15,12 +15,12 @@ import {prepareMyTeams} from '@queries/servers/team';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {isDMorGM, selectDefaultChannelForTeam} from '@utils/channel';
|
||||
|
||||
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, type MyChannelsRequest} from './channel';
|
||||
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, MyChannelsRequest} from './channel';
|
||||
import {fetchPostsForChannel} from './post';
|
||||
import {fetchMyPreferences, type MyPreferencesRequest} from './preference';
|
||||
import {fetchMyPreferences, MyPreferencesRequest} from './preference';
|
||||
import {fetchRolesIfNeeded} from './role';
|
||||
import {type ConfigAndLicenseRequest, fetchConfigAndLicense} from './systems';
|
||||
import {fetchMyTeams, type MyTeamsRequest} from './team';
|
||||
import {ConfigAndLicenseRequest, fetchConfigAndLicense} from './systems';
|
||||
import {fetchMyTeams, MyTeamsRequest} from './team';
|
||||
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
@@ -30,6 +30,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) {
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
try {
|
||||
@@ -138,6 +139,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) {
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {General} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {queryRoles} from '@queries/servers/role';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
@@ -20,36 +17,44 @@ export const fetchRolesIfNeeded = async (serverUrl: string, updatedRoles: string
|
||||
return {roles: []};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
let newRoles;
|
||||
if (force) {
|
||||
newRoles = updatedRoles;
|
||||
} else {
|
||||
const existingRoles = await queryRoles(database).fetch();
|
||||
let database;
|
||||
let operator;
|
||||
try {
|
||||
const result = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
database = result.database;
|
||||
operator = result.operator;
|
||||
} catch (e) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const roleNames = new Set(existingRoles.map((role) => {
|
||||
return role.name;
|
||||
}));
|
||||
let newRoles;
|
||||
if (force) {
|
||||
newRoles = updatedRoles;
|
||||
} else {
|
||||
const existingRoles = await queryRoles(database).fetch();
|
||||
|
||||
newRoles = updatedRoles.filter((newRole) => {
|
||||
return !roleNames.has(newRole);
|
||||
});
|
||||
}
|
||||
const roleNames = new Set(existingRoles.map((role) => {
|
||||
return role.name;
|
||||
}));
|
||||
|
||||
if (!newRoles.length) {
|
||||
return {roles: []};
|
||||
}
|
||||
newRoles = updatedRoles.filter((newRole) => {
|
||||
return !roleNames.has(newRole);
|
||||
});
|
||||
}
|
||||
|
||||
const getRolesRequests = [];
|
||||
for (let i = 0; i < newRoles.length; i += General.MAX_GET_ROLES_BY_NAMES) {
|
||||
const chunk = newRoles.slice(i, i + General.MAX_GET_ROLES_BY_NAMES);
|
||||
getRolesRequests.push(client.getRolesByNames(chunk));
|
||||
}
|
||||
if (!newRoles.length) {
|
||||
return {roles: []};
|
||||
}
|
||||
|
||||
const roles = (await Promise.all(getRolesRequests)).flat();
|
||||
try {
|
||||
const roles = await client.getRolesByNames(newRoles);
|
||||
if (!fetchOnly) {
|
||||
await operator.handleRole({
|
||||
roles,
|
||||
@@ -59,8 +64,7 @@ export const fetchRolesIfNeeded = async (serverUrl: string, updatedRoles: string
|
||||
|
||||
return {roles};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchRolesIfNeeded', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getPosts} from '@actions/local/post';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {prepareMissingChannelsForAllTeams} from '@queries/servers/channel';
|
||||
import {getConfigValue} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {getUtcOffsetForTimeZone} from '@utils/helpers';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {getUserTimezone} from '@utils/user';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
import {fetchPostAuthors, fetchMissingChannelsFromPosts} from './post';
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type Model from '@nozbe/watermelondb/Model';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
export async function fetchRecentMentions(serverUrl: string): Promise<PostSearchRequest> {
|
||||
try {
|
||||
@@ -54,18 +48,11 @@ export async function fetchRecentMentions(serverUrl: string): Promise<PostSearch
|
||||
|
||||
export const searchPosts = async (serverUrl: string, teamId: string, params: PostSearchParams): Promise<PostSearchRequest> => {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const viewArchivedChannels = await getConfigValue(database, 'ExperimentalViewArchivedChannels');
|
||||
const user = await getCurrentUser(database);
|
||||
const timezoneOffset = getUtcOffsetForTimeZone(getUserTimezone(user)) * 60;
|
||||
|
||||
let postsArray: Post[] = [];
|
||||
const data = await client.searchPostsWithParams(teamId, {
|
||||
...params,
|
||||
include_deleted_channels: Boolean(viewArchivedChannels),
|
||||
time_zone_offset: timezoneOffset,
|
||||
});
|
||||
const data = await client.searchPosts(teamId, params.terms, params.is_or_search);
|
||||
|
||||
const posts = data.posts || {};
|
||||
const order = data.order || [];
|
||||
@@ -73,7 +60,7 @@ export const searchPosts = async (serverUrl: string, teamId: string, params: Pos
|
||||
const promises: Array<Promise<Model[]>> = [];
|
||||
postsArray = order.map((id) => posts[id]);
|
||||
if (postsArray.length) {
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
if (isCRTEnabled) {
|
||||
promises.push(prepareThreadsFromReceivedPosts(operator, postsArray, false));
|
||||
}
|
||||
@@ -120,11 +107,10 @@ export const searchPosts = async (serverUrl: string, teamId: string, params: Pos
|
||||
return {
|
||||
order,
|
||||
posts: postsArray,
|
||||
matches: data.matches,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on searchPosts', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
logError('Failed: searchPosts', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -134,31 +120,16 @@ export const searchFiles = async (serverUrl: string, teamId: string, params: Fil
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const result = await client.searchFiles(teamId, params.terms);
|
||||
const files = result?.file_infos ? Object.values(result.file_infos) : [];
|
||||
const [allChannelIds, allPostIds] = files.reduce<[Set<string>, Set<string>]>((acc, f) => {
|
||||
const allChannelIds = files.reduce<string[]>((acc, f) => {
|
||||
if (f.channel_id) {
|
||||
acc[0].add(f.channel_id);
|
||||
}
|
||||
if (f.post_id) {
|
||||
acc[1].add(f.post_id);
|
||||
acc.push(f.channel_id);
|
||||
}
|
||||
return acc;
|
||||
}, [new Set<string>(), new Set<string>()]);
|
||||
const channels = Array.from(allChannelIds.values());
|
||||
|
||||
// Attach the file's post's props to the FileInfo (needed for captioning videos)
|
||||
const postIds = Array.from(allPostIds);
|
||||
const posts = await getPosts(serverUrl, postIds);
|
||||
const idToPost = posts.reduce<Dictionary<PostModel>>((acc, p) => {
|
||||
acc[p.id] = p;
|
||||
return acc;
|
||||
}, {});
|
||||
files.forEach((f) => {
|
||||
f.postProps = idToPost[f.post_id]?.props;
|
||||
});
|
||||
}, []);
|
||||
const channels = [...new Set(allChannelIds)];
|
||||
return {files, channels};
|
||||
} catch (error) {
|
||||
logDebug('error on searchFiles', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,13 +14,13 @@ import {getServerDisplayName} from '@queries/app/servers';
|
||||
import {getCurrentUserId, getExpiredSession} from '@queries/servers/system';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {getFullErrorMessage, isErrorWithStatusCode, isErrorWithUrl} from '@utils/errors';
|
||||
import {logWarning, logError, logDebug} from '@utils/log';
|
||||
import {scheduleExpiredNotification} from '@utils/notification';
|
||||
import {getCSRFFromCookie} from '@utils/security';
|
||||
|
||||
import {loginEntry} from './entry';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {LoginArgs} from '@typings/database/database';
|
||||
|
||||
const HTTP_UNAUTHORIZED = 401;
|
||||
@@ -44,7 +44,7 @@ export const addPushProxyVerificationStateFromLogin = async (serverUrl: string)
|
||||
logDebug('error setting the push proxy verification state on login', error);
|
||||
}
|
||||
};
|
||||
export const forceLogoutIfNecessary = async (serverUrl: string, err: unknown) => {
|
||||
export const forceLogoutIfNecessary = async (serverUrl: string, err: ClientErrorProps) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -52,7 +52,7 @@ export const forceLogoutIfNecessary = async (serverUrl: string, err: unknown) =>
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
|
||||
if (isErrorWithStatusCode(err) && err.status_code === HTTP_UNAUTHORIZED && isErrorWithUrl(err) && err.url?.indexOf('/login') === -1 && currentUserId) {
|
||||
if ('status_code' in err && err.status_code === HTTP_UNAUTHORIZED && err?.url?.indexOf('/login') === -1 && currentUserId) {
|
||||
await logout(serverUrl);
|
||||
}
|
||||
|
||||
@@ -69,9 +69,9 @@ export const fetchSessions = async (serverUrl: string, currentUserId: string) =>
|
||||
|
||||
try {
|
||||
return await client.getSessions(currentUserId);
|
||||
} catch (error) {
|
||||
logDebug('error on fetchSessions', getFullErrorMessage(error));
|
||||
await forceLogoutIfNecessary(serverUrl, error);
|
||||
} catch (e) {
|
||||
logError('fetchSessions', e);
|
||||
await forceLogoutIfNecessary(serverUrl, e as ClientError);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -86,8 +86,14 @@ export const login = async (serverUrl: string, {ldapOnly = false, loginId, mfaTo
|
||||
return {error: 'App database not found.', failed: true};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: error as Error, failed: true};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
deviceToken = await getDeviceToken();
|
||||
user = await client.login(
|
||||
loginId,
|
||||
@@ -117,17 +123,16 @@ export const login = async (serverUrl: string, {ldapOnly = false, loginId, mfaTo
|
||||
const csrfToken = await getCSRFFromCookie(serverUrl);
|
||||
client.setCSRFToken(csrfToken);
|
||||
} catch (error) {
|
||||
logDebug('error on login', getFullErrorMessage(error));
|
||||
return {error, failed: true};
|
||||
return {error: error as Error, failed: true};
|
||||
}
|
||||
|
||||
try {
|
||||
await addPushProxyVerificationStateFromLogin(serverUrl);
|
||||
const {error} = await loginEntry({serverUrl});
|
||||
await DatabaseManager.setActiveServerDatabase(serverUrl);
|
||||
return {error, failed: false};
|
||||
return {error: error as ClientError, failed: false};
|
||||
} catch (error) {
|
||||
return {error, failed: false};
|
||||
return {error: error as ClientError, failed: false};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -138,7 +143,7 @@ export const logout = async (serverUrl: string, skipServerLogout = false, remove
|
||||
await client.logout();
|
||||
} catch (error) {
|
||||
// We want to log the user even if logging out from the server failed
|
||||
logWarning('An error occurred logging out from the server', serverUrl, getFullErrorMessage(error));
|
||||
logWarning('An error occurred logging out from the server', serverUrl, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,34 +203,50 @@ export const scheduleSessionNotification = async (serverUrl: string) => {
|
||||
}
|
||||
} catch (e) {
|
||||
logError('scheduleExpiredNotification', e);
|
||||
await forceLogoutIfNecessary(serverUrl, e);
|
||||
await forceLogoutIfNecessary(serverUrl, e as ClientError);
|
||||
}
|
||||
};
|
||||
|
||||
export const sendPasswordResetEmail = async (serverUrl: string, email: string) => {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const response = await client.sendPasswordResetEmail(email);
|
||||
return {status: response.status};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on sendPasswordResetEmail', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await client.sendPasswordResetEmail(email);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
error: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const ssoLogin = async (serverUrl: string, serverDisplayName: string, serverIdentifier: string, bearerToken: string, csrfToken: string): Promise<LoginActionResponse> => {
|
||||
let user;
|
||||
|
||||
const database = DatabaseManager.appDatabase?.database;
|
||||
if (!database) {
|
||||
return {error: 'App database not found', failed: true};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error: error as Error, failed: true};
|
||||
}
|
||||
|
||||
client.setBearerToken(bearerToken);
|
||||
client.setCSRFToken(csrfToken);
|
||||
client.setBearerToken(bearerToken);
|
||||
client.setCSRFToken(csrfToken);
|
||||
|
||||
// Setting up active database for this SSO login flow
|
||||
// Setting up active database for this SSO login flow
|
||||
try {
|
||||
const server = await DatabaseManager.createServerDatabase({
|
||||
config: {
|
||||
dbName: serverUrl,
|
||||
@@ -234,7 +255,7 @@ export const ssoLogin = async (serverUrl: string, serverDisplayName: string, ser
|
||||
displayName: serverDisplayName,
|
||||
},
|
||||
});
|
||||
const user = await client.getMe();
|
||||
user = await client.getMe();
|
||||
await server?.operator.handleUsers({users: [user], prepareRecordsOnly: false});
|
||||
await server?.operator.handleSystem({
|
||||
systems: [{
|
||||
@@ -243,18 +264,17 @@ export const ssoLogin = async (serverUrl: string, serverDisplayName: string, ser
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
} catch (error) {
|
||||
logDebug('error on ssoLogin', getFullErrorMessage(error));
|
||||
return {error, failed: true};
|
||||
} catch (e) {
|
||||
return {error: e as ClientError, failed: true};
|
||||
}
|
||||
|
||||
try {
|
||||
await addPushProxyVerificationStateFromLogin(serverUrl);
|
||||
const {error} = await loginEntry({serverUrl});
|
||||
await DatabaseManager.setActiveServerDatabase(serverUrl);
|
||||
return {error, failed: false};
|
||||
return {error: error as ClientError, failed: false};
|
||||
} catch (error) {
|
||||
return {error, failed: false};
|
||||
return {error: error as ClientError, failed: false};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import {forceLogoutIfNecessary} from '@actions/remote/session';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
|
||||
export type ConfigAndLicenseRequest = {
|
||||
config?: ClientConfig;
|
||||
@@ -23,36 +23,51 @@ export type DataRetentionPoliciesRequest = {
|
||||
}
|
||||
|
||||
export const fetchDataRetentionPolicy = async (serverUrl: string, fetchOnly = false): Promise<DataRetentionPoliciesRequest> => {
|
||||
const {data: globalPolicy, error: globalPolicyError} = await fetchGlobalDataRetentionPolicy(serverUrl);
|
||||
const {data: teamPolicies, error: teamPoliciesError} = await fetchAllGranularDataRetentionPolicies(serverUrl);
|
||||
const {data: channelPolicies, error: channelPoliciesError} = await fetchAllGranularDataRetentionPolicies(serverUrl, true);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const error = globalPolicyError || teamPoliciesError || channelPoliciesError;
|
||||
if (error) {
|
||||
try {
|
||||
const {data: globalPolicy, error: globalPolicyError} = await fetchGlobalDataRetentionPolicy(serverUrl);
|
||||
const {data: teamPolicies, error: teamPoliciesError} = await fetchAllGranularDataRetentionPolicies(serverUrl);
|
||||
const {data: channelPolicies, error: channelPoliciesError} = await fetchAllGranularDataRetentionPolicies(serverUrl, true);
|
||||
|
||||
const hasError = globalPolicyError || teamPoliciesError || channelPoliciesError;
|
||||
if (hasError) {
|
||||
return hasError;
|
||||
}
|
||||
|
||||
const data = {
|
||||
globalPolicy,
|
||||
teamPolicies: teamPolicies as TeamDataRetentionPolicy[],
|
||||
channelPolicies: channelPolicies as ChannelDataRetentionPolicy[],
|
||||
};
|
||||
|
||||
if (!fetchOnly) {
|
||||
await storeDataRetentionPolicies(serverUrl, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
|
||||
const data = {
|
||||
globalPolicy,
|
||||
teamPolicies: teamPolicies as TeamDataRetentionPolicy[],
|
||||
channelPolicies: channelPolicies as ChannelDataRetentionPolicy[],
|
||||
};
|
||||
|
||||
if (!fetchOnly) {
|
||||
await storeDataRetentionPolicies(serverUrl, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchGlobalDataRetentionPolicy = async (serverUrl: string): Promise<{data?: GlobalDataRetentionPolicy; error?: unknown}> => {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const data = await client.getGlobalDataRetentionPolicy();
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchGlobalDataRetentionPolicy', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -63,31 +78,43 @@ export const fetchAllGranularDataRetentionPolicies = async (
|
||||
page = 0,
|
||||
policies: Array<TeamDataRetentionPolicy | ChannelDataRetentionPolicy> = [],
|
||||
): Promise<{data?: Array<TeamDataRetentionPolicy | ChannelDataRetentionPolicy>; error?: unknown}> => {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
let data;
|
||||
if (isChannel) {
|
||||
data = await client.getChannelDataRetentionPolicies(currentUserId, page);
|
||||
} else {
|
||||
data = await client.getTeamDataRetentionPolicies(currentUserId, page);
|
||||
}
|
||||
policies.push(...data.policies);
|
||||
if (policies.length < data.total_count) {
|
||||
await fetchAllGranularDataRetentionPolicies(serverUrl, isChannel, page + 1, policies);
|
||||
}
|
||||
return {data: policies};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on fetchAllGranularDataRetentionPolicies', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
let data;
|
||||
if (isChannel) {
|
||||
data = await client.getChannelDataRetentionPolicies(currentUserId, page);
|
||||
} else {
|
||||
data = await client.getTeamDataRetentionPolicies(currentUserId, page);
|
||||
}
|
||||
policies.push(...data.policies);
|
||||
if (policies.length < data.total_count) {
|
||||
await fetchAllGranularDataRetentionPolicies(serverUrl, isChannel, page + 1, policies);
|
||||
}
|
||||
return {data: policies};
|
||||
};
|
||||
|
||||
export const fetchConfigAndLicense = async (serverUrl: string, fetchOnly = false): Promise<ConfigAndLicenseRequest> => {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const [config, license]: [ClientConfig, ClientLicense] = await Promise.all([
|
||||
client.getClientConfigOld(),
|
||||
client.getClientLicenseOld(),
|
||||
@@ -99,8 +126,7 @@ export const fetchConfigAndLicense = async (serverUrl: string, fetchOnly = false
|
||||
|
||||
return {config, license};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchConfigAndLicense', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,10 +13,9 @@ import {prepareCategoriesAndCategoriesChannels} from '@queries/servers/categorie
|
||||
import {prepareMyChannelsForTeam, getDefaultChannelForTeam} from '@queries/servers/channel';
|
||||
import {prepareCommonSystemValues, getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
|
||||
import {addTeamToTeamHistory, prepareDeleteTeam, prepareMyTeams, getNthLastChannelFromTeam, queryTeamsById, getLastTeam, getTeamById, removeTeamFromTeamHistory, queryMyTeams} from '@queries/servers/team';
|
||||
import {dismissAllModalsAndPopToRoot} from '@screens/navigation';
|
||||
import {dismissAllModals, popToRoot} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {setTeamLoading} from '@store/team_load_store';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
@@ -27,6 +26,7 @@ import {fetchRolesIfNeeded} from './role';
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
|
||||
export type MyTeamsRequest = {
|
||||
@@ -52,11 +52,15 @@ export async function addCurrentUserToTeam(serverUrl: string, teamId: string, fe
|
||||
}
|
||||
|
||||
export async function addUserToTeam(serverUrl: string, teamId: string, userId: string, fetchOnly = false) {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
let loadEventSent = false;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
EphemeralStore.startAddingToTeam(teamId);
|
||||
const team = await client.getTeam(teamId);
|
||||
const member = await client.addToTeam(teamId, userId);
|
||||
@@ -67,40 +71,45 @@ export async function addUserToTeam(serverUrl: string, teamId: string, userId: s
|
||||
|
||||
fetchRolesIfNeeded(serverUrl, member.roles.split(' '));
|
||||
const {channels, memberships: channelMembers, categories} = await fetchMyChannelsForTeam(serverUrl, teamId, false, 0, true);
|
||||
const myTeams: MyTeam[] = [{
|
||||
id: member.team_id,
|
||||
roles: member.roles,
|
||||
}];
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator) {
|
||||
const myTeams: MyTeam[] = [{
|
||||
id: member.team_id,
|
||||
roles: member.roles,
|
||||
}];
|
||||
|
||||
const models: Model[] = (await Promise.all([
|
||||
operator.handleTeam({teams: [team], prepareRecordsOnly: true}),
|
||||
operator.handleMyTeam({myTeams, prepareRecordsOnly: true}),
|
||||
operator.handleTeamMemberships({teamMemberships: [member], prepareRecordsOnly: true}),
|
||||
...await prepareMyChannelsForTeam(operator, teamId, channels || [], channelMembers || []),
|
||||
prepareCategoriesAndCategoriesChannels(operator, categories || [], true),
|
||||
])).flat();
|
||||
const models: Model[] = (await Promise.all([
|
||||
operator.handleTeam({teams: [team], prepareRecordsOnly: true}),
|
||||
operator.handleMyTeam({myTeams, prepareRecordsOnly: true}),
|
||||
operator.handleTeamMemberships({teamMemberships: [member], prepareRecordsOnly: true}),
|
||||
...await prepareMyChannelsForTeam(operator, teamId, channels || [], channelMembers || []),
|
||||
prepareCategoriesAndCategoriesChannels(operator, categories || [], true),
|
||||
])).flat();
|
||||
|
||||
await operator.batchRecords(models, 'addUserToTeam');
|
||||
setTeamLoading(serverUrl, false);
|
||||
loadEventSent = false;
|
||||
await operator.batchRecords(models, 'addUserToTeam');
|
||||
setTeamLoading(serverUrl, false);
|
||||
loadEventSent = false;
|
||||
|
||||
if (isTablet()) {
|
||||
const channel = await getDefaultChannelForTeam(database, teamId);
|
||||
if (channel) {
|
||||
fetchPostsForChannel(serverUrl, channel.id);
|
||||
if (await isTablet()) {
|
||||
const channel = await getDefaultChannelForTeam(operator.database, teamId);
|
||||
if (channel) {
|
||||
fetchPostsForChannel(serverUrl, channel.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setTeamLoading(serverUrl, false);
|
||||
loadEventSent = false;
|
||||
}
|
||||
}
|
||||
EphemeralStore.finishAddingToTeam(teamId);
|
||||
updateCanJoinTeams(serverUrl);
|
||||
return {member};
|
||||
} catch (error) {
|
||||
logDebug('error on addUserToTeam', getFullErrorMessage(error));
|
||||
if (loadEventSent) {
|
||||
setTeamLoading(serverUrl, false);
|
||||
}
|
||||
EphemeralStore.finishAddingToTeam(teamId);
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
@@ -132,12 +141,11 @@ export async function addUsersToTeam(serverUrl: string, teamId: string, userIds:
|
||||
EphemeralStore.finishAddingToTeam(teamId);
|
||||
return {members};
|
||||
} catch (error) {
|
||||
logDebug('error on addUsersToTeam', getFullErrorMessage(error));
|
||||
if (EphemeralStore.isAddingToTeam(teamId)) {
|
||||
EphemeralStore.finishAddingToTeam(teamId);
|
||||
}
|
||||
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
@@ -149,23 +157,27 @@ export async function sendEmailInvitesToTeam(serverUrl: string, teamId: string,
|
||||
|
||||
return {members};
|
||||
} catch (error) {
|
||||
logDebug('error on sendEmailInvitesToTeam', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMyTeams(serverUrl: string, fetchOnly = false): Promise<MyTeamsRequest> {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const [teams, memberships]: [Team[], TeamMembership[]] = await Promise.all([
|
||||
client.getMyTeams(),
|
||||
client.getMyTeamMembers(),
|
||||
]);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
if (operator) {
|
||||
const removeTeamIds = new Set(memberships.filter((m) => m.delete_at > 0).map((m) => m.team_id));
|
||||
@@ -177,7 +189,7 @@ export async function fetchMyTeams(serverUrl: string, fetchOnly = false): Promis
|
||||
|
||||
if (removeTeamIds.size) {
|
||||
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
|
||||
const removeTeams = await queryTeamsById(database, Array.from(removeTeamIds)).fetch();
|
||||
const removeTeams = await queryTeamsById(operator.database, Array.from(removeTeamIds)).fetch();
|
||||
removeTeams.forEach((team) => {
|
||||
modelPromises.push(prepareDeleteTeam(team));
|
||||
});
|
||||
@@ -195,36 +207,41 @@ export async function fetchMyTeams(serverUrl: string, fetchOnly = false): Promis
|
||||
|
||||
return {teams, memberships};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchMyTeams', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMyTeam(serverUrl: string, teamId: string, fetchOnly = false): Promise<MyTeamsRequest> {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const [team, membership] = await Promise.all([
|
||||
client.getTeam(teamId),
|
||||
client.getTeamMember(teamId, 'me'),
|
||||
]);
|
||||
if (!fetchOnly) {
|
||||
const modelPromises = prepareMyTeams(operator, [team], [membership]);
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat();
|
||||
if (flattenedModels?.length > 0) {
|
||||
await operator.batchRecords(flattenedModels, 'fetchMyTeam');
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator) {
|
||||
const modelPromises = prepareMyTeams(operator, [team], [membership]);
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat();
|
||||
if (flattenedModels?.length > 0) {
|
||||
await operator.batchRecords(flattenedModels, 'fetchMyTeam');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {teams: [team], memberships: [membership]};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchMyTeam', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
@@ -235,8 +252,7 @@ export const fetchAllTeams = async (serverUrl: string, page = 0, perPage = PER_P
|
||||
const teams = await client.getTeams(page, perPage);
|
||||
return {teams};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchAllTeams', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -305,9 +321,8 @@ export const updateCanJoinTeams = async (serverUrl: string) => {
|
||||
EphemeralStore.setCanJoinOtherTeams(serverUrl, canJoin);
|
||||
return {};
|
||||
} catch (error) {
|
||||
logDebug('error on updateCanJoinTeams', getFullErrorMessage(error));
|
||||
EphemeralStore.setCanJoinOtherTeams(serverUrl, false);
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -329,25 +344,31 @@ export const fetchTeamsChannelsAndUnreadPosts = async (serverUrl: string, since:
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export async function fetchTeamByName(serverUrl: string, teamName: string, fetchOnly = false) {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const team = await client.getTeamByName(teamName);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const models = await operator.handleTeam({teams: [team], prepareRecordsOnly: true});
|
||||
await operator.batchRecords(models, 'fetchTeamByName');
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator) {
|
||||
const models = await operator.handleTeam({teams: [team], prepareRecordsOnly: true});
|
||||
await operator.batchRecords(models, 'fetchTeamByName');
|
||||
}
|
||||
}
|
||||
|
||||
return {team};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchTeamByName', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
@@ -363,8 +384,14 @@ export const removeCurrentUserFromTeam = async (serverUrl: string, teamId: strin
|
||||
};
|
||||
|
||||
export const removeUserFromTeam = async (serverUrl: string, teamId: string, userId: string, fetchOnly = false) => {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.removeFromTeam(teamId, userId);
|
||||
|
||||
if (!fetchOnly) {
|
||||
@@ -372,10 +399,9 @@ export const removeUserFromTeam = async (serverUrl: string, teamId: string, user
|
||||
updateCanJoinTeams(serverUrl);
|
||||
}
|
||||
|
||||
return {};
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
logDebug('error on removeUserFromTeam', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -385,8 +411,8 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
const {database} = operator;
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
|
||||
if (currentTeamId === teamId) {
|
||||
@@ -395,7 +421,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
|
||||
|
||||
let channelId = '';
|
||||
DeviceEventEmitter.emit(Events.TEAM_SWITCH, true);
|
||||
if (isTablet()) {
|
||||
if (await isTablet()) {
|
||||
channelId = await getNthLastChannelFromTeam(database, teamId);
|
||||
if (channelId) {
|
||||
await switchToChannelById(serverUrl, channelId, teamId);
|
||||
@@ -435,7 +461,8 @@ export async function handleKickFromTeam(serverUrl: string, teamId: string) {
|
||||
if (currentServer === serverUrl) {
|
||||
const team = await getTeamById(database, teamId);
|
||||
DeviceEventEmitter.emit(Events.LEAVE_TEAM, team?.displayName);
|
||||
await dismissAllModalsAndPopToRoot();
|
||||
await dismissAllModals();
|
||||
await popToRoot();
|
||||
}
|
||||
|
||||
await removeTeamFromTeamHistory(operator, teamId);
|
||||
@@ -470,8 +497,7 @@ export async function getTeamMembersByIds(serverUrl: string, teamId: string, use
|
||||
|
||||
return {members};
|
||||
} catch (error) {
|
||||
logDebug('error on getTeamMembersByIds', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,24 @@
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
|
||||
export async function fetchTermsOfService(serverUrl: string): Promise<{terms?: TermsOfService; error?: any}> {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const terms = await client.getTermsOfService();
|
||||
return {terms};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchTermsOfService', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
@@ -42,8 +47,7 @@ export async function updateTermsOfServiceStatus(serverUrl: string, id: string,
|
||||
}
|
||||
return {resp};
|
||||
} catch (error) {
|
||||
logDebug('error on updateTermsOfServiceStatus', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ import {getPostById} from '@queries/servers/post';
|
||||
import {getConfigValue, getCurrentChannelId, getCurrentTeamId} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled, getThreadById, getTeamThreadsSyncData} from '@queries/servers/thread';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {showThreadFollowingSnackbar} from '@utils/snack_bar';
|
||||
import {getThreadsListEdges} from '@utils/thread';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
@@ -78,23 +75,34 @@ export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string,
|
||||
};
|
||||
|
||||
export const fetchThread = async (serverUrl: string, teamId: string, threadId: string, extended?: boolean) => {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const thread = await client.getThread('me', teamId, threadId, extended);
|
||||
|
||||
await processReceivedThreads(serverUrl, [thread], teamId);
|
||||
|
||||
return {data: thread};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchThread', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const updateTeamThreadsAsRead = async (serverUrl: string, teamId: string) => {
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const data = await client.updateTeamThreadsAsRead('me', teamId);
|
||||
|
||||
// Update locally
|
||||
@@ -102,17 +110,26 @@ export const updateTeamThreadsAsRead = async (serverUrl: string, teamId: string)
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on updateTeamThreadsAsRead', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const markThreadAsRead = async (serverUrl: string, teamId: string | undefined, threadId: string, updateLastViewed = true) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// DM/GM doesn't have a teamId, so we pass the current team id
|
||||
@@ -141,17 +158,26 @@ export const markThreadAsRead = async (serverUrl: string, teamId: string | undef
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on markThreadAsRead', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const markThreadAsUnread = async (serverUrl: string, teamId: string, threadId: string, postId: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
// DM/GM doesn't have a teamId, so we pass the current team id
|
||||
let threadTeamId = teamId;
|
||||
if (!threadTeamId) {
|
||||
@@ -171,37 +197,40 @@ export const markThreadAsUnread = async (serverUrl: string, teamId: string, thre
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on markThreadAsUnread', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const updateThreadFollowing = async (serverUrl: string, teamId: string, threadId: string, state: boolean, showSnackBar: boolean) => {
|
||||
export const updateThreadFollowing = async (serverUrl: string, teamId: string, threadId: string, state: boolean) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
// DM/GM doesn't have a teamId, so we pass the current team id
|
||||
let threadTeamId = teamId;
|
||||
if (!threadTeamId) {
|
||||
threadTeamId = await getCurrentTeamId(database);
|
||||
}
|
||||
// DM/GM doesn't have a teamId, so we pass the current team id
|
||||
let threadTeamId = teamId;
|
||||
if (!threadTeamId) {
|
||||
threadTeamId = await getCurrentTeamId(database);
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await client.updateThreadFollow('me', threadTeamId, threadId, state);
|
||||
|
||||
// Update locally
|
||||
await updateThread(serverUrl, threadId, {is_following: state});
|
||||
|
||||
if (showSnackBar) {
|
||||
const onUndo = () => updateThreadFollowing(serverUrl, teamId, threadId, !state, false);
|
||||
showThreadFollowingSnackbar(state, onUndo);
|
||||
}
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on updateThreadFollowing', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -214,10 +243,10 @@ export const fetchThreads = async (
|
||||
pages?: number,
|
||||
) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
@@ -228,12 +257,12 @@ export const fetchThreads = async (
|
||||
|
||||
const fetchDirection = direction ?? Direction.Up;
|
||||
|
||||
const currentUser = await getCurrentUser(database);
|
||||
const currentUser = await getCurrentUser(operator.database);
|
||||
if (!currentUser) {
|
||||
return {error: 'currentUser not found'};
|
||||
}
|
||||
|
||||
const version = await getConfigValue(database, 'Version');
|
||||
const version = await getConfigValue(operator.database, 'Version');
|
||||
const threadsData: Thread[] = [];
|
||||
|
||||
let currentPage = 0;
|
||||
@@ -267,7 +296,6 @@ export const fetchThreads = async (
|
||||
try {
|
||||
await fetchThreadsFunc(options);
|
||||
} catch (error) {
|
||||
logDebug('error on fetchThreads', getFullErrorMessage(error));
|
||||
if (__DEV__) {
|
||||
throw error;
|
||||
}
|
||||
@@ -278,9 +306,13 @@ export const fetchThreads = async (
|
||||
};
|
||||
|
||||
export const syncTeamThreads = async (serverUrl: string, teamId: string, prepareRecordsOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const syncData = await getTeamThreadsSyncData(database, teamId);
|
||||
const syncData = await getTeamThreadsSyncData(operator.database, teamId);
|
||||
const syncDataUpdate = {
|
||||
id: teamId,
|
||||
} as TeamThreadsSync;
|
||||
@@ -380,9 +412,12 @@ export const syncTeamThreads = async (serverUrl: string, teamId: string, prepare
|
||||
};
|
||||
|
||||
export const loadEarlierThreads = async (serverUrl: string, teamId: string, lastThreadId: string, prepareRecordsOnly = false) => {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* - We will fetch one page of old threads
|
||||
* - Update the sync data with the earliest thread last_reply_at timestamp
|
||||
@@ -435,7 +470,7 @@ export const loadEarlierThreads = async (serverUrl: string, teamId: string, last
|
||||
}
|
||||
}
|
||||
|
||||
return {models, threads};
|
||||
return {error: false, models, threads};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
@@ -14,16 +14,17 @@ import {debounce} from '@helpers/api/general';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getMembersCountByChannelsId, queryChannelsByTypes} from '@queries/servers/channel';
|
||||
import {queryGroupsByNames} from '@queries/servers/group';
|
||||
import {getCurrentUserId, setCurrentUserId} from '@queries/servers/system';
|
||||
import {getConfig, getCurrentUserId} from '@queries/servers/system';
|
||||
import {getCurrentUser, prepareUsers, queryAllUsers, queryUsersById, queryUsersByIdsOrUsernames, queryUsersByUsername} from '@queries/servers/user';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {getDeviceTimezone} from '@utils/timezone';
|
||||
import {logError} from '@utils/log';
|
||||
import {getDeviceTimezone, isTimezoneEnabled} from '@utils/timezone';
|
||||
import {getUserTimezoneProps, removeUserFromList} from '@utils/user';
|
||||
|
||||
import {fetchGroupsByNames} from './groups';
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
@@ -44,68 +45,50 @@ export type ProfilesInChannelRequest = {
|
||||
}
|
||||
|
||||
export const fetchMe = async (serverUrl: string, fetchOnly = false): Promise<MyUserRequest> => {
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const resultSettled = await Promise.allSettled([client.getMe(), client.getStatus('me')]);
|
||||
let user: UserProfile|undefined;
|
||||
let userStatus: UserStatus|undefined;
|
||||
for (const result of resultSettled) {
|
||||
if (result.status === 'fulfilled') {
|
||||
const {value} = result;
|
||||
if ('email' in value) {
|
||||
user = value;
|
||||
} else {
|
||||
userStatus = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
const [user, userStatus] = await Promise.all<[Promise<UserProfile>, Promise<UserStatus>]>([
|
||||
client.getMe(),
|
||||
client.getStatus('me'),
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
user.status = userStatus?.status;
|
||||
user.status = userStatus.status;
|
||||
|
||||
if (!fetchOnly) {
|
||||
await operator.handleUsers({users: [user], prepareRecordsOnly: false});
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator) {
|
||||
await operator.handleUsers({users: [user], prepareRecordsOnly: false});
|
||||
}
|
||||
}
|
||||
|
||||
return {user};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchMe', getFullErrorMessage(error));
|
||||
await forceLogoutIfNecessary(serverUrl, error);
|
||||
await forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const refetchCurrentUser = async (serverUrl: string, currentUserId: string | undefined) => {
|
||||
logDebug('re-fetching self');
|
||||
const {user} = await fetchMe(serverUrl);
|
||||
if (!user || currentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
logDebug('missing currentUserId');
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
logDebug('missing operator');
|
||||
return;
|
||||
}
|
||||
setCurrentUserId(operator, user.id);
|
||||
};
|
||||
|
||||
export async function fetchProfilesInChannel(serverUrl: string, channelId: string, excludeUserId?: string, options?: GetUsersOptions, fetchOnly = false): Promise<ProfilesInChannelRequest> {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {channelId, error};
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await client.getProfilesInChannel(channelId, options);
|
||||
const uniqueUsers = Array.from(new Set(users));
|
||||
const filteredUsers = uniqueUsers.filter((u) => u.id !== excludeUserId);
|
||||
if (!fetchOnly) {
|
||||
if (filteredUsers.length) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator && filteredUsers.length) {
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
const membership = filteredUsers.map((u) => ({
|
||||
channel_id: channelId,
|
||||
@@ -125,17 +108,28 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin
|
||||
|
||||
return {channelId, users: filteredUsers};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchProfilesInChannel', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
logError('fetchProfilesInChannel', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {channelId, error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchProfilesInGroupChannels(serverUrl: string, groupChannelIds: string[], fetchOnly = false): Promise<ProfilesPerChannelRequest> {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
// let's filter those channels that we already have the users
|
||||
const membersCount = await getMembersCountByChannelsId(database, groupChannelIds);
|
||||
const channelsToFetch = groupChannelIds.filter((c) => membersCount[c] <= 1);
|
||||
@@ -189,14 +183,16 @@ export async function fetchProfilesInGroupChannels(serverUrl: string, groupChann
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchProfilesInGroupChannels', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchProfilesPerChannels(serverUrl: string, channelIds: string[], excludeUserId?: string, fetchOnly = false): Promise<ProfilesPerChannelRequest> {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
// Batch fetching profiles per channel by chunks of 250
|
||||
const channels = chunk(channelIds, 250);
|
||||
@@ -239,31 +235,39 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st
|
||||
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchProfilesPerChannels', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export const updateMe = async (serverUrl: string, user: Partial<UserProfile>) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const data = await client.patchMe(user);
|
||||
|
||||
if (data) {
|
||||
operator.handleUsers({prepareRecordsOnly: false, users: [data]});
|
||||
|
||||
const updatedRoles: string[] = data.roles.split(' ');
|
||||
await fetchRolesIfNeeded(serverUrl, updatedRoles);
|
||||
}
|
||||
|
||||
return {data};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on updateMe', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
|
||||
let data: UserProfile;
|
||||
try {
|
||||
data = await client.patchMe(user);
|
||||
} catch (e) {
|
||||
forceLogoutIfNecessary(serverUrl, e as ClientError);
|
||||
return {error: e};
|
||||
}
|
||||
|
||||
if (data) {
|
||||
operator.handleUsers({prepareRecordsOnly: false, users: [data]});
|
||||
|
||||
const updatedRoles: string[] = data.roles.split(' ');
|
||||
await fetchRolesIfNeeded(serverUrl, updatedRoles);
|
||||
}
|
||||
|
||||
return {data};
|
||||
};
|
||||
|
||||
let ids: string[] = [];
|
||||
@@ -294,19 +298,14 @@ const debouncedFetchUserOrGroupsByMentionNames = debounce(
|
||||
},
|
||||
);
|
||||
|
||||
const notFoundMentions: {[serverUrl: string]: Set<string>} = {};
|
||||
const fetchUserOrGroupsByMentionNames = async (serverUrl: string, mentions: string[]) => {
|
||||
try {
|
||||
if (!notFoundMentions[serverUrl]) {
|
||||
notFoundMentions[serverUrl] = new Set();
|
||||
}
|
||||
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
// Get any missing users
|
||||
const usersInDb = await queryUsersByIdsOrUsernames(database, [], mentions).fetch();
|
||||
const usersMap = new Set(usersInDb.map((u) => u.username));
|
||||
const usernamesToFetch = mentions.filter((m) => !usersMap.has(m) && !notFoundMentions[serverUrl].has(m));
|
||||
const usernamesToFetch = mentions.filter((m) => !usersMap.has(m));
|
||||
|
||||
let fetchedUsers;
|
||||
if (usernamesToFetch.length) {
|
||||
@@ -322,117 +321,81 @@ const fetchUserOrGroupsByMentionNames = async (serverUrl: string, mentions: stri
|
||||
const groupsToFetch = groupsToCheck.filter((g) => !groupsMap.has(g));
|
||||
|
||||
if (groupsToFetch.length) {
|
||||
const results = await fetchGroupsByNames(serverUrl, groupsToFetch, false);
|
||||
if (!('error' in results)) {
|
||||
const retrievedSet = new Set(results.map((r) => r.name));
|
||||
for (const g of groupsToFetch) {
|
||||
if (!retrievedSet.has(g)) {
|
||||
notFoundMentions[serverUrl].add(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
await fetchGroupsByNames(serverUrl, groupsToFetch, false);
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchUserOrGroupsByMentionNames', getFullErrorMessage(error));
|
||||
return {error};
|
||||
return {data: true};
|
||||
} catch (e) {
|
||||
return {error: e};
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchStatusByIds(serverUrl: string, userIds: string[], fetchOnly = false) {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
if (!userIds.length) {
|
||||
return {statuses: []};
|
||||
}
|
||||
|
||||
let database;
|
||||
let operator;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const result = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
database = result.database;
|
||||
operator = result.operator;
|
||||
} catch (e) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const statuses = await client.getStatusesByIds(userIds);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const users = await queryUsersById(database, userIds).fetch();
|
||||
const userStatuses = statuses.reduce((result: Record<string, UserStatus>, s) => {
|
||||
result[s.user_id] = s;
|
||||
return result;
|
||||
}, {});
|
||||
if (!fetchOnly && DatabaseManager.serverDatabases[serverUrl]) {
|
||||
if (operator) {
|
||||
const users = await queryUsersById(database, userIds).fetch();
|
||||
const userStatuses = statuses.reduce((result: Record<string, UserStatus>, s) => {
|
||||
result[s.user_id] = s;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const usersToBatch = [];
|
||||
for (const user of users) {
|
||||
const receivedStatus = userStatuses[user.id];
|
||||
const statusToSet = receivedStatus?.status || General.OFFLINE;
|
||||
if (statusToSet !== user.status) {
|
||||
user.prepareStatus(statusToSet);
|
||||
usersToBatch.push(user);
|
||||
for (const user of users) {
|
||||
const status = userStatuses[user.id];
|
||||
user.prepareStatus(status?.status || General.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
if (usersToBatch.length) {
|
||||
await operator.batchRecords(usersToBatch, 'fetchStatusByIds');
|
||||
await operator.batchRecords(users, 'fetchStatusByIds');
|
||||
}
|
||||
}
|
||||
|
||||
return {statuses};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchStatusByIds', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
let usersByIdBatch: {
|
||||
serverUrl: string;
|
||||
userIds: Set<string>;
|
||||
timeout?: NodeJS.Timeout;
|
||||
} | undefined;
|
||||
const TIME_TO_BATCH = 500;
|
||||
|
||||
const processBatch = () => {
|
||||
if (!usersByIdBatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (usersByIdBatch.timeout) {
|
||||
clearTimeout(usersByIdBatch.timeout);
|
||||
}
|
||||
if (usersByIdBatch.userIds.size) {
|
||||
fetchUsersByIds(usersByIdBatch.serverUrl, Array.from(usersByIdBatch.userIds));
|
||||
}
|
||||
|
||||
usersByIdBatch = undefined;
|
||||
};
|
||||
|
||||
export const fetchUserByIdBatched = async (serverUrl: string, userId: string) => {
|
||||
if (serverUrl !== usersByIdBatch?.serverUrl) {
|
||||
processBatch();
|
||||
}
|
||||
|
||||
if (!usersByIdBatch) {
|
||||
usersByIdBatch = {
|
||||
serverUrl,
|
||||
userIds: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
if (usersByIdBatch.timeout) {
|
||||
clearTimeout(usersByIdBatch.timeout);
|
||||
}
|
||||
|
||||
usersByIdBatch.userIds.add(userId);
|
||||
usersByIdBatch.timeout = setTimeout(processBatch, TIME_TO_BATCH);
|
||||
};
|
||||
|
||||
export const fetchUsersByIds = async (serverUrl: string, userIds: string[], fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
if (!userIds.length) {
|
||||
return {users: [], existingUsers: []};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const currentUser = await getCurrentUser(database);
|
||||
const existingUsers = await queryUsersById(database, userIds).fetch();
|
||||
try {
|
||||
const currentUser = await getCurrentUser(operator.database);
|
||||
const existingUsers = await queryUsersById(operator.database, userIds).fetch();
|
||||
if (userIds.includes(currentUser!.id)) {
|
||||
existingUsers.push(currentUser!);
|
||||
}
|
||||
@@ -454,22 +417,30 @@ export const fetchUsersByIds = async (serverUrl: string, userIds: string[], fetc
|
||||
|
||||
return {users, existingUsers};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchUsersByIds', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchUsersByUsernames = async (serverUrl: string, usernames: string[], fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
if (!usernames.length) {
|
||||
return {users: []};
|
||||
}
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const currentUser = await getCurrentUser(database);
|
||||
const existingUsers = await queryUsersByUsername(database, usernames).fetch();
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUser = await getCurrentUser(operator.database);
|
||||
const existingUsers = await queryUsersByUsername(operator.database, usernames).fetch();
|
||||
const exisitingUsersMap = existingUsers.reduce((result: Record<string, UserModel>, u) => {
|
||||
result[u.username] = u;
|
||||
return result;
|
||||
@@ -490,80 +461,29 @@ export const fetchUsersByUsernames = async (serverUrl: string, usernames: string
|
||||
|
||||
return {users};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchUsersByUsernames', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProfiles = async (serverUrl: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE, options: any = {}, fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await client.getProfiles(page, perPage, options);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const toStore = removeUserFromList(currentUserId, users);
|
||||
if (toStore.length) {
|
||||
await operator.handleUsers({
|
||||
users: toStore,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {users};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchProfiles', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProfilesInTeam = async (serverUrl: string, teamId: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE, sort = '', options: any = {}, fetchOnly = false) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const users = await client.getProfilesInTeam(teamId, page, perPage, sort, options);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const toStore = removeUserFromList(currentUserId, users);
|
||||
if (toStore.length) {
|
||||
await operator.handleUsers({
|
||||
users: toStore,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {users};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchProfilesInTeam', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProfilesNotInChannel = async (
|
||||
serverUrl: string,
|
||||
teamId: string,
|
||||
channelId: string,
|
||||
groupConstrained = false,
|
||||
page = 0,
|
||||
perPage: number = General.PROFILE_CHUNK_SIZE,
|
||||
fetchOnly = false,
|
||||
) => {
|
||||
try {
|
||||
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const users = await client.getProfilesNotInChannel(teamId, channelId, groupConstrained, page, perPage);
|
||||
|
||||
if (!fetchOnly && users.length) {
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const currentUserId = await getCurrentUserId(operator.database);
|
||||
const toStore = removeUserFromList(currentUserId, users);
|
||||
await operator.handleUsers({
|
||||
users: toStore,
|
||||
@@ -573,21 +493,63 @@ export const fetchProfilesNotInChannel = async (
|
||||
|
||||
return {users};
|
||||
} catch (error) {
|
||||
logDebug('error on fetchProfilesNotInChannel', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProfilesInTeam = async (serverUrl: string, teamId: string, page = 0, perPage: number = General.PROFILE_CHUNK_SIZE, sort = '', options: any = {}, fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await client.getProfilesInTeam(teamId, page, perPage, sort, options);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const currentUserId = await getCurrentUserId(operator.database);
|
||||
const toStore = removeUserFromList(currentUserId, users);
|
||||
|
||||
await operator.handleUsers({
|
||||
users: toStore,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
return {users};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const searchProfiles = async (serverUrl: string, term: string, options: SearchUserOptions, fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUserId = await getCurrentUserId(operator.database);
|
||||
const users = await client.searchUsers(term, options);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const {database} = operator;
|
||||
const existing = await queryUsersById(database, users.map((u) => u.id)).fetchIds();
|
||||
const existingSet = new Set(existing);
|
||||
const usersToAdd = users.filter((u) => !existingSet.has(u.id));
|
||||
@@ -602,28 +564,48 @@ export const searchProfiles = async (serverUrl: string, term: string, options: S
|
||||
|
||||
return {data: users};
|
||||
} catch (error) {
|
||||
logDebug('error on searchProfiles', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
logError('searchProfiles', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchMissingProfilesByIds = async (serverUrl: string, userIds: string[]) => {
|
||||
const {users} = await fetchUsersByIds(serverUrl, userIds);
|
||||
if (users) {
|
||||
const statusToLoad = users.map((u) => u.id);
|
||||
fetchStatusByIds(serverUrl, statusToLoad);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {users} = await fetchUsersByIds(serverUrl, userIds);
|
||||
if (users) {
|
||||
const statusToLoad = users.map((u) => u.id);
|
||||
fetchStatusByIds(serverUrl, statusToLoad);
|
||||
}
|
||||
return {users};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
return {users};
|
||||
};
|
||||
|
||||
export const fetchMissingProfilesByUsernames = async (serverUrl: string, usernames: string[]) => {
|
||||
const {users} = await fetchUsersByUsernames(serverUrl, usernames);
|
||||
if (users) {
|
||||
const statusToLoad = users.map((u) => u.id);
|
||||
fetchStatusByIds(serverUrl, statusToLoad);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const {users} = await fetchUsersByUsernames(serverUrl, usernames);
|
||||
if (users) {
|
||||
const statusToLoad = users.map((u) => u.id);
|
||||
fetchStatusByIds(serverUrl, statusToLoad);
|
||||
}
|
||||
return {users};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
return {users};
|
||||
};
|
||||
|
||||
export async function updateAllUsersSince(serverUrl: string, since: number, fetchOnly = false) {
|
||||
@@ -631,13 +613,23 @@ export async function updateAllUsersSince(serverUrl: string, since: number, fetc
|
||||
return {users: []};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const database = operator.database;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const userIds = (await queryAllUsers(database).fetchIds()).filter((id) => id !== currentUserId);
|
||||
let userUpdates: UserProfile[] = [];
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const userIds = (await queryAllUsers(database).fetchIds()).filter((id) => id !== currentUserId);
|
||||
userUpdates = await client.getProfilesByIds(userIds, {since});
|
||||
if (userUpdates.length && !fetchOnly) {
|
||||
const modelsToBatch: Model[] = [];
|
||||
@@ -651,9 +643,7 @@ export async function updateAllUsersSince(serverUrl: string, since: number, fetc
|
||||
|
||||
await operator.batchRecords(modelsToBatch, 'updateAllUsersSince');
|
||||
}
|
||||
} catch (error) {
|
||||
logDebug('error on updateAllUsersSince', getFullErrorMessage(error));
|
||||
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@@ -661,16 +651,25 @@ export async function updateAllUsersSince(serverUrl: string, since: number, fetc
|
||||
}
|
||||
|
||||
export async function updateUsersNoLongerVisible(serverUrl: string, prepareRecordsOnly = false): Promise<{error?: unknown; models?: Model[]}> {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
const serverDatabase = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!serverDatabase) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
try {
|
||||
const knownUsers = new Set(await client.getKnownUsers());
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
const currentUserId = await getCurrentUserId(serverDatabase.database);
|
||||
knownUsers.add(currentUserId);
|
||||
|
||||
const allUsers = await queryAllUsers(database).fetch();
|
||||
const allUsers = await queryAllUsers(serverDatabase.database).fetch();
|
||||
for (const user of allUsers) {
|
||||
if (!knownUsers.has(user.id)) {
|
||||
user.prepareDestroyPermanently();
|
||||
@@ -678,85 +677,131 @@ export async function updateUsersNoLongerVisible(serverUrl: string, prepareRecor
|
||||
}
|
||||
}
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
operator.batchRecords(models, 'updateUsersNoLongerVisible');
|
||||
serverDatabase.operator.batchRecords(models, 'updateUsersNoLongerVisible');
|
||||
}
|
||||
return {models};
|
||||
} catch (error) {
|
||||
logDebug('error on updateUsersNoLongerVisible', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {models};
|
||||
}
|
||||
|
||||
export const setStatus = async (serverUrl: string, status: UserStatus) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const data = await client.updateStatus(status);
|
||||
await updateLocalUser(serverUrl, {status: status.status});
|
||||
|
||||
return {data};
|
||||
return {
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
logDebug('error on setStatus', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCustomStatus = async (serverUrl: string, customStatus: UserCustomStatus) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
if (!customStatus.duration) {
|
||||
delete customStatus.expires_at;
|
||||
}
|
||||
await client.updateCustomStatus(customStatus);
|
||||
return {};
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logDebug('error on updateCustomStatus', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const removeRecentCustomStatus = async (serverUrl: string, customStatus: UserCustomStatus) => {
|
||||
updateRecentCustomStatuses(serverUrl, customStatus, false, true);
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.removeRecentCustomStatus(customStatus);
|
||||
return {};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on removeRecentCustomStatus', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
updateRecentCustomStatuses(serverUrl, customStatus, false, true);
|
||||
|
||||
try {
|
||||
await client.removeRecentCustomStatus(customStatus);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
|
||||
export const unsetCustomStatus = async (serverUrl: string) => {
|
||||
let client: Client;
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.unsetCustomStatus();
|
||||
return {};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on unsetCustomStatus', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
await client.unsetCustomStatus();
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
|
||||
export const setDefaultProfileImage = async (serverUrl: string, userId: string) => {
|
||||
let client: Client;
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.setDefaultProfileImage(userId);
|
||||
updateLocalUser(serverUrl, {last_picture_update: Date.now()});
|
||||
return {};
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
logDebug('error on setDefaultProfileImage', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
await client.setDefaultProfileImage(userId);
|
||||
updateLocalUser(serverUrl, {last_picture_update: Date.now()});
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
|
||||
export const uploadUserProfileImage = async (serverUrl: string, localPath: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUser = await getCurrentUser(database);
|
||||
if (currentUser) {
|
||||
const endpoint = `${client.getUserRoute(currentUser.id)}/image`;
|
||||
@@ -769,31 +814,42 @@ export const uploadUserProfileImage = async (serverUrl: string, localPath: strin
|
||||
},
|
||||
});
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
logDebug('error on uploadUserProfileImage', getFullErrorMessage(error));
|
||||
return {error};
|
||||
} catch (e) {
|
||||
return {error: e};
|
||||
}
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export const searchUsers = async (serverUrl: string, term: string, teamId: string, channelId?: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const users = await client.autocompleteUsers(term, teamId, channelId);
|
||||
return {users};
|
||||
} catch (error) {
|
||||
logDebug('error on searchUsers', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const buildProfileImageUrl = (serverUrl: string, userId: string, timestamp = 0) => {
|
||||
let client: Client;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
return client.getProfilePictureUrl(userId, timestamp);
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return client.getProfilePictureUrl(userId, timestamp);
|
||||
};
|
||||
|
||||
export const autoUpdateTimezone = async (serverUrl: string) => {
|
||||
@@ -802,13 +858,14 @@ export const autoUpdateTimezone = async (serverUrl: string) => {
|
||||
const result = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
database = result.database;
|
||||
} catch (e) {
|
||||
return;
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const config = await getConfig(database);
|
||||
const currentUser = await getCurrentUser(database);
|
||||
|
||||
if (!currentUser) {
|
||||
return;
|
||||
if (!currentUser || !config || !isTimezoneEnabled(config)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set timezone
|
||||
@@ -821,13 +878,26 @@ export const autoUpdateTimezone = async (serverUrl: string) => {
|
||||
const timezone = {useAutomaticTimezone: 'true', automaticTimezone: deviceTimezone, manualTimezone: currentTimezone.manualTimezone};
|
||||
await updateMe(serverUrl, {timezone});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const fetchTeamAndChannelMembership = async (serverUrl: string, userId: string, teamId: string, channelId?: string) => {
|
||||
let operator;
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const result = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
operator = result.operator;
|
||||
} catch (e) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const requests = await Promise.all([
|
||||
client.getTeamMember(teamId, userId),
|
||||
channelId ? client.getChannelMember(channelId, userId) : undefined,
|
||||
@@ -848,9 +918,8 @@ export const fetchTeamAndChannelMembership = async (serverUrl: string, userId: s
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat(), 'fetchTeamAndChannelMembership');
|
||||
return {};
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
logDebug('error on searchUsers', getFullErrorMessage(error));
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
@@ -861,7 +930,7 @@ export const getAllSupportedTimezones = async (serverUrl: string) => {
|
||||
const allTzs = await client.getTimezones();
|
||||
return allTzs;
|
||||
} catch (error) {
|
||||
logDebug('error on getAllSupportedTimezones', getFullErrorMessage(error));
|
||||
logError('FAILED TO GET ALL TIMEZONES', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,7 +78,13 @@ export async function handleCategoryDeletedEvent(serverUrl: string, msg: Websock
|
||||
|
||||
export async function handleCategoryOrderUpdatedEvent(serverUrl: string, msg: WebsocketCategoriesMessage) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
// Update category order
|
||||
if (msg.data.order?.length) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {addChannelToDefaultCategory, handleConvertedGMCategories} from '@actions/local/category';
|
||||
import {addChannelToDefaultCategory} from '@actions/local/category';
|
||||
import {
|
||||
markChannelAsViewed, removeCurrentUserFromChannel, setChannelDeleteAt,
|
||||
storeMyChannelsForTeam, updateChannelInfoFromChannel, updateMyChannelFromWebsocket,
|
||||
@@ -11,30 +11,31 @@ import {fetchMissingDirectChannelsInfo, fetchMyChannel, fetchChannelStats, fetch
|
||||
import {fetchPostsForChannel} from '@actions/remote/post';
|
||||
import {fetchRolesIfNeeded} from '@actions/remote/role';
|
||||
import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user';
|
||||
import {loadCallForChannel, leaveCall} from '@calls/actions/calls';
|
||||
import {userLeftChannelErr, userRemovedFromChannelErr} from '@calls/errors';
|
||||
import {getCurrentCall} from '@calls/state';
|
||||
import {Events, General} from '@constants';
|
||||
import {loadCallForChannel} from '@calls/actions/calls';
|
||||
import {Events} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {deleteChannelMembership, getChannelById, prepareMyChannelsForTeam, getCurrentChannel} from '@queries/servers/channel';
|
||||
import {getConfig, getCurrentChannelId, getCurrentTeamId, setCurrentTeamId} from '@queries/servers/system';
|
||||
import {getConfig, getCurrentChannelId} from '@queries/servers/system';
|
||||
import {getCurrentUser, getTeammateNameDisplay, getUserById} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
|
||||
// Received when current user created a channel in a different client
|
||||
export async function handleChannelCreatedEvent(serverUrl: string, msg: any) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
const {team_id: teamId, channel_id: channelId} = msg.data;
|
||||
if (EphemeralStore.creatingChannel) {
|
||||
return; // We probably don't need to handle this WS because we provoked it
|
||||
}
|
||||
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (channel) {
|
||||
return; // We already have this channel
|
||||
@@ -75,9 +76,12 @@ export async function handleChannelUnarchiveEvent(serverUrl: string, msg: any) {
|
||||
}
|
||||
|
||||
export async function handleChannelConvertedEvent(serverUrl: string, msg: any) {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const channelId = msg.data.channel_id;
|
||||
if (EphemeralStore.isConvertingChannel(channelId)) {
|
||||
return;
|
||||
@@ -93,37 +97,19 @@ export async function handleChannelConvertedEvent(serverUrl: string, msg: any) {
|
||||
}
|
||||
|
||||
export async function handleChannelUpdatedEvent(serverUrl: string, msg: any) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const updatedChannel = JSON.parse(msg.data.channel) as Channel;
|
||||
|
||||
if (EphemeralStore.isConvertingChannel(updatedChannel.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const existingChannel = await getChannelById(database, updatedChannel.id);
|
||||
const existingChannelType = existingChannel?.type;
|
||||
|
||||
const updatedChannel = JSON.parse(msg.data.channel);
|
||||
const models: Model[] = await operator.handleChannel({channels: [updatedChannel], prepareRecordsOnly: true});
|
||||
const infoModel = await updateChannelInfoFromChannel(serverUrl, updatedChannel, true);
|
||||
if (infoModel.model) {
|
||||
models.push(...infoModel.model);
|
||||
}
|
||||
operator.batchRecords(models, 'handleChannelUpdatedEvent');
|
||||
|
||||
// This indicates a GM was converted to a private channel
|
||||
if (existingChannelType === General.GM_CHANNEL && updatedChannel.type === General.PRIVATE_CHANNEL) {
|
||||
await handleConvertedGMCategories(serverUrl, updatedChannel.id, updatedChannel.team_id);
|
||||
|
||||
const currentChannelId = await getCurrentChannelId(database);
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
|
||||
// Making sure user is in the correct team
|
||||
if (currentChannelId === updatedChannel.id && currentTeamId !== updatedChannel.team_id) {
|
||||
await setCurrentTeamId(operator, updatedChannel.team_id);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
@@ -131,7 +117,10 @@ export async function handleChannelUpdatedEvent(serverUrl: string, msg: any) {
|
||||
|
||||
export async function handleChannelViewedEvent(serverUrl: string, msg: any) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {channel_id: channelId} = msg.data;
|
||||
|
||||
@@ -146,48 +135,14 @@ export async function handleChannelViewedEvent(serverUrl: string, msg: any) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMultipleChannelsViewedEvent(serverUrl: string, msg: any) {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const {channel_times: channelTimes} = msg.data;
|
||||
|
||||
const activeServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
const currentChannelId = await getCurrentChannelId(database);
|
||||
|
||||
const promises: Array<ReturnType<typeof markChannelAsViewed>> = [];
|
||||
for (const id of Object.keys(channelTimes)) {
|
||||
if (activeServerUrl === serverUrl && (currentChannelId === id || EphemeralStore.isSwitchingToChannel(id))) {
|
||||
continue;
|
||||
}
|
||||
promises.push(markChannelAsViewed(serverUrl, id, false, true));
|
||||
}
|
||||
|
||||
const members = (await Promise.allSettled(promises)).reduce<MyChannelModel[]>((acum, v) => {
|
||||
if (v.status === 'rejected') {
|
||||
return acum;
|
||||
}
|
||||
|
||||
const value = v.value;
|
||||
if (value.member) {
|
||||
acum.push(value.member);
|
||||
}
|
||||
return acum;
|
||||
}, []);
|
||||
|
||||
if (members.length) {
|
||||
operator.batchRecords(members, 'handleMultipleCahnnelViewedEvent');
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// This event is triggered by changes in the notify props or in the roles.
|
||||
export async function handleChannelMemberUpdatedEvent(serverUrl: string, msg: any) {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const models: Model[] = [];
|
||||
|
||||
const updatedChannelMember: ChannelMembership = JSON.parse(msg.data.channelMember);
|
||||
@@ -217,6 +172,13 @@ export async function handleChannelMemberUpdatedEvent(serverUrl: string, msg: an
|
||||
}
|
||||
|
||||
export async function handleDirectAddedEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
if (EphemeralStore.creatingDMorGMTeammates.length) {
|
||||
let userList: string[] | undefined;
|
||||
if ('teammate_ids' in msg.data) { // GM
|
||||
@@ -237,8 +199,6 @@ export async function handleDirectAddedEvent(serverUrl: string, msg: WebSocketMe
|
||||
}
|
||||
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
const {channel_id: channelId} = msg.broadcast;
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (channel) {
|
||||
@@ -282,13 +242,17 @@ export async function handleDirectAddedEvent(serverUrl: string, msg: WebSocketMe
|
||||
}
|
||||
|
||||
export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any) {
|
||||
const userId = msg.data.user_id || msg.broadcast.userId;
|
||||
const channelId = msg.data.channel_id || msg.broadcast.channel_id;
|
||||
const {team_id: teamId} = msg.data;
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const {database} = operator;
|
||||
const currentUser = await getCurrentUser(database);
|
||||
const userId = msg.data.user_id || msg.broadcast.userId;
|
||||
const channelId = msg.data.channel_id || msg.broadcast.channel_id;
|
||||
const {team_id: teamId} = msg.data;
|
||||
const models: Model[] = [];
|
||||
|
||||
if (userId === currentUser?.id) {
|
||||
@@ -362,9 +326,6 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
|
||||
const channelId = msg.data.channel_id || msg.broadcast.channel_id;
|
||||
|
||||
if (EphemeralStore.isLeavingChannel(channelId)) {
|
||||
if (getCurrentCall()?.channelId === channelId) {
|
||||
leaveCall(userLeftChannelErr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -387,12 +348,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
|
||||
if (currentChannelId && currentChannelId === channelId) {
|
||||
await handleKickFromChannel(serverUrl, currentChannelId);
|
||||
}
|
||||
|
||||
await removeCurrentUserFromChannel(serverUrl, channelId);
|
||||
|
||||
if (getCurrentCall()?.channelId === channelId) {
|
||||
leaveCall(userRemovedFromChannelErr);
|
||||
}
|
||||
} else {
|
||||
const {models: deleteMemberModels} = await deleteChannelMembership(operator, userId, channelId, true);
|
||||
if (deleteMemberModels) {
|
||||
@@ -407,26 +363,32 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
|
||||
}
|
||||
|
||||
export async function handleChannelDeletedEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const {channel_id: channelId, delete_at: deleteAt} = msg.data;
|
||||
if (EphemeralStore.isLeavingChannel(channelId) || EphemeralStore.isArchivingChannel(channelId)) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
||||
try {
|
||||
const {database} = operator;
|
||||
const {channel_id: channelId, delete_at: deleteAt} = msg.data;
|
||||
if (EphemeralStore.isLeavingChannel(channelId) || EphemeralStore.isArchivingChannel(channelId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentChannel = await getCurrentChannel(database);
|
||||
const user = await getCurrentUser(database);
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await getConfig(database);
|
||||
|
||||
await setChannelDeleteAt(serverUrl, channelId, deleteAt);
|
||||
|
||||
if (user.isGuest) {
|
||||
updateUsersNoLongerVisible(serverUrl);
|
||||
}
|
||||
|
||||
const currentChannel = await getCurrentChannel(database);
|
||||
const config = await getConfig(database);
|
||||
|
||||
if (config?.ExperimentalViewArchivedChannels !== 'true') {
|
||||
if (currentChannel && currentChannel.id === channelId) {
|
||||
await handleKickFromChannel(serverUrl, channelId, Events.CHANNEL_ARCHIVED);
|
||||
|
||||
@@ -4,14 +4,9 @@
|
||||
import {markChannelAsViewed} from '@actions/local/channel';
|
||||
import {dataRetentionCleanup} from '@actions/local/systems';
|
||||
import {markChannelAsRead} from '@actions/remote/channel';
|
||||
import {
|
||||
deferredAppEntryActions,
|
||||
entry,
|
||||
handleEntryAfterLoadNavigation,
|
||||
registerDeviceToken,
|
||||
} from '@actions/remote/entry/common';
|
||||
import {handleEntryAfterLoadNavigation, registerDeviceToken} from '@actions/remote/entry/common';
|
||||
import {deferredAppEntryActions, entry} from '@actions/remote/entry/gql_common';
|
||||
import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post';
|
||||
import {openAllUnreadChannels} from '@actions/remote/preference';
|
||||
import {autoUpdateTimezone} from '@actions/remote/user';
|
||||
import {loadConfigAndCalls} from '@calls/actions/calls';
|
||||
import {
|
||||
@@ -22,9 +17,9 @@ import {
|
||||
handleCallRecordingState,
|
||||
handleCallScreenOff,
|
||||
handleCallScreenOn,
|
||||
handleCallStarted, handleCallUserConnected, handleCallUserDisconnected,
|
||||
handleCallUserJoined,
|
||||
handleCallUserLeft,
|
||||
handleCallStarted,
|
||||
handleCallUserConnected,
|
||||
handleCallUserDisconnected,
|
||||
handleCallUserMuted,
|
||||
handleCallUserRaiseHand,
|
||||
handleCallUserReacted,
|
||||
@@ -32,7 +27,6 @@ import {
|
||||
handleCallUserUnraiseHand,
|
||||
handleCallUserVoiceOff,
|
||||
handleCallUserVoiceOn,
|
||||
handleUserDismissedNotification,
|
||||
} from '@calls/connection/websocket_event_handlers';
|
||||
import {isSupportedServerCalls} from '@calls/utils';
|
||||
import {Screens, WebsocketEvents} from '@constants';
|
||||
@@ -56,57 +50,26 @@ import {setTeamLoading} from '@store/team_load_store';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {logDebug, logInfo} from '@utils/log';
|
||||
|
||||
import {
|
||||
handleCategoryCreatedEvent,
|
||||
handleCategoryDeletedEvent,
|
||||
handleCategoryOrderUpdatedEvent,
|
||||
handleCategoryUpdatedEvent,
|
||||
} from './category';
|
||||
import {
|
||||
handleChannelConvertedEvent, handleChannelCreatedEvent,
|
||||
import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category';
|
||||
import {handleChannelConvertedEvent, handleChannelCreatedEvent,
|
||||
handleChannelDeletedEvent,
|
||||
handleChannelMemberUpdatedEvent,
|
||||
handleChannelUnarchiveEvent,
|
||||
handleChannelUpdatedEvent,
|
||||
handleChannelViewedEvent,
|
||||
handleMultipleChannelsViewedEvent,
|
||||
handleDirectAddedEvent,
|
||||
handleUserAddedToChannelEvent,
|
||||
handleUserRemovedFromChannelEvent,
|
||||
} from './channel';
|
||||
import {
|
||||
handleGroupMemberAddEvent,
|
||||
handleGroupMemberDeleteEvent,
|
||||
handleGroupReceivedEvent,
|
||||
handleGroupTeamAssociatedEvent,
|
||||
handleGroupTeamDissociateEvent,
|
||||
} from './group';
|
||||
handleUserRemovedFromChannelEvent} from './channel';
|
||||
import {handleGroupMemberAddEvent, handleGroupMemberDeleteEvent, handleGroupReceivedEvent, handleGroupTeamAssociatedEvent, handleGroupTeamDissociateEvent} from './group';
|
||||
import {handleOpenDialogEvent} from './integrations';
|
||||
import {
|
||||
handleNewPostEvent,
|
||||
handlePostAcknowledgementAdded,
|
||||
handlePostAcknowledgementRemoved,
|
||||
handlePostDeleted,
|
||||
handlePostEdited,
|
||||
handlePostUnread,
|
||||
} from './posts';
|
||||
import {
|
||||
handlePreferenceChangedEvent,
|
||||
handlePreferencesChangedEvent,
|
||||
handlePreferencesDeletedEvent,
|
||||
} from './preferences';
|
||||
import {handleNewPostEvent, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
|
||||
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
|
||||
import {handleAddCustomEmoji, handleReactionRemovedFromPostEvent, handleReactionAddedToPostEvent} from './reactions';
|
||||
import {handleUserRoleUpdatedEvent, handleTeamMemberRoleUpdatedEvent, handleRoleUpdatedEvent} from './roles';
|
||||
import {handleLicenseChangedEvent, handleConfigChangedEvent} from './system';
|
||||
import {
|
||||
handleLeaveTeamEvent,
|
||||
handleUserAddedToTeamEvent,
|
||||
handleUpdateTeamEvent,
|
||||
handleTeamArchived,
|
||||
handleTeamRestored,
|
||||
} from './teams';
|
||||
import {handleLeaveTeamEvent, handleUserAddedToTeamEvent, handleUpdateTeamEvent, handleTeamArchived, handleTeamRestored} from './teams';
|
||||
import {handleThreadUpdatedEvent, handleThreadReadChangedEvent, handleThreadFollowChangedEvent} from './threads';
|
||||
import {handleUserUpdatedEvent, handleUserTypingEvent, handleStatusChangedEvent} from './users';
|
||||
import {handleUserUpdatedEvent, handleUserTypingEvent} from './users';
|
||||
|
||||
export async function handleFirstConnect(serverUrl: string) {
|
||||
registerDeviceToken(serverUrl);
|
||||
@@ -159,16 +122,16 @@ async function doReconnect(serverUrl: string) {
|
||||
setTeamLoading(serverUrl, false);
|
||||
return entryData.error;
|
||||
}
|
||||
const {models, initialTeamId, initialChannelId, prefData, teamData, chData, gmConverted} = entryData;
|
||||
const {models, initialTeamId, initialChannelId, prefData, teamData, chData} = entryData;
|
||||
|
||||
await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId || '', currentChannelId || '', initialTeamId, initialChannelId, gmConverted);
|
||||
await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId || '', currentChannelId || '', initialTeamId, initialChannelId);
|
||||
|
||||
const dt = Date.now();
|
||||
if (models?.length) {
|
||||
await operator.batchRecords(models, 'doReconnect');
|
||||
}
|
||||
|
||||
const tabletDevice = isTablet();
|
||||
const tabletDevice = await isTablet();
|
||||
if (tabletDevice && initialChannelId === currentChannelId) {
|
||||
await markChannelAsRead(serverUrl, initialChannelId);
|
||||
markChannelAsViewed(serverUrl, initialChannelId);
|
||||
@@ -189,8 +152,6 @@ async function doReconnect(serverUrl: string) {
|
||||
|
||||
await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId);
|
||||
|
||||
openAllUnreadChannels(serverUrl);
|
||||
|
||||
dataRetentionCleanup(serverUrl);
|
||||
|
||||
AppsManager.refreshAppBindings(serverUrl);
|
||||
@@ -216,13 +177,6 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
handlePostUnread(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.POST_ACKNOWLEDGEMENT_ADDED:
|
||||
handlePostAcknowledgementAdded(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.POST_ACKNOWLEDGEMENT_REMOVED:
|
||||
handlePostAcknowledgementRemoved(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.LEAVE_TEAM:
|
||||
handleLeaveTeamEvent(serverUrl, msg);
|
||||
break;
|
||||
@@ -290,10 +244,6 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
handleChannelViewedEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.MULTIPLE_CHANNELS_VIEWED:
|
||||
handleMultipleChannelsViewedEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.CHANNEL_MEMBER_UPDATED:
|
||||
handleChannelMemberUpdatedEvent(serverUrl, msg);
|
||||
break;
|
||||
@@ -320,8 +270,9 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
break;
|
||||
|
||||
case WebsocketEvents.STATUS_CHANGED:
|
||||
handleStatusChangedEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// return dispatch(handleStatusChangedEvent(msg));
|
||||
case WebsocketEvents.TYPING:
|
||||
handleUserTypingEvent(serverUrl, msg);
|
||||
break;
|
||||
@@ -382,23 +333,12 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
handleCallChannelDisabled(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// DEPRECATED in favour of user_joined (since v0.21.0)
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
handleCallUserConnected(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// DEPRECATED in favour of user_left (since v0.21.0)
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
handleCallUserDisconnected(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.CALLS_USER_JOINED:
|
||||
handleCallUserJoined(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_LEFT:
|
||||
handleCallUserLeft(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
handleCallUserMuted(serverUrl, msg);
|
||||
break;
|
||||
@@ -438,9 +378,6 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
case WebsocketEvents.CALLS_HOST_CHANGED:
|
||||
handleCallHostChanged(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_DISMISSED_NOTIFICATION:
|
||||
handleUserDismissedNotification(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.GROUP_RECEIVED:
|
||||
handleGroupReceivedEvent(serverUrl, msg);
|
||||
@@ -479,7 +416,7 @@ async function fetchPostDataIfNeeded(serverUrl: string) {
|
||||
const mountedScreens = NavigationStore.getScreensInStack();
|
||||
const isChannelScreenMounted = mountedScreens.includes(Screens.CHANNEL);
|
||||
const isThreadScreenMounted = mountedScreens.includes(Screens.THREAD);
|
||||
const tabletDevice = isTablet();
|
||||
const tabletDevice = await isTablet();
|
||||
|
||||
if (isCRTEnabled && isThreadScreenMounted) {
|
||||
// Fetch new posts in the thread only when CRT is enabled,
|
||||
|
||||
@@ -4,20 +4,17 @@
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {storeMyChannelsForTeam, markChannelAsUnread, markChannelAsViewed, updateLastPostAt} from '@actions/local/channel';
|
||||
import {addPostAcknowledgement, markPostAsDeleted, removePostAcknowledgement} from '@actions/local/post';
|
||||
import {markPostAsDeleted} from '@actions/local/post';
|
||||
import {createThreadFromNewPost, updateThread} from '@actions/local/thread';
|
||||
import {fetchChannelStats, fetchMyChannel} from '@actions/remote/channel';
|
||||
import {fetchPostAuthors, fetchPostById} from '@actions/remote/post';
|
||||
import {openChannelIfNeeded} from '@actions/remote/preference';
|
||||
import {fetchThread} from '@actions/remote/thread';
|
||||
import {fetchMissingProfilesByIds} from '@actions/remote/user';
|
||||
import {ActionType, Events, Screens} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getChannelById, getMyChannel} from '@queries/servers/channel';
|
||||
import {getPostById} from '@queries/servers/post';
|
||||
import {getCurrentChannelId, getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled} from '@queries/servers/thread';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {isFromWebhook, isSystemMessage, shouldIgnorePost} from '@utils/post';
|
||||
@@ -47,7 +44,7 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
||||
}
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
|
||||
const existing = await getPostById(database, post.pending_post_id) || await getPostById(database, post.id);
|
||||
const existing = await getPostById(database, post.pending_post_id);
|
||||
|
||||
if (existing) {
|
||||
return;
|
||||
@@ -130,7 +127,7 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
||||
} else if ((post.channel_id === currentChannelId)) {
|
||||
const isChannelScreenMounted = NavigationStore.getScreensInStack().includes(Screens.CHANNEL);
|
||||
|
||||
const isTabletDevice = isTablet();
|
||||
const isTabletDevice = await isTablet();
|
||||
if (isChannelScreenMounted || isTabletDevice) {
|
||||
markAsViewed = false;
|
||||
}
|
||||
@@ -158,8 +155,6 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
||||
models.push(unreadAt);
|
||||
}
|
||||
}
|
||||
|
||||
openChannelIfNeeded(serverUrl, post.channel_id);
|
||||
}
|
||||
|
||||
let actionType: string = ActionType.POSTS.RECEIVED_NEW;
|
||||
@@ -167,20 +162,6 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
||||
actionType = ActionType.POSTS.RECEIVED_IN_THREAD;
|
||||
}
|
||||
|
||||
const outOfOrderWebsocketEvent = EphemeralStore.getLastPostWebsocketEvent(serverUrl, post.id);
|
||||
if (outOfOrderWebsocketEvent?.deleted) {
|
||||
for (const model of models) {
|
||||
if (model._preparedState === 'update') {
|
||||
model.cancelPrepareUpdate();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (outOfOrderWebsocketEvent?.post) {
|
||||
post = outOfOrderWebsocketEvent.post;
|
||||
}
|
||||
|
||||
const postModels = await operator.handlePosts({
|
||||
actionType,
|
||||
order: [post.id],
|
||||
@@ -198,7 +179,6 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
const {database} = operator;
|
||||
|
||||
let post: Post;
|
||||
try {
|
||||
@@ -208,14 +188,10 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
const {database} = operator;
|
||||
|
||||
const oldPost = await getPostById(database, post.id);
|
||||
if (!oldPost) {
|
||||
EphemeralStore.addEditingPost(serverUrl, post);
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldPost.isPinned !== post.is_pinned) {
|
||||
if (oldPost && oldPost.isPinned !== post.is_pinned) {
|
||||
fetchChannelStats(serverUrl, post.channel_id);
|
||||
}
|
||||
|
||||
@@ -226,7 +202,7 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
}
|
||||
|
||||
let actionType: string = ActionType.POSTS.RECEIVED_NEW;
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
if (isCRTEnabled && post.root_id) {
|
||||
actionType = ActionType.POSTS.RECEIVED_IN_THREAD;
|
||||
}
|
||||
@@ -243,17 +219,15 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
}
|
||||
|
||||
export async function handlePostDeleted(serverUrl: string, msg: WebSocketMessage) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const {database} = operator;
|
||||
|
||||
const post: Post = JSON.parse(msg.data.post);
|
||||
|
||||
const oldPost = await getPostById(database, post.id);
|
||||
if (!oldPost) {
|
||||
EphemeralStore.addRemovingPost(serverUrl, post.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
const {model: deleteModel} = await markPostAsDeleted(serverUrl, post, true);
|
||||
@@ -326,41 +300,3 @@ export async function handlePostUnread(serverUrl: string, msg: WebSocketMessage)
|
||||
markChannelAsUnread(serverUrl, channelId, delta, mentions, lastViewedAt);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handlePostAcknowledgementAdded(serverUrl: string, msg: WebSocketMessage) {
|
||||
try {
|
||||
const acknowledgement = JSON.parse(msg.data.acknowledgement);
|
||||
const {user_id, post_id, acknowledged_at} = acknowledgement;
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
const currentUserId = getCurrentUserId(database);
|
||||
if (EphemeralStore.isAcknowledgingPost(post_id) && currentUserId === user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
addPostAcknowledgement(serverUrl, post_id, user_id, acknowledged_at);
|
||||
fetchMissingProfilesByIds(serverUrl, [user_id]);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export async function handlePostAcknowledgementRemoved(serverUrl: string, msg: WebSocketMessage) {
|
||||
try {
|
||||
const acknowledgement = JSON.parse(msg.data.acknowledgement);
|
||||
const {user_id, post_id} = acknowledgement;
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
const currentUserId = getCurrentUserId(database);
|
||||
if (EphemeralStore.isUnacknowledgingPost(post_id) && currentUserId === user_id) {
|
||||
return;
|
||||
}
|
||||
await removePostAcknowledgement(serverUrl, post_id, user_id);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user