Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sparrow SDK #1282

Merged
merged 21 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,12 @@ jobs:
fileDir: './android/app'
encodedString: ${{ secrets.KEYSTORE }}

- name: Generate app.env
env:
ANDROID_INTERSTITIAL_AD_ID: ${{ secrets.INTERSTITIAL_AD_UNIT_ID }}
IOS_INTERSTITIAL_AD_ID: ${{ secrets.INTERSTITIAL_AD_UNIT_ID_IOS }}
TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID: ${{ secrets.TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID }}
TAPSELL_INTERSTITIAL_ZONE_ID: ${{ secrets.TAPSELL_INTERSTITIAL_ZONE_ID }}

- name: Decode APP_ENV and write to app.env
run: |
touch app.env
echo "Android_interstitialAd=$ANDROID_INTERSTITIAL_AD_ID" > app.env
echo "IOS_interstitialAd=$IOS_INTERSTITIAL_AD_ID" >> app.env
echo "VideoInterstitialZoneId=$TAPSELL_VIDEO_INTERSTITIAL_ZONE_ID" >> app.env
echo "InterstitialZoneId=$TAPSELL_INTERSTITIAL_ZONE_ID" >> app.env
echo "${{ secrets.APP_ENV }}" | base64 --decode > app.env
echo "File 'app.env' created."
ls -lh app.env

- name: Extract app version from pubspec.yaml
id: extract_version
Expand Down
2 changes: 2 additions & 0 deletions desktop/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type ConfigOptions struct {
ExpirationDate string `json:"expirationDate"`
Chat ChatOptions `json:"chat"`
ProxyAll bool `json:"proxyAll"`
Country string `json:"country"`
}

