@ -10,7 +10,6 @@ import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.tailscale.ipn.UninitializedApp.Companion.STATUS_CHANNEL_ID
import com.tailscale.ipn.UninitializedApp.Companion.STATUS_EXIT_NODE_FAILURE_NOTIFICATION_ID
import com.tailscale.ipn.ui.localapi.Client
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.notifier.Notifier
@ -18,95 +17,97 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
class UseExitNodeWorker (
appContext : Context ,
workerParams : WorkerParameters
) : CoroutineWorker ( appContext , workerParams ) {
override suspend fun doWork ( ) : Result {
val app = UninitializedApp . get ( )
suspend fun runAndGetResult ( ) : String ? {
val exitNodeName = inputData . getString ( EXIT _NODE _NAME )
val exitNodeId = if ( exitNodeName . isNullOrEmpty ( ) ) {
null
} else {
if ( ! app . isAbleToStartVPN ( ) ) {
return app . getString ( R . string . vpn _is _not _ready _to _start )
}
val peers =
( Notifier . netmap . value
?: run { return @runAndGetResult app . getString ( R . string . tailscale _is _not _setup ) } )
. Peers ?: run { return @runAndGetResult app . getString ( R . string . no _peers _found ) }
val filteredPeers = peers . filter {
it . displayName == exitNodeName
} . toList ( )
if ( filteredPeers . isEmpty ( ) ) {
return app . getString ( R . string . no _peers _with _name _found , exitNodeName )
} else if ( filteredPeers . size > 1 ) {
return app . getString ( R . string . multiple _peers _with _name _found , exitNodeName )
} else if ( ! filteredPeers [ 0 ] . isExitNode ) {
return app . getString (
R . string . peer _with _name _is _not _an _exit _node ,
exitNodeName
)
}
filteredPeers [ 0 ] . StableID
}
class UseExitNodeWorker ( appContext : Context , workerParams : WorkerParameters ) :
CoroutineWorker ( appContext , workerParams ) {
override suspend fun doWork ( ) : Result {
val app = UninitializedApp . get ( ) ?: return Result . failure ( )
val allowLanAccess = inputData . getBoolean ( ALLOW _LAN _ACCESS , false )
val prefsOut = Ipn . MaskedPrefs ( )
prefsOut . ExitNodeID = exitNodeId
prefsOut . ExitNodeAllowLANAccess = allowLanAccess
val scope = CoroutineScope ( Dispatchers . Default + Job ( ) )
var result : String ? = null
Client ( scope ) . editPrefs ( prefsOut ) {
result = if ( it . isFailure ) {
it . exceptionOrNull ( ) ?. message
} else {
null
}
}
suspend fun runAndGetResult ( ) : String ? {
val exitNodeName = inputData . getString ( EXIT _NODE _NAME )
scope . coroutineContext [ Job ] ?. join ( )
val exitNodeId =
if ( exitNodeName . isNullOrEmpty ( ) ) {
null
} else {
if ( app . isAbleToStartVPN ( ) != true ) {
return app . getString ( R . string . vpn _is _not _ready _to _start )
}
return result
}
val peers =
( Notifier . netmap . value
?: run {
return @runAndGetResult app . getString ( R . string . tailscale _is _not _setup )
} )
. Peers
?: run {
return @runAndGetResult app . getString ( R . string . no _peers _found )
}
val filteredPeers = peers . filter { it . displayName == exitNodeName } . toList ( )
if ( filteredPeers . isEmpty ( ) ) {
return app . getString ( R . string . no _peers _with _name _found , exitNodeName )
} else if ( filteredPeers . size > 1 ) {
return app . getString ( R . string . multiple _peers _with _name _found , exitNodeName )
} else if ( ! filteredPeers [ 0 ] . isExitNode ) {
return app . getString ( R . string . peer _with _name _is _not _an _exit _node , exitNodeName )
}
val result = runAndGetResult ( )
filteredPeers [ 0 ] . StableID
}
return if ( result != null ) {
val intent =
Intent ( app , MainActivity :: class . java ) . apply {
flags = Intent . FLAG _ACTIVITY _NEW _TASK or Intent . FLAG _ACTIVITY _CLEAR _TASK
}
val pendingIntent : PendingIntent =
PendingIntent . getActivity (
app , 1 , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
val allowLanAccess = inputData . getBoolean ( ALLOW _LAN _ACCESS , false )
val prefsOut = Ipn . MaskedPrefs ( )
prefsOut . ExitNodeID = exitNodeId
prefsOut . ExitNodeAllowLANAccess = allowLanAccess
val notification = NotificationCompat . Builder ( app , STATUS _CHANNEL _ID )
. setSmallIcon ( R . drawable . ic _notification )
. setContentTitle ( app . getString ( R . string . use _exit _node _intent _failed ) )
. setContentText ( result )
. setPriority ( NotificationCompat . PRIORITY _DEFAULT )
. setContentIntent ( pendingIntent )
. build ( )
val scope = CoroutineScope ( Dispatchers . Default + Job ( ) )
var result : String ? = null
Client ( scope ) . editPrefs ( prefsOut ) {
result =
if ( it . isFailure ) {
it . exceptionOrNull ( ) ?. message
} else {
null
}
}
app . notifyStatus ( notification )
scope . coroutineContext [ Job ] ?. join ( )
Result . failure ( Data . Builder ( ) . putString ( ERROR _KEY , result ) . build ( ) )
} else {
Result . success ( )
}
return result
}
companion object {
const val EXIT _NODE _NAME = " EXIT_NODE_NAME "
const val ALLOW _LAN _ACCESS = " ALLOW_LAN_ACCESS "
const val ERROR _KEY = " error "
val result = runAndGetResult ( )
return if ( result != null ) {
val intent =
Intent ( app , MainActivity :: class . java ) . apply {
flags = Intent . FLAG _ACTIVITY _NEW _TASK or Intent . FLAG _ACTIVITY _CLEAR _TASK
}
val pendingIntent : PendingIntent =
PendingIntent . getActivity (
app , 1 , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
val notification =
NotificationCompat . Builder ( app , STATUS _CHANNEL _ID )
. setSmallIcon ( R . drawable . ic _notification )
. setContentTitle ( app . getString ( R . string . use _exit _node _intent _failed ) )
. setContentText ( result )
. setPriority ( NotificationCompat . PRIORITY _DEFAULT )
. setContentIntent ( pendingIntent )
. build ( )
app . notifyStatus ( notification )
Result . failure ( Data . Builder ( ) . putString ( ERROR _KEY , result ) . build ( ) )
} else {
Result . success ( )
}
}
companion object {
const val EXIT _NODE _NAME = " EXIT_NODE_NAME "
const val ALLOW _LAN _ACCESS = " ALLOW_LAN_ACCESS "
const val ERROR _KEY = " error "
}
}