Compare commits
16 Commits
_archived-
...
_archived-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e9db28201 | ||
|
|
6d5b023b67 | ||
|
|
422c8bf88d | ||
|
|
0988eaef08 | ||
|
|
1b6f4683d9 | ||
|
|
41681aaa6b | ||
|
|
e8fe5632f4 | ||
|
|
61e452356b | ||
|
|
78565914db | ||
|
|
d79c9d7ef5 | ||
|
|
fd247a4e6b | ||
|
|
19ef1f65db | ||
|
|
09ace76b82 | ||
|
|
e198424da8 | ||
|
|
a39cd2990f | ||
|
|
fec99d526a |
@@ -7,7 +7,13 @@ on:
|
||||
- v5
|
||||
tags:
|
||||
- "v*"
|
||||
paths:
|
||||
- haskell/**
|
||||
- .github/workflows/build_haskell.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- haskell/**
|
||||
- .github/workflows/build_haskell.yml
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
32
.github/workflows/ci_dart.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Run CI
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # every sunday at midnight
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on ${{ matrix.os }} / ${{ matrix.flutter }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/repo_support
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
flutter: [stable, beta, dev]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '12.x'
|
||||
- uses: subosito/flutter-action@v1
|
||||
with:
|
||||
channel: ${{ matrix.flutter }}
|
||||
- run: flutter --version
|
||||
- run: flutter upgrade
|
||||
- run: dart --version
|
||||
- run: dart pub get
|
||||
- run: dart run tool/run_ci.dart
|
||||
51
.gitignore
vendored
@@ -43,9 +43,54 @@ cabal.project.local~
|
||||
*.cabal
|
||||
stack.yaml.lock
|
||||
|
||||
# Idris
|
||||
*.ibc
|
||||
|
||||
# chat database
|
||||
*.db
|
||||
*.db.bak
|
||||
|
||||
# Misc
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
build/
|
||||
# Default behavior, only keep it for apps
|
||||
pubspec.lock
|
||||
|
||||
# Web
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio build artifacts
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
|
||||
44
packages/analysis_options.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
prefer_single_quotes: true
|
||||
constant_identifier_names: false
|
||||
always_declare_return_types: true
|
||||
avoid_dynamic_calls: true
|
||||
avoid_empty_else: true
|
||||
avoid_relative_lib_imports: true
|
||||
avoid_shadowing_type_parameters: true
|
||||
avoid_slow_async_io: true
|
||||
avoid_types_as_parameter_names: true
|
||||
await_only_futures: true
|
||||
camel_case_extensions: true
|
||||
camel_case_types: true
|
||||
cancel_subscriptions: true
|
||||
curly_braces_in_flow_control_structures: true
|
||||
directives_ordering: true
|
||||
empty_catches: true
|
||||
hash_and_equals: true
|
||||
iterable_contains_unrelated_type: true
|
||||
list_remove_unrelated_type: true
|
||||
no_adjacent_strings_in_list: true
|
||||
no_duplicate_case_values: true
|
||||
package_api_docs: true
|
||||
package_prefixed_library_names: true
|
||||
prefer_generic_function_type_aliases: true
|
||||
prefer_is_empty: true
|
||||
prefer_is_not_empty: true
|
||||
prefer_iterable_whereType: true
|
||||
prefer_typing_uninitialized_variables: true
|
||||
sort_child_properties_last: true
|
||||
test_types_in_equals: true
|
||||
throw_in_finally: true
|
||||
unawaited_futures: true
|
||||
unnecessary_import: true
|
||||
unnecessary_null_aware_assignments: true
|
||||
unnecessary_statements: true
|
||||
unnecessary_type_check: true
|
||||
unrelated_type_equality_checks: true
|
||||
unsafe_html: true
|
||||
use_full_hex_values_for_flutter_colors: true
|
||||
valid_regexps: true
|
||||
23
packages/repo_support/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
|
||||
# Intellij
|
||||
*.idea/
|
||||
*.iml
|
||||
|
||||
# VS code
|
||||
.vscode
|
||||
|
||||
# Local
|
||||
.local/
|
||||
8
packages/repo_support/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# repo_support
|
||||
|
||||
App flutter utils repo support
|
||||
|
||||
```
|
||||
dart run tool/run_ci.dart
|
||||
|
||||
```
|
||||
11
packages/repo_support/pubspec.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: simplex_repo_support
|
||||
description: SimpleX build tools
|
||||
version: 0.2.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
|
||||
dev_dependencies:
|
||||
dev_test:
|
||||
|
||||
14
packages/repo_support/tool/run_ci.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
// ignore_for_file: prefer_double_quotes
|
||||
|
||||
import 'package:dev_test/package.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
Future main() async {
|
||||
for (var dir in [
|
||||
'simplex_app',
|
||||
'simplexmq',
|
||||
'repo_support',
|
||||
]) {
|
||||
await packageRunCi(join('..', dir));
|
||||
}
|
||||
}
|
||||
2
packages/simplex_app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Keep for apps
|
||||
!pubspec.lock
|
||||
10
packages/simplex_app/.metadata
Normal file
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
16
packages/simplex_app/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# simplex_app
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||
|
||||
For help getting started with Flutter, view our
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
45
packages/simplex_app/analysis_options.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
rules:
|
||||
prefer_double_quotes: true
|
||||
always_declare_return_types: true
|
||||
avoid_dynamic_calls: true
|
||||
avoid_empty_else: true
|
||||
avoid_relative_lib_imports: true
|
||||
avoid_shadowing_type_parameters: true
|
||||
avoid_slow_async_io: true
|
||||
avoid_types_as_parameter_names: true
|
||||
await_only_futures: true
|
||||
camel_case_extensions: true
|
||||
camel_case_types: true
|
||||
cancel_subscriptions: true
|
||||
curly_braces_in_flow_control_structures: true
|
||||
directives_ordering: true
|
||||
empty_catches: true
|
||||
hash_and_equals: true
|
||||
iterable_contains_unrelated_type: true
|
||||
list_remove_unrelated_type: true
|
||||
no_adjacent_strings_in_list: true
|
||||
no_duplicate_case_values: true
|
||||
package_api_docs: true
|
||||
package_prefixed_library_names: true
|
||||
prefer_generic_function_type_aliases: true
|
||||
prefer_is_empty: true
|
||||
prefer_is_not_empty: true
|
||||
prefer_iterable_whereType: true
|
||||
prefer_typing_uninitialized_variables: true
|
||||
sort_child_properties_last: true
|
||||
test_types_in_equals: true
|
||||
throw_in_finally: true
|
||||
unawaited_futures: true
|
||||
unnecessary_import: true
|
||||
unnecessary_null_aware_assignments: true
|
||||
unnecessary_statements: true
|
||||
unnecessary_type_check: true
|
||||
unrelated_type_equality_checks: true
|
||||
unsafe_html: true
|
||||
use_full_hex_values_for_flutter_colors: true
|
||||
valid_regexps: true
|
||||
13
packages/simplex_app/android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
68
packages/simplex_app/android/app/build.gradle
Normal file
@@ -0,0 +1,68 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "chat.simplex.app"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="chat.simplex.app">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,41 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="chat.simplex.app">
|
||||
<application
|
||||
android:label="simplex_chat"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,6 @@
|
||||
package chat.simplex.app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="chat.simplex.app">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
29
packages/simplex_app/android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
packages/simplex_app/android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
6
packages/simplex_app/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
11
packages/simplex_app/android/settings.gradle
Normal file
@@ -0,0 +1,11 @@
|
||||
include ':app'
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
@@ -0,0 +1 @@
|
||||
{"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]}
|
||||
@@ -0,0 +1 @@
|
||||
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]
|
||||
BIN
packages/simplex_app/build/unit_test_assets/NOTICES.Z
Normal file
33
packages/simplex_app/ios/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
26
packages/simplex_app/ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>9.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
1
packages/simplex_app/ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
1
packages/simplex_app/ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
471
packages/simplex_app/ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,471 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
7
packages/simplex_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
7
packages/simplex_app/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
13
packages/simplex_app/ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
23
packages/simplex_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
packages/simplex_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
packages/simplex_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
packages/simplex_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
packages/simplex_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
26
packages/simplex_app/ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
45
packages/simplex_app/ios/Runner/Info.plist
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>simplex_chat</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
1
packages/simplex_app/ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
115
packages/simplex_app/lib/main.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: "Flutter Demo",
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: "Flutter Demo Home Page"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Invoke "debug painting" (press "p" in the console, choose the
|
||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||
// to see the wireframe for each widget.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
"You have pushed the button this many times:",
|
||||
),
|
||||
Text(
|
||||
"$_counter",
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: "Increment",
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
167
packages/simplex_app/pubspec.lock
Normal file
@@ -0,0 +1,167 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.10"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
sdks:
|
||||
dart: ">=2.14.0 <3.0.0"
|
||||
89
packages/simplex_app/pubspec.yaml
Normal file
@@ -0,0 +1,89 @@
|
||||
name: simplex_app
|
||||
description: A new Flutter project.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
# followed by an optional build number separated by a +.
|
||||
# Both the version and the builder number may be overridden in flutter
|
||||
# build by specifying --build-name and --build-number, respectively.
|
||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.14.0 <3.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^1.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
30
packages/simplex_app/test/widget_test.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "package:simplex_app/main.dart";
|
||||
|
||||
void main() {
|
||||
testWidgets("Counter increments smoke test", (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text("0"), findsOneWidget);
|
||||
expect(find.text("1"), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text("0"), findsNothing);
|
||||
expect(find.text("1"), findsOneWidget);
|
||||
});
|
||||
}
|
||||
BIN
packages/simplex_app/web/favicon.png
Normal file
|
After Width: | Height: | Size: 917 B |
BIN
packages/simplex_app/web/icons/Icon-192.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
packages/simplex_app/web/icons/Icon-512.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
packages/simplex_app/web/icons/Icon-maskable-192.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
packages/simplex_app/web/icons/Icon-maskable-512.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
101
packages/simplex_app/web/index.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="simplex_chat">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<title>simplex_chat</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||
application. For more information, see:
|
||||
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||
<script>
|
||||
var serviceWorkerVersion = null;
|
||||
var scriptLoaded = false;
|
||||
function loadMainDartJs() {
|
||||
if (scriptLoaded) {
|
||||
return;
|
||||
}
|
||||
scriptLoaded = true;
|
||||
var scriptTag = document.createElement('script');
|
||||
scriptTag.src = 'main.dart.js';
|
||||
scriptTag.type = 'application/javascript';
|
||||
document.body.append(scriptTag);
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
// Service workers are supported. Use them.
|
||||
window.addEventListener('load', function () {
|
||||
// Wait for registration to finish before dropping the <script> tag.
|
||||
// Otherwise, the browser will load the script multiple times,
|
||||
// potentially different versions.
|
||||
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
|
||||
navigator.serviceWorker.register(serviceWorkerUrl)
|
||||
.then((reg) => {
|
||||
function waitForActivation(serviceWorker) {
|
||||
serviceWorker.addEventListener('statechange', () => {
|
||||
if (serviceWorker.state == 'activated') {
|
||||
console.log('Installed new service worker.');
|
||||
loadMainDartJs();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!reg.active && (reg.installing || reg.waiting)) {
|
||||
// No active web worker and we have installed or are installing
|
||||
// one for the first time. Simply wait for it to activate.
|
||||
waitForActivation(reg.installing || reg.waiting);
|
||||
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||||
// When the app updates the serviceWorkerVersion changes, so we
|
||||
// need to ask the service worker to update.
|
||||
console.log('New service worker available.');
|
||||
reg.update();
|
||||
waitForActivation(reg.installing);
|
||||
} else {
|
||||
// Existing service worker is still good.
|
||||
console.log('Loading app from service worker.');
|
||||
loadMainDartJs();
|
||||
}
|
||||
});
|
||||
|
||||
// If service worker doesn't succeed in a reasonable amount of time,
|
||||
// fallback to plaint <script> tag.
|
||||
setTimeout(() => {
|
||||
if (!scriptLoaded) {
|
||||
console.warn(
|
||||
'Failed to load app from service worker. Falling back to plain <script> tag.',
|
||||
);
|
||||
loadMainDartJs();
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
} else {
|
||||
// Service workers not supported. Just drop the <script> tag.
|
||||
loadMainDartJs();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
35
packages/simplex_app/web/manifest.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "simplex_chat",
|
||||
"short_name": "simplex_chat",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "A new Flutter project.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
packages/simplexmq/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.0.0
|
||||
|
||||
- Initial version.
|
||||
39
packages/simplexmq/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||
-->
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
|
||||
## Features
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
|
||||
## Getting started
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
||||
1
packages/simplexmq/example/simplexmq_example.dart
Normal file
@@ -0,0 +1 @@
|
||||
void main() {}
|
||||
7
packages/simplexmq/lib/simplexmq.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
/// Support for doing something awesome.
|
||||
///
|
||||
/// More dartdocs go here.
|
||||
library simplexmq;
|
||||
|
||||
export 'src/protocol.dart';
|
||||
export 'src/transport.dart' show Transport, SMPTransportClient;
|
||||
154
packages/simplexmq/lib/src/buffer.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
Uint8List encodeAscii(String s) => Uint8List.fromList(s.codeUnits);
|
||||
|
||||
String decodeAscii(Uint8List b) => String.fromCharCodes(b);
|
||||
|
||||
Uint8List concat(Uint8List b1, Uint8List b2) {
|
||||
final a = Uint8List(b1.length + b2.length);
|
||||
a.setAll(0, b1);
|
||||
a.setAll(b1.length, b2);
|
||||
return a;
|
||||
}
|
||||
|
||||
T fold<T, E>(List<E> xs, T Function(T, E) combine, T initial) {
|
||||
T res = initial;
|
||||
for (final x in xs) {
|
||||
res = combine(res, x);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Uint8List concatN(List<Uint8List> bs) {
|
||||
final aLen = fold(bs, (int size, Uint8List b) => size + b.length, 0);
|
||||
final a = Uint8List(aLen);
|
||||
fold(bs, (int offset, Uint8List b) {
|
||||
a.setAll(offset, b);
|
||||
return offset + b.length;
|
||||
}, 0);
|
||||
return a;
|
||||
}
|
||||
|
||||
final charSpace = ' '.codeUnitAt(0);
|
||||
final charEqual = '='.codeUnitAt(0);
|
||||
final empty = Uint8List(0);
|
||||
|
||||
Uint8List unwords(Uint8List b1, Uint8List b2) {
|
||||
final a = Uint8List(b1.length + b2.length + 1);
|
||||
a.setAll(0, b1);
|
||||
a[b1.length] = charSpace;
|
||||
a.setAll(b1.length + 1, b2);
|
||||
return a;
|
||||
}
|
||||
|
||||
Uint8List unwordsN(List<Uint8List> bs) {
|
||||
int i = bs.length;
|
||||
int size = bs.length - 1;
|
||||
while (i > 0) {
|
||||
size += bs[--i].length;
|
||||
}
|
||||
final a = Uint8List(size);
|
||||
|
||||
int offset = 0;
|
||||
for (i = 0; i < bs.length - 1; i++) {
|
||||
final b = bs[i];
|
||||
a.setAll(offset, b);
|
||||
offset += b.length;
|
||||
a[offset++] = charSpace;
|
||||
}
|
||||
a.setAll(offset, bs[i]);
|
||||
return a;
|
||||
}
|
||||
|
||||
final _base64chars = Uint8List.fromList(
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
.codeUnits);
|
||||
|
||||
List<int?> __base64lookup() {
|
||||
final a = List<int?>.filled(256, null);
|
||||
for (int i = 0; i < _base64chars.length; i++) {
|
||||
a[_base64chars[i]] = i;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
final _base64lookup = __base64lookup();
|
||||
|
||||
Uint8List encode64(Uint8List a) {
|
||||
final len = a.length;
|
||||
final b64len = (len / 3).ceil() * 4;
|
||||
final b64 = Uint8List(b64len);
|
||||
|
||||
int j = 0;
|
||||
for (int i = 0; i < len; i += 3) {
|
||||
final e1 = i + 1 < len ? a[i + 1] : 0;
|
||||
final e2 = i + 2 < len ? a[i + 2] : 0;
|
||||
b64[j++] = _base64chars[a[i] >> 2];
|
||||
b64[j++] = _base64chars[((a[i] & 3) << 4) | (e1 >> 4)];
|
||||
b64[j++] = _base64chars[((e1 & 15) << 2) | (e2 >> 6)];
|
||||
b64[j++] = _base64chars[e2 & 63];
|
||||
}
|
||||
|
||||
if (len % 3 != 0) b64[b64len - 1] = charEqual;
|
||||
if (len % 3 == 1) b64[b64len - 2] = charEqual;
|
||||
|
||||
return b64;
|
||||
}
|
||||
|
||||
Uint8List? decode64(Uint8List b64) {
|
||||
int len = b64.length;
|
||||
if (len % 4 != 0) return null;
|
||||
int bLen = (len * 3) >> 2;
|
||||
|
||||
if (b64[len - 1] == charEqual) {
|
||||
len--;
|
||||
bLen--;
|
||||
if (b64[len - 1] == charEqual) {
|
||||
len--;
|
||||
bLen--;
|
||||
}
|
||||
}
|
||||
|
||||
final bytes = Uint8List(bLen);
|
||||
|
||||
int i = 0;
|
||||
int pos = 0;
|
||||
while (i < len) {
|
||||
final enc1 = _base64lookup[b64[i++]];
|
||||
final enc2 = i < len ? _base64lookup[b64[i++]] : 0;
|
||||
final enc3 = i < len ? _base64lookup[b64[i++]] : 0;
|
||||
final enc4 = i < len ? _base64lookup[b64[i++]] : 0;
|
||||
if (enc1 == null || enc2 == null || enc3 == null || enc4 == null) {
|
||||
return null;
|
||||
}
|
||||
bytes[pos++] = (enc1 << 2) | (enc2 >> 4);
|
||||
int p = pos++;
|
||||
if (p < bLen) bytes[p] = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
p = pos++;
|
||||
if (p < bLen) bytes[p] = ((enc3 & 3) << 6) | (enc4 & 63);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Uint8List encodeInt32(int n) {
|
||||
final data = Uint8List(4);
|
||||
ByteData.sublistView(data).setInt32(0, n);
|
||||
return data;
|
||||
}
|
||||
|
||||
Uint8List encodeInt16(int n) {
|
||||
final data = Uint8List(2);
|
||||
ByteData.sublistView(data).setInt16(0, n);
|
||||
return data;
|
||||
}
|
||||
|
||||
extension EqualUint8List on Uint8List {
|
||||
bool equal(Uint8List b) {
|
||||
if (length != b.length) return false;
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (this[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
101
packages/simplexmq/lib/src/crypto.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'dart:math' show Random;
|
||||
import 'dart:typed_data';
|
||||
import 'package:pointycastle/api.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart';
|
||||
import 'package:pointycastle/asymmetric/oaep.dart';
|
||||
import 'package:pointycastle/asymmetric/rsa.dart';
|
||||
import 'package:pointycastle/block/aes_fast.dart';
|
||||
import 'package:pointycastle/block/modes/gcm.dart';
|
||||
import 'package:pointycastle/digests/sha256.dart';
|
||||
import 'package:pointycastle/key_generators/api.dart';
|
||||
import 'package:pointycastle/key_generators/rsa_key_generator.dart';
|
||||
import 'package:pointycastle/random/fortuna_random.dart';
|
||||
import 'package:pointycastle/signers/pss_signer.dart';
|
||||
import 'buffer.dart';
|
||||
|
||||
class AESKey {
|
||||
final Uint8List _key;
|
||||
AESKey._make(this._key);
|
||||
|
||||
static AESKey random([bool secure = false]) =>
|
||||
AESKey._make((secure ? secureRandomBytes : pseudoRandomBytes)(32));
|
||||
|
||||
static AESKey decode(Uint8List rawKey) => AESKey._make(rawKey);
|
||||
|
||||
Uint8List get bytes => _key;
|
||||
}
|
||||
|
||||
Uint8List randomIV() => pseudoRandomBytes(16);
|
||||
|
||||
Uint8List secureRandomBytes(int len) => _randomBytes(len, Random.secure());
|
||||
|
||||
final sessionSeed = Random.secure();
|
||||
|
||||
Uint8List pseudoRandomBytes(int len) => _randomBytes(len, sessionSeed);
|
||||
|
||||
Uint8List _randomBytes(int len, Random seedSource) {
|
||||
final bytes = Uint8List(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
bytes[i] = seedSource.nextInt(256);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
final paddingByte = '#'.codeUnitAt(0);
|
||||
|
||||
Uint8List encryptAES(AESKey key, Uint8List iv, int padTo, Uint8List data) {
|
||||
if (data.length >= padTo) throw ArgumentError('large message');
|
||||
final padded = Uint8List(padTo);
|
||||
padded.setAll(0, data);
|
||||
padded.fillRange(data.length, padTo, paddingByte);
|
||||
return _makeGCMCipher(key, iv, true).process(padded);
|
||||
}
|
||||
|
||||
Uint8List decryptAES(AESKey key, Uint8List iv, Uint8List encryptedAndTag) =>
|
||||
_makeGCMCipher(key, iv, false).process(encryptedAndTag);
|
||||
|
||||
GCMBlockCipher _makeGCMCipher(AESKey key, Uint8List iv, bool encrypt) =>
|
||||
GCMBlockCipher(AESFastEngine())
|
||||
..init(encrypt, AEADParameters(KeyParameter(key._key), 128, iv, empty));
|
||||
|
||||
FortunaRandom _secureFortunaRandom() =>
|
||||
FortunaRandom()..seed(KeyParameter(secureRandomBytes(32)));
|
||||
|
||||
AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateRSAkeyPair(
|
||||
[int bitLength = 2048]) {
|
||||
final keyGen = RSAKeyGenerator()
|
||||
..init(ParametersWithRandom(
|
||||
RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64),
|
||||
_secureFortunaRandom()));
|
||||
final pair = keyGen.generateKeyPair();
|
||||
return AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey>(
|
||||
pair.publicKey as RSAPublicKey, pair.privateKey as RSAPrivateKey);
|
||||
}
|
||||
|
||||
Uint8List encryptOAEP(RSAPublicKey key, Uint8List data) =>
|
||||
_oaep(true, PublicKeyParameter<RSAPublicKey>(key)).process(data);
|
||||
|
||||
Uint8List decryptOAEP(RSAPrivateKey key, Uint8List data) =>
|
||||
_oaep(false, PrivateKeyParameter<RSAPrivateKey>(key)).process(data);
|
||||
|
||||
OAEPEncoding _oaep(bool encrypt, AsymmetricKeyParameter keyParam) =>
|
||||
OAEPEncoding.withSHA256(RSAEngine())..init(encrypt, keyParam);
|
||||
|
||||
Uint8List signPSS(RSAPrivateKey privateKey, Uint8List data) =>
|
||||
_pss(true, PrivateKeyParameter<RSAPrivateKey>(privateKey))
|
||||
.generateSignature(data)
|
||||
.bytes;
|
||||
|
||||
bool verifyPSS(RSAPublicKey publicKey, Uint8List data, Uint8List sig) {
|
||||
try {
|
||||
return _pss(false, PublicKeyParameter<RSAPublicKey>(publicKey))
|
||||
.verifySignature(data, PSSSignature(sig));
|
||||
} on ArgumentError {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PSSSigner _pss(bool sign, AsymmetricKeyParameter keyParam) => PSSSigner(
|
||||
RSAEngine(), SHA256Digest(), SHA256Digest())
|
||||
..init(sign,
|
||||
ParametersWithSaltConfiguration(keyParam, _secureFortunaRandom(), 32));
|
||||
160
packages/simplexmq/lib/src/parser.dart
Normal file
@@ -0,0 +1,160 @@
|
||||
import 'dart:typed_data';
|
||||
import 'buffer.dart';
|
||||
|
||||
typedef BinaryTags<T> = Map<T, Uint8List>;
|
||||
|
||||
int cc(String c) => c.codeUnitAt(0);
|
||||
|
||||
final char0 = cc('0');
|
||||
final char9 = cc('9');
|
||||
final charLowerA = cc('a');
|
||||
final charLowerZ = cc('z');
|
||||
final charUpperA = cc('A');
|
||||
final charUpperZ = cc('Z');
|
||||
final charPlus = cc('+');
|
||||
final charSlash = cc('/');
|
||||
final charDot = cc('.');
|
||||
|
||||
class Parser {
|
||||
final Uint8List _s;
|
||||
final List<int> _positions = [];
|
||||
int _pos = 0;
|
||||
bool _fail = false;
|
||||
Parser(this._s);
|
||||
|
||||
bool get fail => _fail;
|
||||
bool get end => _pos >= _s.length;
|
||||
|
||||
// only calls `parse` if the parser did not previously fail
|
||||
T? _run<T>(T? Function() parse) {
|
||||
if (_fail || _pos >= _s.length) {
|
||||
_fail = true;
|
||||
return null;
|
||||
}
|
||||
final res = parse();
|
||||
if (res == null) _fail = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
T? tryParse<T>(T? Function(Parser p) parse) {
|
||||
if (_fail || _pos >= _s.length) {
|
||||
_fail = true;
|
||||
return null;
|
||||
}
|
||||
_positions.add(_pos);
|
||||
final res = parse(this);
|
||||
final prevPos = _positions.removeLast();
|
||||
if (res == null) {
|
||||
_pos = prevPos;
|
||||
_fail = false;
|
||||
}
|
||||
}
|
||||
|
||||
// takes a required number of bytes
|
||||
Uint8List? take(int len) => _run(() {
|
||||
final end = _pos + len;
|
||||
if (end > _s.length) return null;
|
||||
final res = _s.sublist(_pos, end);
|
||||
_pos = end;
|
||||
return res;
|
||||
});
|
||||
|
||||
// takes chars (> 0) while condition is true; function isAlphaNum or isDigit can be used
|
||||
Uint8List? takeWhile1(bool Function(int) f) => _run(() {
|
||||
final pos = _pos;
|
||||
while (f(_s[_pos])) {
|
||||
_pos++;
|
||||
}
|
||||
return _pos > pos ? _s.sublist(pos, _pos) : null;
|
||||
});
|
||||
|
||||
// takes the non-empty word until the first space or until the end of the string
|
||||
Uint8List? word() => _run(() {
|
||||
int pos = _s.indexOf(charSpace, _pos);
|
||||
if (pos == -1) pos = _s.length;
|
||||
if (pos <= _pos) return null;
|
||||
final res = _s.sublist(_pos, pos);
|
||||
_pos = pos;
|
||||
return res;
|
||||
});
|
||||
|
||||
bool? str(Uint8List s) => _run(() {
|
||||
for (int i = 0, j = _pos; i < s.length; i++, j++) {
|
||||
if (s[i] != _s[j]) return null;
|
||||
}
|
||||
_pos += s.length;
|
||||
return true;
|
||||
});
|
||||
|
||||
// takes the passed char
|
||||
bool? char(int c) => _run(() {
|
||||
if (_s[_pos] == c) {
|
||||
_pos++;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// takes space
|
||||
bool? space() => _run(() {
|
||||
if (_s[_pos] == charSpace) {
|
||||
_pos++;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
int? decimal() => _run(() {
|
||||
final s = takeWhile1(isDigit);
|
||||
if (s == null) return null;
|
||||
int n = 0;
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
n *= 10;
|
||||
n += s[i] - char0;
|
||||
}
|
||||
return n;
|
||||
});
|
||||
|
||||
DateTime? datetime() => _run(() {
|
||||
final s = word();
|
||||
if (s != null) return DateTime.tryParse(decodeAscii(s));
|
||||
});
|
||||
|
||||
// takes base-64 encoded string and returns decoded binary
|
||||
Uint8List? base64() => _run(() {
|
||||
bool tryCharEqual() {
|
||||
final ok = _pos < _s.length && _s[_pos] == charEqual;
|
||||
if (ok) _pos++;
|
||||
return ok;
|
||||
}
|
||||
|
||||
final pos = _pos;
|
||||
int c;
|
||||
do {
|
||||
c = _s[_pos];
|
||||
} while ((isAlphaNum(c) || c == charPlus || c == charSlash) &&
|
||||
++_pos < _s.length);
|
||||
|
||||
if (tryCharEqual()) tryCharEqual();
|
||||
return _pos > pos ? decode64(_s.sublist(pos, _pos)) : null;
|
||||
});
|
||||
|
||||
// takes one of the binary tags and returns its key
|
||||
T? someStr<T>(BinaryTags<T> ts) => _run(() {
|
||||
outer:
|
||||
for (final t in ts.entries) {
|
||||
final s = t.value;
|
||||
for (int i = 0, j = _pos; i < s.length; i++, j++) {
|
||||
if (s[i] != _s[j]) continue outer;
|
||||
}
|
||||
_pos += s.length;
|
||||
return t.key;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
bool isDigit(int c) => c >= char0 && c <= char9;
|
||||
|
||||
bool isAlphaNum(int c) =>
|
||||
(c >= char0 && c <= char9) ||
|
||||
(c >= charLowerA && c <= charLowerZ) ||
|
||||
(c >= charUpperA && c <= charUpperZ);
|
||||
268
packages/simplexmq/lib/src/protocol.dart
Normal file
@@ -0,0 +1,268 @@
|
||||
import 'dart:typed_data';
|
||||
import 'buffer.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
abstract class SMPCommand {
|
||||
Uint8List serialize();
|
||||
}
|
||||
|
||||
abstract class ClientCommand extends SMPCommand {}
|
||||
|
||||
abstract class BrokerCommand extends SMPCommand {}
|
||||
|
||||
final rsaPrefix = encodeAscii('rsa:');
|
||||
|
||||
Uint8List serializePubKey(Uint8List rcvPubKey) =>
|
||||
concat(rsaPrefix, encode64(rcvPubKey));
|
||||
|
||||
final Uint8List cNEW = encodeAscii('NEW');
|
||||
final Uint8List cSUB = encodeAscii('SUB');
|
||||
final Uint8List cKEY = encodeAscii('KEY');
|
||||
final Uint8List cACK = encodeAscii('ACK');
|
||||
final Uint8List cOFF = encodeAscii('OFF');
|
||||
final Uint8List cDEL = encodeAscii('DEL');
|
||||
final Uint8List cSEND = encodeAscii('SEND');
|
||||
final Uint8List cPING = encodeAscii('PING');
|
||||
final Uint8List cIDS = encodeAscii('IDS');
|
||||
final Uint8List cMSG = encodeAscii('MSG');
|
||||
final Uint8List cEND = encodeAscii('END');
|
||||
final Uint8List cOK = encodeAscii('OK');
|
||||
final Uint8List cERR = encodeAscii('ERR');
|
||||
final Uint8List cPONG = encodeAscii('PONG');
|
||||
|
||||
enum SMPCmdTag {
|
||||
NEW,
|
||||
SUB,
|
||||
KEY,
|
||||
ACK,
|
||||
OFF,
|
||||
DEL,
|
||||
SEND,
|
||||
PING,
|
||||
IDS,
|
||||
MSG,
|
||||
END,
|
||||
OK,
|
||||
ERR,
|
||||
PONG,
|
||||
}
|
||||
|
||||
final BinaryTags<SMPCmdTag> smpCmdTags = {
|
||||
SMPCmdTag.NEW: cNEW,
|
||||
SMPCmdTag.SUB: cSUB,
|
||||
SMPCmdTag.KEY: cKEY,
|
||||
SMPCmdTag.ACK: cACK,
|
||||
SMPCmdTag.OFF: cOFF,
|
||||
SMPCmdTag.DEL: cDEL,
|
||||
SMPCmdTag.SEND: cSEND,
|
||||
SMPCmdTag.PING: cPING,
|
||||
SMPCmdTag.IDS: cIDS,
|
||||
SMPCmdTag.MSG: cMSG,
|
||||
SMPCmdTag.END: cEND,
|
||||
SMPCmdTag.OK: cOK,
|
||||
SMPCmdTag.ERR: cERR,
|
||||
SMPCmdTag.PONG: cPONG,
|
||||
};
|
||||
|
||||
class NEW extends ClientCommand {
|
||||
final Uint8List rcvPubKey;
|
||||
NEW(this.rcvPubKey);
|
||||
@override
|
||||
Uint8List serialize() => unwords(cNEW, serializePubKey(rcvPubKey));
|
||||
}
|
||||
|
||||
class SUB extends ClientCommand {
|
||||
@override
|
||||
Uint8List serialize() => cSUB;
|
||||
}
|
||||
|
||||
class KEY extends ClientCommand {
|
||||
final Uint8List sndPubKey;
|
||||
KEY(this.sndPubKey);
|
||||
@override
|
||||
Uint8List serialize() => unwords(cKEY, serializePubKey(sndPubKey));
|
||||
}
|
||||
|
||||
class ACK extends ClientCommand {
|
||||
@override
|
||||
Uint8List serialize() => cACK;
|
||||
}
|
||||
|
||||
class OFF extends ClientCommand {
|
||||
@override
|
||||
Uint8List serialize() => cOFF;
|
||||
}
|
||||
|
||||
class DEL extends ClientCommand {
|
||||
@override
|
||||
Uint8List serialize() => cDEL;
|
||||
}
|
||||
|
||||
List<Uint8List> serializeMsg(Uint8List msg) =>
|
||||
[encodeAscii(msg.length.toString()), msg, empty];
|
||||
|
||||
class SEND extends ClientCommand {
|
||||
final Uint8List msgBody;
|
||||
SEND(this.msgBody);
|
||||
@override
|
||||
Uint8List serialize() => unwordsN([cSEND, ...serializeMsg(msgBody)]);
|
||||
}
|
||||
|
||||
class PING extends ClientCommand {
|
||||
@override
|
||||
Uint8List serialize() => cPING;
|
||||
}
|
||||
|
||||
class IDS extends BrokerCommand {
|
||||
final Uint8List rcvId;
|
||||
final Uint8List sndId;
|
||||
IDS(this.rcvId, this.sndId) : super();
|
||||
@override
|
||||
Uint8List serialize() => unwordsN([cIDS, encode64(rcvId), encode64(sndId)]);
|
||||
}
|
||||
|
||||
class MSG extends BrokerCommand {
|
||||
final Uint8List msgId;
|
||||
final DateTime ts;
|
||||
final Uint8List msgBody;
|
||||
MSG(this.msgId, this.ts, this.msgBody);
|
||||
@override
|
||||
Uint8List serialize() => unwordsN([
|
||||
cMSG,
|
||||
encode64(msgId),
|
||||
encodeAscii(ts.toIso8601String()),
|
||||
...serializeMsg(msgBody)
|
||||
]);
|
||||
}
|
||||
|
||||
class END extends BrokerCommand {
|
||||
@override
|
||||
Uint8List serialize() => cEND;
|
||||
}
|
||||
|
||||
class OK extends BrokerCommand {
|
||||
@override
|
||||
Uint8List serialize() => cOK;
|
||||
}
|
||||
|
||||
enum ErrorType { BLOCK, CMD, AUTH, QUOTA, NO_MSG, INTERNAL }
|
||||
|
||||
final BinaryTags<ErrorType> errorTags = {
|
||||
ErrorType.BLOCK: encodeAscii('BLOCK'),
|
||||
ErrorType.CMD: encodeAscii('CMD'),
|
||||
ErrorType.AUTH: encodeAscii('AUTH'),
|
||||
ErrorType.QUOTA: encodeAscii('QUOTA'),
|
||||
ErrorType.NO_MSG: encodeAscii('NO_MSG'),
|
||||
ErrorType.INTERNAL: encodeAscii('INTERNAL'),
|
||||
};
|
||||
|
||||
enum CmdErrorType { PROHIBITED, KEY_SIZE, SYNTAX, NO_AUTH, HAS_AUTH, NO_QUEUE }
|
||||
|
||||
final BinaryTags<CmdErrorType> cmdErrorTags = {
|
||||
CmdErrorType.PROHIBITED: encodeAscii('PROHIBITED'),
|
||||
CmdErrorType.KEY_SIZE: encodeAscii('KEY_SIZE'),
|
||||
CmdErrorType.SYNTAX: encodeAscii('SYNTAX'),
|
||||
CmdErrorType.NO_AUTH: encodeAscii('NO_AUTH'),
|
||||
CmdErrorType.HAS_AUTH: encodeAscii('HAS_AUTH'),
|
||||
CmdErrorType.NO_QUEUE: encodeAscii('NO_QUEUE'),
|
||||
};
|
||||
|
||||
class ERR extends BrokerCommand {
|
||||
final ErrorType err;
|
||||
final CmdErrorType? cmdErr;
|
||||
ERR(this.err)
|
||||
: cmdErr = err == ErrorType.CMD
|
||||
? throw ArgumentError('CMD error should be created with ERR.CMD')
|
||||
: null;
|
||||
ERR.cmd(CmdErrorType this.cmdErr) : err = ErrorType.CMD;
|
||||
@override
|
||||
Uint8List serialize() {
|
||||
final _err = errorTags[err]!;
|
||||
return cmdErr == null
|
||||
? unwords(cERR, _err)
|
||||
: unwordsN([cERR, _err, cmdErrorTags[cmdErr!]!]);
|
||||
}
|
||||
}
|
||||
|
||||
class PONG extends BrokerCommand {
|
||||
@override
|
||||
Uint8List serialize() => cPONG;
|
||||
}
|
||||
|
||||
final Map<SMPCmdTag, SMPCommand? Function(Parser p)> smpCmdParsers = {
|
||||
SMPCmdTag.NEW: (p) {
|
||||
p.space();
|
||||
final key = pubKeyP(p);
|
||||
if (key != null) return NEW(key);
|
||||
},
|
||||
SMPCmdTag.SUB: (_) => SUB(),
|
||||
SMPCmdTag.KEY: (p) {
|
||||
p.space();
|
||||
final key = pubKeyP(p);
|
||||
if (key != null) return KEY(key);
|
||||
},
|
||||
SMPCmdTag.ACK: (_) => ACK(),
|
||||
SMPCmdTag.OFF: (_) => OFF(),
|
||||
SMPCmdTag.DEL: (_) => DEL(),
|
||||
SMPCmdTag.SEND: (p) {
|
||||
p.space();
|
||||
final msg = messageP(p);
|
||||
if (msg != null) return SEND(msg);
|
||||
},
|
||||
SMPCmdTag.PING: (_) => PING(),
|
||||
SMPCmdTag.IDS: (p) {
|
||||
p.space();
|
||||
final rId = p.base64();
|
||||
p.space();
|
||||
final sId = p.base64();
|
||||
if (rId != null && sId != null) return IDS(rId, sId);
|
||||
},
|
||||
SMPCmdTag.MSG: (p) {
|
||||
p.space();
|
||||
final msgId = p.base64();
|
||||
p.space();
|
||||
final ts = p.datetime();
|
||||
p.space();
|
||||
final msg = messageP(p);
|
||||
if (msgId != null && ts != null && msg != null) return MSG(msgId, ts, msg);
|
||||
},
|
||||
SMPCmdTag.END: (_) => END(),
|
||||
SMPCmdTag.OK: (_) => OK(),
|
||||
SMPCmdTag.ERR: (p) {
|
||||
p.space();
|
||||
final err = p.someStr(errorTags);
|
||||
if (err == ErrorType.CMD) {
|
||||
p.space();
|
||||
final cmdErr = p.someStr(cmdErrorTags);
|
||||
if (cmdErr != null) return ERR.cmd(cmdErr);
|
||||
} else if (err != null) {
|
||||
return ERR(err);
|
||||
}
|
||||
},
|
||||
SMPCmdTag.PONG: (_) => PONG(),
|
||||
};
|
||||
|
||||
SMPCommand? smpCommandP(Parser p) {
|
||||
final cmd = p.someStr(smpCmdTags);
|
||||
return p.fail ? null : smpCmdParsers[cmd]!(p);
|
||||
}
|
||||
|
||||
SMPCommand? parseSMPCommand(Uint8List s) {
|
||||
final p = Parser(s);
|
||||
final cmd = smpCommandP(p);
|
||||
if (cmd != null && p.end) return cmd;
|
||||
}
|
||||
|
||||
Uint8List? pubKeyP(Parser p) {
|
||||
p.str(rsaPrefix);
|
||||
return p.base64();
|
||||
}
|
||||
|
||||
Uint8List? messageP(Parser p) {
|
||||
final len = p.decimal();
|
||||
p.space();
|
||||
Uint8List? msg;
|
||||
if (len != null) msg = p.take(len);
|
||||
p.space();
|
||||
return p.fail ? null : msg;
|
||||
}
|
||||
109
packages/simplexmq/lib/src/rsa_keys.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:asn1lib/asn1lib.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart'
|
||||
show RSAPublicKey, RSAPrivateKey;
|
||||
|
||||
const _rsaOid = '1.2.840.113549.1.1.1';
|
||||
final _asnRsaOid = ASN1ObjectIdentifier.fromComponentString(_rsaOid);
|
||||
final _asn1Null = ASN1Null();
|
||||
|
||||
ASN1Sequence _asn1Sequence(List<ASN1Object> elements) {
|
||||
final seq = ASN1Sequence()..elements = elements;
|
||||
return seq;
|
||||
}
|
||||
|
||||
void _assertRsaAlgorithm(ASN1Sequence seq) {
|
||||
if (seq.elements.isEmpty ||
|
||||
seq.elements[0] is! ASN1ObjectIdentifier ||
|
||||
(seq.elements[0] as ASN1ObjectIdentifier).identifier != _rsaOid) {
|
||||
throw Exception('Invalid key algorithm identifier');
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes binary PKCS1 to [RSAPublicKey]
|
||||
RSAPublicKey decodeRsaPubKeyPKCS1(Uint8List bytes) {
|
||||
final els = ASN1Sequence.fromBytes(bytes).elements;
|
||||
if (els.length != 2 || els[0] is! ASN1Integer || els[1] is! ASN1Integer) {
|
||||
throw Exception('Invalid PKCS1 encoding');
|
||||
}
|
||||
return RSAPublicKey(
|
||||
(els[0] as ASN1Integer).valueAsBigInteger!,
|
||||
(els[1] as ASN1Integer).valueAsBigInteger!,
|
||||
);
|
||||
}
|
||||
|
||||
/// Decodes binary SPKI to [RSAPublicKey]
|
||||
RSAPublicKey decodeRsaPubKey(Uint8List bytes) {
|
||||
final els = ASN1Sequence.fromBytes(bytes).elements;
|
||||
if (els.length != 2 || els[1] is! ASN1BitString || els[0] is! ASN1Sequence) {
|
||||
throw Exception('Invalid SPKI structure');
|
||||
}
|
||||
_assertRsaAlgorithm(els[0] as ASN1Sequence);
|
||||
return decodeRsaPubKeyPKCS1(els[1].valueBytes().sublist(1));
|
||||
}
|
||||
|
||||
/// Encodes [key] as binary PKCS1
|
||||
Uint8List encodeRsaPubKeyPKCS1(RSAPublicKey key) =>
|
||||
_asn1Sequence([ASN1Integer(key.modulus!), ASN1Integer(key.publicExponent!)])
|
||||
.encodedBytes;
|
||||
|
||||
/// Encodes [key] as binary SPKI
|
||||
Uint8List encodeRsaPubKey(RSAPublicKey key) => _asn1Sequence([
|
||||
_asn1Sequence([_asnRsaOid, _asn1Null]),
|
||||
ASN1BitString(encodeRsaPubKeyPKCS1(key))
|
||||
]).encodedBytes;
|
||||
|
||||
/// Decodes binary PKCS1 to [RSAPrivateKey]
|
||||
RSAPrivateKey decodeRsaPrivKeyPKCS1(Uint8List bytes) {
|
||||
final els = ASN1Sequence.fromBytes(bytes).elements;
|
||||
if (els.length != 9 || els.any((el) => el is! ASN1Integer)) {
|
||||
throw Exception('Invalid PKCS1 encoding');
|
||||
}
|
||||
return RSAPrivateKey(
|
||||
(els[1] as ASN1Integer).valueAsBigInteger!,
|
||||
(els[3] as ASN1Integer).valueAsBigInteger!,
|
||||
(els[4] as ASN1Integer).valueAsBigInteger!,
|
||||
(els[5] as ASN1Integer).valueAsBigInteger!);
|
||||
}
|
||||
|
||||
/// Decodes binary PKCS8 to [RSAPrivateKey]
|
||||
RSAPrivateKey decodeRsaPrivKey(Uint8List bytes) {
|
||||
final els = ASN1Sequence.fromBytes(bytes).elements;
|
||||
if (els.length != 3 ||
|
||||
els[1] is! ASN1Sequence ||
|
||||
els[2] is! ASN1OctetString) {
|
||||
throw Exception('Invalid PKCS8 structure');
|
||||
}
|
||||
_assertRsaAlgorithm(els[1] as ASN1Sequence);
|
||||
return decodeRsaPrivKeyPKCS1(els[2].valueBytes());
|
||||
}
|
||||
|
||||
final _asnZero = ASN1Integer(BigInt.from(0));
|
||||
|
||||
/// Encodes [key] as PKCS1 binary
|
||||
Uint8List encodeRsaPrivKeyPKCS1(RSAPrivateKey key) {
|
||||
final d = key.privateExponent!;
|
||||
final p = key.p!;
|
||||
final q = key.q!;
|
||||
final dModP = d % (p - BigInt.from(1));
|
||||
final dModQ = d % (q - BigInt.from(1));
|
||||
final coefficient = q.modInverse(p);
|
||||
return _asn1Sequence([
|
||||
_asnZero,
|
||||
ASN1Integer(key.modulus!),
|
||||
ASN1Integer(key.publicExponent!),
|
||||
ASN1Integer(d),
|
||||
ASN1Integer(p),
|
||||
ASN1Integer(q),
|
||||
ASN1Integer(dModP),
|
||||
ASN1Integer(dModQ),
|
||||
ASN1Integer(coefficient),
|
||||
]).encodedBytes;
|
||||
}
|
||||
|
||||
/// Encodes [key] as PKCS8 binary
|
||||
Uint8List encodeRsaPrivKey(RSAPrivateKey key) => _asn1Sequence([
|
||||
_asnZero,
|
||||
_asn1Sequence([_asnRsaOid, _asn1Null]),
|
||||
ASN1OctetString(encodeRsaPrivKeyPKCS1(key))
|
||||
]).encodedBytes;
|
||||
310
packages/simplexmq/lib/src/transport.dart
Normal file
@@ -0,0 +1,310 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:pointycastle/asymmetric/api.dart';
|
||||
import 'package:pointycastle/digests/sha256.dart';
|
||||
import 'buffer.dart';
|
||||
import 'crypto.dart';
|
||||
import 'parser.dart';
|
||||
import 'protocol.dart';
|
||||
import 'rsa_keys.dart';
|
||||
|
||||
abstract class Transport {
|
||||
Future<Uint8List> read(int n);
|
||||
Future<void> write(Uint8List data);
|
||||
Future<void> close();
|
||||
}
|
||||
|
||||
class SMPServer {
|
||||
final String host;
|
||||
final int? port;
|
||||
final Uint8List? keyHash;
|
||||
SMPServer(this.host, [this.port, this.keyHash]);
|
||||
}
|
||||
|
||||
class SessionKey {
|
||||
final AESKey aesKey;
|
||||
final Uint8List baseIV;
|
||||
int _counter = 0;
|
||||
SessionKey._(this.aesKey, this.baseIV);
|
||||
static SessionKey create() => SessionKey._(AESKey.random(), randomIV());
|
||||
Uint8List serialize() => concat(aesKey.bytes, baseIV);
|
||||
}
|
||||
|
||||
class ServerHeader {
|
||||
final int blockSize;
|
||||
final int keySize;
|
||||
ServerHeader(this.blockSize, this.keySize);
|
||||
}
|
||||
|
||||
class ServerHandshake {
|
||||
final RSAPublicKey publicKey;
|
||||
final int blockSize;
|
||||
ServerHandshake(this.publicKey, this.blockSize);
|
||||
}
|
||||
|
||||
typedef SMPVersion = List<int>;
|
||||
|
||||
SMPVersion _currentSMPVersion = const [0, 4, 1, 0];
|
||||
const int _serverHeaderSize = 8;
|
||||
const int _binaryRsaTransport = 0;
|
||||
const int _transportBlockSize = 4096;
|
||||
const int _maxTransportBlockSize = 65536;
|
||||
|
||||
class _Request {
|
||||
final Uint8List queueId;
|
||||
final Completer<BrokerResponse> completer = Completer();
|
||||
_Request(this.queueId);
|
||||
}
|
||||
|
||||
class Either<L, R> {
|
||||
final L? left;
|
||||
final R? right;
|
||||
Either.left(L this.left) : right = null;
|
||||
Either.right(R this.right) : left = null;
|
||||
}
|
||||
|
||||
enum ClientErrorType { SMPServerError, SMPResponseError, SMPUnexpectedResponse }
|
||||
|
||||
class BrokerResponse {
|
||||
final BrokerCommand? command;
|
||||
final ClientErrorType? errorType;
|
||||
final ERR? error;
|
||||
BrokerResponse(BrokerCommand this.command)
|
||||
: errorType = null,
|
||||
error = null;
|
||||
BrokerResponse.error(ClientErrorType this.errorType, this.error)
|
||||
: command = null;
|
||||
}
|
||||
|
||||
class ClientTransmission {
|
||||
final String corrId;
|
||||
final Uint8List queueId;
|
||||
final ClientCommand command;
|
||||
ClientTransmission(this.corrId, this.queueId, this.command);
|
||||
|
||||
Uint8List serialize() =>
|
||||
unwordsN([encodeAscii(corrId), encode64(queueId), command.serialize()]);
|
||||
}
|
||||
|
||||
class BrokerTransmission {
|
||||
final String corrId;
|
||||
final Uint8List queueId;
|
||||
final BrokerCommand? command;
|
||||
final ERR? error;
|
||||
BrokerTransmission(this.corrId, this.queueId, BrokerCommand this.command)
|
||||
: error = null;
|
||||
BrokerTransmission.error(this.corrId, this.queueId, ERR this.error)
|
||||
: command = null;
|
||||
}
|
||||
|
||||
final badBlock = BrokerTransmission.error('', empty, ERR(ErrorType.BLOCK));
|
||||
|
||||
class SMPTransportClient {
|
||||
final Transport _conn;
|
||||
final _sndKey = SessionKey.create();
|
||||
final _rcvKey = SessionKey.create();
|
||||
final int blockSize;
|
||||
int _corrId = 0;
|
||||
bool _messageStreamCreated = false;
|
||||
final Map<String, _Request> _sentCommands = {};
|
||||
SMPTransportClient._(this._conn, this.blockSize);
|
||||
|
||||
static Future<SMPTransportClient> connect(Transport conn,
|
||||
{Uint8List? keyHash, int? blockSize}) {
|
||||
return _clientHandshake(conn, keyHash, blockSize);
|
||||
}
|
||||
|
||||
Future<void> close() {
|
||||
return _conn.close();
|
||||
}
|
||||
|
||||
Future<BrokerResponse> sendSMPCommand(
|
||||
RSAPrivateKey? key, Uint8List queueId, ClientCommand cmd) async {
|
||||
final corrId = (++_corrId).toString();
|
||||
final t = ClientTransmission(corrId, queueId, cmd).serialize();
|
||||
final sig = key == null ? empty : encode64(signPSS(key, t));
|
||||
final data = unwordsN([sig, t, empty]);
|
||||
final r = _sentCommands[corrId] = _Request(queueId);
|
||||
await _writeEncrypted(data);
|
||||
return r.completer.future;
|
||||
}
|
||||
|
||||
Stream<BrokerTransmission> messageStream() {
|
||||
if (_messageStreamCreated) {
|
||||
throw Exception('message stream already created');
|
||||
}
|
||||
_messageStreamCreated = true;
|
||||
return _messageStream();
|
||||
}
|
||||
|
||||
Stream<BrokerTransmission> _messageStream() async* {
|
||||
try {
|
||||
while (true) {
|
||||
final block = await _readEncrypted();
|
||||
final t = _parseBrokerTransmission(block);
|
||||
if (t.corrId == '') {
|
||||
yield t;
|
||||
} else {
|
||||
final r = _sentCommands.remove(t.corrId);
|
||||
if (r == null) {
|
||||
yield t;
|
||||
} else {
|
||||
final cmd = t.command;
|
||||
r.completer.complete(r.queueId.equal(t.queueId)
|
||||
? cmd == null
|
||||
? BrokerResponse.error(
|
||||
ClientErrorType.SMPResponseError, t.error)
|
||||
: cmd is ERR
|
||||
? BrokerResponse.error(
|
||||
ClientErrorType.SMPServerError, cmd)
|
||||
: BrokerResponse(cmd)
|
||||
: BrokerResponse.error(
|
||||
ClientErrorType.SMPUnexpectedResponse, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static BrokerTransmission _parseBrokerTransmission(Uint8List s) {
|
||||
final p = Parser(s);
|
||||
p.space();
|
||||
final cId = p.word();
|
||||
p.space();
|
||||
final queueId = p.tryParse((p) => p.base64()) ?? empty;
|
||||
p.space();
|
||||
if (p.fail || cId == null) return badBlock;
|
||||
final corrId = decodeAscii(cId);
|
||||
final command = smpCommandP(p);
|
||||
if (command == null) {
|
||||
return BrokerTransmission.error(
|
||||
corrId, queueId, ERR.cmd(CmdErrorType.SYNTAX));
|
||||
}
|
||||
if (command is! BrokerCommand) {
|
||||
return BrokerTransmission.error(
|
||||
corrId, queueId, ERR.cmd(CmdErrorType.PROHIBITED));
|
||||
}
|
||||
final qErr = _tQueueError(queueId, command);
|
||||
if (qErr != null) {
|
||||
return BrokerTransmission.error(corrId, queueId, ERR.cmd(qErr));
|
||||
}
|
||||
return BrokerTransmission(corrId, queueId, command);
|
||||
}
|
||||
|
||||
static CmdErrorType? _tQueueError(Uint8List queueId, BrokerCommand cmd) {
|
||||
if (cmd is IDS || cmd is PONG) {
|
||||
if (queueId.isNotEmpty) return CmdErrorType.HAS_AUTH;
|
||||
} else if (cmd is! ERR && queueId.isEmpty) {
|
||||
return CmdErrorType.NO_QUEUE;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<SMPTransportClient> _clientHandshake(
|
||||
Transport conn, Uint8List? keyHash, int? blkSize) async {
|
||||
final srv = await _getHeaderAndPublicKey_1_2(conn, keyHash);
|
||||
final t = SMPTransportClient._(conn, blkSize ?? srv.blockSize);
|
||||
await t._sendEncryptedKeys_4(srv.publicKey);
|
||||
_checkVersion(await t._getWelcome_6());
|
||||
return t;
|
||||
}
|
||||
|
||||
static Future<ServerHandshake> _getHeaderAndPublicKey_1_2(
|
||||
Transport conn, Uint8List? keyHash) async {
|
||||
final srvHeader = parseServerHeader(await conn.read(_serverHeaderSize));
|
||||
final blkSize = srvHeader.blockSize;
|
||||
if (blkSize < _transportBlockSize || blkSize > _maxTransportBlockSize) {
|
||||
throw Exception('smp handshake header error: bad block size $blkSize');
|
||||
}
|
||||
final rawKey = await conn.read(srvHeader.keySize);
|
||||
if (keyHash != null) validateKeyHash_2(rawKey, keyHash);
|
||||
final serverKey = decodeRsaPubKey(rawKey);
|
||||
return ServerHandshake(serverKey, blkSize);
|
||||
}
|
||||
|
||||
static void validateKeyHash_2(Uint8List rawKey, Uint8List keyHash) {
|
||||
if (keyHash.equal(SHA256Digest().process(rawKey))) return;
|
||||
throw Exception('smp handshake error: bad key hash');
|
||||
}
|
||||
|
||||
static ServerHeader parseServerHeader(Uint8List a) {
|
||||
if (a.length != 8) {
|
||||
throw Exception('smp handshake error: bad header size ${a.length}');
|
||||
}
|
||||
final v = ByteData.sublistView(a);
|
||||
final blockSize = v.getUint32(0);
|
||||
final transportMode = v.getUint16(4);
|
||||
if (transportMode != _binaryRsaTransport) {
|
||||
throw Exception('smp handshake error: bad transport mode $transportMode');
|
||||
}
|
||||
final keySize = v.getUint16(6);
|
||||
return ServerHeader(blockSize, keySize);
|
||||
}
|
||||
|
||||
Future<void> _sendEncryptedKeys_4(RSAPublicKey serverKey) async {
|
||||
final header = encryptOAEP(serverKey, _clientHeader());
|
||||
await _conn.write(header);
|
||||
}
|
||||
|
||||
Uint8List _clientHeader() => concatN([
|
||||
encodeInt32(blockSize),
|
||||
encodeInt16(_binaryRsaTransport),
|
||||
_sndKey.serialize(),
|
||||
_rcvKey.serialize()
|
||||
]);
|
||||
|
||||
Future<SMPVersion> _getWelcome_6() async =>
|
||||
_parseSMPVersion(await _readEncrypted());
|
||||
|
||||
static SMPVersion _parseSMPVersion(Uint8List block) {
|
||||
final p = Parser(block);
|
||||
final SMPVersion version = [0, 0, 0, 0];
|
||||
void setVer(int i, int? v) {
|
||||
if (v == null || p.fail) {
|
||||
throw Exception('smp handshake error: bad version format');
|
||||
}
|
||||
version[i] = v;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final v = p.decimal();
|
||||
p.char(charDot);
|
||||
setVer(i, v);
|
||||
}
|
||||
final v = p.decimal();
|
||||
p.space();
|
||||
setVer(3, v);
|
||||
return version;
|
||||
}
|
||||
|
||||
static void _checkVersion(SMPVersion srvVersion) {
|
||||
final s0 = srvVersion[0];
|
||||
final c0 = _currentSMPVersion[0];
|
||||
if (s0 > c0 || (s0 == c0 && srvVersion[1] > _currentSMPVersion[1])) {
|
||||
throw Exception('smp handshake error: incompatible server version');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> _readEncrypted() async {
|
||||
final block = await _conn.read(blockSize);
|
||||
final iv = _nextIV(_rcvKey);
|
||||
return decryptAES(_rcvKey.aesKey, iv, block);
|
||||
}
|
||||
|
||||
Future<void> _writeEncrypted(Uint8List data) {
|
||||
final iv = _nextIV(_sndKey);
|
||||
final block = encryptAES(_sndKey.aesKey, iv, blockSize - 16, data);
|
||||
return _conn.write(block);
|
||||
}
|
||||
|
||||
static Uint8List _nextIV(SessionKey sk) {
|
||||
final c = encodeInt32(sk._counter++);
|
||||
final start = sk.baseIV.sublist(0, 4);
|
||||
final rest = sk.baseIV.sublist(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
start[i] ^= c[i];
|
||||
}
|
||||
return concat(start, rest);
|
||||
}
|
||||
}
|
||||
20
packages/simplexmq/pubspec.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: simplexmq
|
||||
description: A starting point for Dart libraries or applications.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.14.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
asn1lib: ^1.0.2
|
||||
pointycastle:
|
||||
git:
|
||||
url: https://github.com/simplex-chat/pc-dart.git
|
||||
ref: nullsafety
|
||||
|
||||
# pointycastle: ^3.3.4
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^1.0.0
|
||||
test: ^1.16.0
|
||||
66
packages/simplexmq/test/buffer_test.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:simplexmq/src/buffer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final hello123 = Uint8List.fromList([104, 101, 108, 108, 111, 49, 50, 51]);
|
||||
|
||||
class Base64Test {
|
||||
final String description;
|
||||
final String binary;
|
||||
final String base64;
|
||||
|
||||
Base64Test(this.binary, this.base64) : description = binary;
|
||||
Base64Test.withDescription(this.description, this.binary, this.base64);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ascii encoding/decoding', () {
|
||||
test('encodeAscii', () {
|
||||
expect(encodeAscii('hello123'), hello123);
|
||||
});
|
||||
|
||||
test('decodeAscii', () {
|
||||
expect(decodeAscii(hello123), 'hello123');
|
||||
});
|
||||
});
|
||||
|
||||
group('base-64 encoding/decoding', () {
|
||||
String allBinaryChars() {
|
||||
final a = Uint8List(256);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
a[i] = i;
|
||||
}
|
||||
return decodeAscii(a);
|
||||
}
|
||||
|
||||
final base64tests = [
|
||||
Base64Test('\x12\x34\x56\x78', 'EjRWeA=='),
|
||||
Base64Test('hello123', 'aGVsbG8xMjM='),
|
||||
Base64Test('Hello world', 'SGVsbG8gd29ybGQ='),
|
||||
Base64Test('Hello worlds!', 'SGVsbG8gd29ybGRzIQ=='),
|
||||
Base64Test('May', 'TWF5'),
|
||||
Base64Test('Ma', 'TWE='),
|
||||
Base64Test('M', 'TQ=='),
|
||||
Base64Test.withDescription(
|
||||
'all binary chars',
|
||||
allBinaryChars(),
|
||||
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==',
|
||||
),
|
||||
];
|
||||
|
||||
test('encode64', () {
|
||||
for (final t in base64tests) {
|
||||
expect(encode64(encodeAscii(t.binary)), encodeAscii(t.base64));
|
||||
}
|
||||
});
|
||||
|
||||
test('decode64', () {
|
||||
for (final t in base64tests) {
|
||||
expect(decode64(encodeAscii(t.base64)), encodeAscii(t.binary));
|
||||
}
|
||||
expect(decode64(encodeAscii('TWE')), null);
|
||||
expect(decode64(encodeAscii('TWE==')), null);
|
||||
expect(decode64(encodeAscii('TW!=')), null);
|
||||
});
|
||||
});
|
||||
}
|
||||
29
packages/simplexmq/test/crypto_test.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:simplexmq/src/buffer.dart';
|
||||
import 'package:simplexmq/src/crypto.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('AES-GCM encryption with padding', () {
|
||||
test('encrypt and decrypt', () {
|
||||
final key = AESKey.random();
|
||||
final iv = pseudoRandomBytes(16);
|
||||
final data = encodeAscii('hello');
|
||||
final cipherText = encryptAES(key, iv, 32, data);
|
||||
expect(cipherText.length, 32 + 16);
|
||||
final decrypted = decryptAES(key, iv, cipherText);
|
||||
expect(decodeAscii(decrypted),
|
||||
'hello' + List.filled(32 - 'hello'.length, '#').join());
|
||||
});
|
||||
});
|
||||
|
||||
group('RSA-OAEP encryption', () {
|
||||
test('encrypt and decrypt', () {
|
||||
final keyPair = generateRSAkeyPair();
|
||||
final data = encodeAscii('hello there');
|
||||
final cipherText = encryptOAEP(keyPair.publicKey, data);
|
||||
expect(cipherText.length, 2048 ~/ 8);
|
||||
final decrypted = decryptOAEP(keyPair.privateKey, cipherText);
|
||||
expect(decodeAscii(decrypted), 'hello there');
|
||||
});
|
||||
});
|
||||
}
|
||||
105
packages/simplexmq/test/protocol_test.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:simplexmq/src/buffer.dart';
|
||||
import 'package:simplexmq/src/protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Parsing & serializing SMP commands', () {
|
||||
group('valid commands', () {
|
||||
Null Function() parseSerialize(SMPCommand cmd) => () {
|
||||
final s = cmd.serialize();
|
||||
expect(parseSMPCommand(s)?.serialize(), s);
|
||||
expect(parseSMPCommand(concat(s, Uint8List.fromList([charSpace]))),
|
||||
null);
|
||||
};
|
||||
|
||||
test('NEW', parseSerialize(NEW(encodeAscii('rsa:1234'))));
|
||||
test('SUB', parseSerialize(SUB()));
|
||||
test('KEY', parseSerialize(KEY(encodeAscii('rsa:1234'))));
|
||||
test('ACK', parseSerialize(ACK()));
|
||||
test('OFF', parseSerialize(OFF()));
|
||||
test('DEL', parseSerialize(DEL()));
|
||||
test('SEND', parseSerialize(SEND(encodeAscii('hello'))));
|
||||
test('PING', parseSerialize(PING()));
|
||||
test('IDS', parseSerialize(IDS(encodeAscii('abc'), encodeAscii('def'))));
|
||||
test(
|
||||
'MSG',
|
||||
parseSerialize(MSG(encodeAscii('fgh'), DateTime.now().toUtc(),
|
||||
encodeAscii('hello'))));
|
||||
test('END', parseSerialize(END()));
|
||||
test('OK', parseSerialize(OK()));
|
||||
test('ERR', parseSerialize(ERR(ErrorType.AUTH)));
|
||||
test('ERR CMD', parseSerialize(ERR.cmd(CmdErrorType.SYNTAX)));
|
||||
test('PONG', parseSerialize(PONG()));
|
||||
});
|
||||
|
||||
group('invalid commands', () {
|
||||
void Function() parseFailure(String s) =>
|
||||
() => expect(parseSMPCommand(encodeAscii(s)), null);
|
||||
|
||||
void Function() parseSuccess(String s) =>
|
||||
() => expect(parseSMPCommand(encodeAscii(s)) is SMPCommand, true);
|
||||
|
||||
group('NEW', () {
|
||||
test('ok', parseSuccess('NEW rsa:abcd'));
|
||||
test('no key', parseFailure('NEW'));
|
||||
test('invalid base64', parseFailure('NEW rsa:abc'));
|
||||
test('double space', parseFailure('NEW rsa:abcd'));
|
||||
});
|
||||
|
||||
group('KEY', () {
|
||||
test('ok', parseSuccess('KEY rsa:abcd'));
|
||||
test('no key', parseFailure('KEY'));
|
||||
test('invalid base64', parseFailure('KEY rsa:abc'));
|
||||
test('double space', parseFailure('KEY rsa:abcd'));
|
||||
});
|
||||
|
||||
group('SEND', () {
|
||||
test('ok', parseSuccess('SEND 5 hello '));
|
||||
test('no size', parseFailure('SEND hello '));
|
||||
test('incorrect size', parseFailure('SEND 6 hello '));
|
||||
test('no trailing space', parseFailure('SEND 5 hello'));
|
||||
test('double space 1', parseFailure('SEND 5 hello '));
|
||||
test('double space 2', parseFailure('SEND 5 hello '));
|
||||
});
|
||||
|
||||
group('IDS', () {
|
||||
test('ok', parseSuccess('IDS abcd efgh'));
|
||||
test('no IDs', parseFailure('IDS'));
|
||||
test('only one ID', parseFailure('IDS abcd'));
|
||||
test('invalid base64 1', parseFailure('IDS abc efgh'));
|
||||
test('invalid base64 2', parseFailure('IDS abcd efg'));
|
||||
test('double space 1', parseFailure('IDS abcd efgh'));
|
||||
test('double space 2', parseFailure('IDS abcd efgh'));
|
||||
});
|
||||
|
||||
group('MSG', () {
|
||||
final String ts = '2021-10-03T10:50:59.895Z';
|
||||
test('ok', parseSuccess('MSG abcd $ts 5 hello '));
|
||||
test('invalid base64', parseFailure('MSG abc $ts 5 hello '));
|
||||
test('invalid timestamp 1',
|
||||
parseFailure('MSG abc 2021-10-03T10:50:59.895 5 hello '));
|
||||
test('invalid timestamp 2',
|
||||
parseFailure('MSG abc 2021-14-03T10:50:59.895Z 5 hello '));
|
||||
test('no size', parseFailure('MSG abcd $ts hello '));
|
||||
test('incorrect size', parseFailure('MSG abcd $ts 6 hello '));
|
||||
test('no trailing space', parseFailure('MSG abcd $ts 5 hello'));
|
||||
test('double space 1', parseFailure('MSG abcd $ts 5 hello '));
|
||||
test('double space 2', parseFailure('MSG abcd $ts 5 hello '));
|
||||
test('double space 3', parseFailure('MSG abcd $ts 5 hello '));
|
||||
test('double space 4', parseFailure('MSG abcd $ts 5 hello '));
|
||||
});
|
||||
|
||||
group('ERR', () {
|
||||
test('ok 1', parseSuccess('ERR AUTH'));
|
||||
test('ok 2', parseSuccess('ERR CMD SYNTAX'));
|
||||
test('unknown error', parseFailure('ERR HELLO'));
|
||||
test('unknown CMD error', parseFailure('ERR CMD HELLO'));
|
||||
test('bad sub-error', parseFailure('ERR AUTH SYNTAX'));
|
||||
test('double space 1', parseFailure('ERR AUTH'));
|
||||
test('double space 2', parseFailure('ERR CMD SYNTAX'));
|
||||
test('double space 3', parseFailure('ERR CMD SYNTAX'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||