func (s *configService) StartService(channel ws.UIChannel) (err error) {
Expand Down Expand Up @@ -105,6 +106,7 @@ func (app *App) sendConfigOptions() {
ExpirationDate: app.settings.GetExpirationDate(),
Devices: app.devices(),
ProxyAll: app.settings.GetProxyAll(),
Country: app.settings.GetCountry(),
Chat: ChatOptions{
AcceptedTermsVersion: 0,
OnBoardingStatus: false,
Expand Down
7 changes: 7 additions & 0 deletions lib/core/app/app_enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ extension AuthFlowExtension on AuthFlow {
bool get isUpdateAccount => this == AuthFlow.updateAccount;
bool get isRestoreAccount => this == AuthFlow.restoreAccount;
}

enum VpnStatus{
connected,
disconnected,
connecting,
disconnecting
}
13 changes: 11 additions & 2 deletions lib/core/app/app_secret.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
class AppSecret {
static String androidAdsAppId = dotenv.get('Android_interstitialAd');
static String iOSAdsAppId = dotenv.get('IOS_interstitialAd');
static String testingSpotCheckTargetToken = dotenv.get('TESTING_SPOTCHECK_TARGET_TOKEN');
static String iranSpotCheckTargetToken = dotenv.get('IRAN_SPOTCHECK_TARGET_TOKEN');
static String russiaSpotCheckTargetToken = dotenv.get('RUSSIA_SPOTCHECK_TARGET_TOKEN');
static String ukraineSpotCheckTargetToken = dotenv.get('UKRAINE_SPOTCHECK_TARGET_TOKEN');
static String belarusSpotCheckTargetToken = dotenv.get('BELARUS_SPOTCHECK_TARGET_TOKEN');
static String chinaSpotCheckTargetToken = dotenv.get('CHINA_SPOTCHECK_TARGET_TOKEN');
static String UAEspotCheckTargetToken = dotenv.get('UAE_SPOTCHECK_TARGET_TOKEN');
static String myanmarSpotCheckTargetToken = dotenv.get('MYANMAR_SPOTCHECK_TARGET_TOKEN');



static String tos = 'https://s3.amazonaws.com/lantern/Lantern-TOS.pdf';
static String privacyPolicy = 'https://s3.amazonaws.com/lantern/LanternPrivacyPolicy.pdf';
static String privacyPolicyV2 = 'https://lantern.io/privacy';
static String tosV2 = 'https://lantern.io/terms';
static String videoInterstitialZoneId = dotenv.get('VideoInterstitialZoneId');
static String interstitialZoneId =dotenv.get('InterstitialZoneId');


static String dnsConfig() {
Expand Down
11 changes: 9 additions & 2 deletions lib/core/service/injection_container.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import 'package:get_it/get_it.dart';
import 'package:lantern/core/service/app_purchase.dart';
import 'package:lantern/core/service/survey_service.dart';
import 'package:lantern/core/utils/common.dart';

final GetIt sl = GetIt.instance;

void initServices() {
//Inject
sl.registerLazySingleton(() => AppPurchase());
sl<AppPurchase>().init();
if (isMobile()) {
sl.registerLazySingleton(() => AppPurchase());
sl<AppPurchase>().init();
}


sl.registerLazySingleton(() => SurveyService());
}
177 changes: 177 additions & 0 deletions lib/core/service/survey_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import 'package:path_provider/path_provider.dart';
import 'package:surveysparrow_flutter_sdk/surveysparrow.dart';

import '../utils/common.dart';

enum SurveyScreens { homeScreen }

enum SurveyCountry {
russia('ru'),
belarus('by'),
ukraine('ua'),
china('cn'),
iran('ir'),
uae('uae'),
myanmar('mm'),
testing('testing');

const SurveyCountry(this.countryCode);

final String countryCode;
}

//This class use spot check service for survey
class SurveyService {
// Need to have spot check for each region
// Russia, Belarus, Ukraine, China, Iran, UAE, Myanmar

SpotCheck? spotCheck;
final int _VPNCONNECTED_COUNT = 10;

SurveyService() {
if (Platform.isWindows || Platform.isLinux) {
return;
}
_createConfigIfNeeded();
_countryListener();
}

void _countryListener() {
if (sessionModel.country.value!.isNotEmpty) {
createSpotCheckByCountry(sessionModel.country.value!.toLowerCase());
return;
}
sessionModel.country.addListener(() {
final country = sessionModel.country.value;
if (country != null && country.isNotEmpty) {
appLogger.d('Country found $country');
createSpotCheckByCountry(country.toLowerCase());
sessionModel.country
.removeListener(() {}); // Remove listener after getting value
}
});
}

//Create method to create spot check by country
//argument by string and use enum for country
//make sure when create country should not be null or empty
SpotCheck createSpotCheckByCountry(String country) {
appLogger.d('Create spot check for country $country');
if (spotCheck != null) {
return spotCheck!;
}
final surveyCountry = SurveyCountry.values.firstWhere(
(e) => e.countryCode == country,
orElse: () => SurveyCountry.testing,
);
String targetToken;
switch (surveyCountry) {
case SurveyCountry.russia:
targetToken = AppSecret.russiaSpotCheckTargetToken;
break;
case SurveyCountry.belarus:
targetToken = AppSecret.belarusSpotCheckTargetToken;
break;
case SurveyCountry.ukraine:
targetToken = AppSecret.ukraineSpotCheckTargetToken;
break;
case SurveyCountry.china:
targetToken = AppSecret.chinaSpotCheckTargetToken;
break;
case SurveyCountry.iran:
targetToken = AppSecret.iranSpotCheckTargetToken;
break;
case SurveyCountry.uae:
targetToken = AppSecret.UAEspotCheckTargetToken;
break;
case SurveyCountry.myanmar:
targetToken = AppSecret.myanmarSpotCheckTargetToken;
break;
case SurveyCountry.testing:
default:
targetToken = AppSecret.testingSpotCheckTargetToken;
appLogger.d('${country.toUpperCase()} not found, using testing token');
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.. you could add a utility method to fetch a SpotCheck for a given country code., and then call trackScreen on it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

spotCheck = SpotCheck(
domainName: "lantern.surveysparrow.com",
targetToken: targetToken,
userDetails: {});
return spotCheck!;
}

void trackScreen(SurveyScreens screen) {
appLogger.d('Track screen $screen');
spotCheck?.trackScreen(screen.name);
}

Widget surveyWidget() {
if (Platform.isWindows || Platform.isLinux) {
return const SizedBox();
}
return spotCheck!;
}

Future<String> get _surveyConfigPath async {
final cacheDir = await getApplicationCacheDirectory();
final filePath = '${cacheDir.path}/survey_config.json';
return filePath;
}

Future<void> _createConfigIfNeeded() async {
final filePath = await _surveyConfigPath;
final file = File(filePath);
try {
if (!await file.exists()) {
await file.create(recursive: true);
const surveyConfig = {"vpnConnectCount": 0};
final jsonString = jsonEncode(surveyConfig);
await file.writeAsString(jsonString);
appLogger.d("Write init config done $filePath");
}
} catch (e) {
appLogger.e("Error while creating config");
}
}

Future<void> incrementVpnConnectCount() async {
try {
final content = await readSurveyConfig();
final surveyConfig = jsonDecode(content.$2) as Map<String, dynamic>;
// Increment the vpnConnectCount field
surveyConfig['vpnConnectCount'] =
(surveyConfig['vpnConnectCount'] ?? 0) + 1;
final updatedJsonString = jsonEncode(surveyConfig);
await content.$1.writeAsString(updatedJsonString);
appLogger.i('vpnConnectCount updated successfully.');
} catch (e) {
appLogger.i('Failed to update vpnConnectCount: $e');
}
}

Future<bool> surveyAvailable() async {
try {
final content = await readSurveyConfig();
final Map<String, dynamic> surveyConfig = jsonDecode(content.$2);
final vpnConnectCount = surveyConfig['vpnConnectCount'] ?? 0;
appLogger.i('Survey config. ${surveyConfig.toString()}');
if (vpnConnectCount >= _VPNCONNECTED_COUNT) {
appLogger.d('Survey is available.');
return true;
}
appLogger.i('Survey is not available.');
return false;
} catch (e) {
appLogger.e('Failed to check survey availability: $e');
return false;
}
}

//this read survey config method will return file and string
Future<(File, String)> readSurveyConfig() async {
final filePath = await _surveyConfigPath;
final file = File(filePath);
final content = await file.readAsString();
return (file, content);
}
}
1 change: 1 addition & 0 deletions lib/core/service/websocket_subscriber.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class WebsocketSubscriber {

sessionModel.isAuthEnabled.value = config.authEnabled;
sessionModel.configNotifier.value = config;
sessionModel.country.value = config.country;
_updatePlans(config.plans);
_updatePaymentMethods(config.paymentMethods);
break;
Expand Down
5 changes: 2 additions & 3 deletions lib/core/utils/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,9 @@ final appLogger = Logger(
errorMethodCount: 5,
colors: true,
printEmojis: true,
printTime: true,

),
filter: ProductionFilter(),
output: ConsoleOutput(),
level: Level.debug,
);

bool isMobile() {
Expand Down
42 changes: 22 additions & 20 deletions lib/core/utils/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class ConfigOptions {

final Map<String, PaymentMethod>? paymentMethods;
final _ChatOptions chat;
final String country;

ConfigOptions({
this.developmentMode = false,
Expand All @@ -62,10 +63,11 @@ class ConfigOptions {
this.httpProxyAddr = '',
this.socksProxyAddr = '',
this.deviceId = '',
this.plans = null,
this.paymentMethods = null,
this.plans,
this.paymentMethods,
required this.devices,
this.chat = const _ChatOptions(),
required this.country,
});

bool get startupReady =>
Expand All @@ -83,24 +85,24 @@ class ConfigOptions {
final paymentMethods = paymentMethodsFromJson(parsedJson['paymentMethods']);

return ConfigOptions(
developmentMode: parsedJson['developmentMode'],
authEnabled: parsedJson['authEnabled'],
chatEnabled: parsedJson['chatEnabled'],
httpProxyAddr: parsedJson['httpProxyAddr'],
socksProxyAddr: parsedJson['socksProxyAddr'],
splitTunneling: parsedJson['splitTunneling'],
hasSucceedingProxy: parsedJson['hasSucceedingProxy'],
fetchedGlobalConfig: parsedJson['fetchedGlobalConfig'],
fetchedProxiesConfig: parsedJson['fetchedProxiesConfig'],
plans: plans,
chat: _ChatOptions.fromJson(parsedJson['chat']),
paymentMethods: paymentMethods,
devices: _parseDevices(parsedJson),
replicaAddr: parsedJson['replicaAddr'].toString(),
deviceId: parsedJson['deviceId'].toString(),
expirationDate: parsedJson['expirationDate'].toString(),
sdkVersion: parsedJson['sdkVersion'].toString(),
);
developmentMode: parsedJson['developmentMode'],
authEnabled: parsedJson['authEnabled'],
chatEnabled: parsedJson['chatEnabled'],
httpProxyAddr: parsedJson['httpProxyAddr'],
socksProxyAddr: parsedJson['socksProxyAddr'],
splitTunneling: parsedJson['splitTunneling'],
hasSucceedingProxy: parsedJson['hasSucceedingProxy'],
fetchedGlobalConfig: parsedJson['fetchedGlobalConfig'],
fetchedProxiesConfig: parsedJson['fetchedProxiesConfig'],
plans: plans,
chat: _ChatOptions.fromJson(parsedJson['chat']),
paymentMethods: paymentMethods,
devices: _parseDevices(parsedJson),
replicaAddr: parsedJson['replicaAddr'].toString(),
deviceId: parsedJson['deviceId'].toString(),
expirationDate: parsedJson['expirationDate'].toString(),
sdkVersion: parsedJson['sdkVersion'].toString(),
country: parsedJson['country'] ?? "");
}

static Devices _parseDevices(Map json) {
Expand Down
2 changes: 1 addition & 1 deletion lib/features/account/report_issue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:lantern/core/utils/common.dart';
class ReportIssue extends StatefulWidget {
final String? description;

const ReportIssue({Key? key, this.description}) : super(key: key);
const ReportIssue({super.key, this.description});

@override
State<ReportIssue> createState() => _ReportIssueState();
Expand Down
Loading