Browse Source
Merge pull request #8 from vernu/feature/receive-sms
Merge pull request #8 from vernu/feature/receive-sms
Receive Messages Featurepull/19/head v2.3.0
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 1822 additions and 425 deletions
-
14README.md
-
17android/app/build.gradle
-
34android/app/src/main/AndroidManifest.xml
-
33android/app/src/main/java/com/vernu/sms/ApiManager.java
-
19android/app/src/main/java/com/vernu/sms/AppConstants.java
-
53android/app/src/main/java/com/vernu/sms/TextBeeUtils.java
-
254android/app/src/main/java/com/vernu/sms/activities/MainActivity.java
-
25android/app/src/main/java/com/vernu/sms/database/local/AppDatabase.java
-
17android/app/src/main/java/com/vernu/sms/database/local/DateConverter.java
-
193android/app/src/main/java/com/vernu/sms/database/local/SMS.java
-
27android/app/src/main/java/com/vernu/sms/database/local/SMSDao.java
-
6android/app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java
-
42android/app/src/main/java/com/vernu/sms/dtos/SMSDTO.java
-
9android/app/src/main/java/com/vernu/sms/dtos/SMSForwardResponseDTO.java
-
7android/app/src/main/java/com/vernu/sms/helpers/SharedPreferenceHelper.java
-
25android/app/src/main/java/com/vernu/sms/models/SMSPayload.java
-
21android/app/src/main/java/com/vernu/sms/receivers/BootCompletedReceiver.java
-
101android/app/src/main/java/com/vernu/sms/receivers/SMSBroadcastReceiver.java
-
16android/app/src/main/java/com/vernu/sms/services/FCMService.java
-
11android/app/src/main/java/com/vernu/sms/services/GatewayApiService.java
-
82android/app/src/main/java/com/vernu/sms/services/StickyNotificationService.java
-
184android/app/src/main/res/layout/activity_main.xml
-
1api/.env.example
-
17api/src/auth/auth.controller.ts
-
7api/src/auth/auth.module.ts
-
37api/src/auth/auth.service.ts
-
27api/src/auth/guards/auth.guard.ts
-
31api/src/auth/schemas/access-log.schema.ts
-
6api/src/auth/schemas/api-key.schema.ts
-
62api/src/gateway/gateway.controller.ts
-
134api/src/gateway/gateway.dto.ts
-
5api/src/gateway/gateway.module.ts
-
112api/src/gateway/gateway.service.ts
-
3api/src/gateway/schemas/device.schema.ts
-
45api/src/gateway/schemas/sms.schema.ts
-
4api/src/gateway/sms-type.enum.ts
-
5api/src/main.ts
-
2web/components/Footer.tsx
-
18web/components/Navbar.tsx
-
29web/components/dashboard/APIKeyAndDevices.tsx
-
2web/components/dashboard/ApiKeyList.tsx
-
2web/components/dashboard/DeviceList.tsx
-
170web/components/dashboard/ReceiveSMS.tsx
-
101web/components/dashboard/SendSMS.tsx
-
76web/components/dashboard/UserStats.tsx
-
9web/components/dashboard/UserStatsCard.tsx
-
10web/components/landing/CodeSnippetSection.tsx
-
2web/components/landing/DownloadAppSection.tsx
-
2web/components/landing/howItWorksContent.ts
-
16web/components/meta/Meta.tsx
-
2web/next.config.js
-
68web/pages/dashboard.tsx
-
5web/services/authService.ts
-
5web/services/gatewayService.ts
-
4web/services/types.ts
-
35web/store/deviceSlice.ts
-
3web/store/statsSlice.ts
@ -0,0 +1,33 @@ |
|||||
|
package com.vernu.sms; |
||||
|
|
||||
|
import com.vernu.sms.services.GatewayApiService; |
||||
|
|
||||
|
import retrofit2.Retrofit; |
||||
|
import retrofit2.converter.gson.GsonConverterFactory; |
||||
|
|
||||
|
public class ApiManager { |
||||
|
private static GatewayApiService apiService; |
||||
|
|
||||
|
public static GatewayApiService getApiService() { |
||||
|
if (apiService == null) { |
||||
|
apiService = createApiService(); |
||||
|
} |
||||
|
return apiService; |
||||
|
} |
||||
|
|
||||
|
private static GatewayApiService createApiService() { |
||||
|
// OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); |
||||
|
// HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); |
||||
|
// loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); |
||||
|
// httpClient.addInterceptor(loggingInterceptor); |
||||
|
|
||||
|
Retrofit retrofit = new Retrofit.Builder() |
||||
|
.baseUrl(AppConstants.API_BASE_URL) |
||||
|
// .client(httpClient.build()) |
||||
|
.addConverterFactory(GsonConverterFactory.create()) |
||||
|
.build(); |
||||
|
apiService = retrofit.create(GatewayApiService.class); |
||||
|
|
||||
|
return retrofit.create(GatewayApiService.class); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.vernu.sms; |
||||
|
|
||||
|
import android.Manifest; |
||||
|
|
||||
|
public class AppConstants { |
||||
|
public static final String API_BASE_URL = "https://api.textbee.dev/api/v1/"; |
||||
|
public static final String[] requiredPermissions = new String[]{ |
||||
|
Manifest.permission.SEND_SMS, |
||||
|
Manifest.permission.READ_SMS, |
||||
|
Manifest.permission.RECEIVE_SMS, |
||||
|
Manifest.permission.READ_PHONE_STATE |
||||
|
}; |
||||
|
public static final String SHARED_PREFS_DEVICE_ID_KEY = "DEVICE_ID"; |
||||
|
public static final String SHARED_PREFS_API_KEY_KEY = "API_KEY"; |
||||
|
public static final String SHARED_PREFS_GATEWAY_ENABLED_KEY = "GATEWAY_ENABLED"; |
||||
|
public static final String SHARED_PREFS_PREFERRED_SIM_KEY = "PREFERRED_SIM"; |
||||
|
public static final String SHARED_PREFS_RECEIVE_SMS_ENABLED_KEY = "RECEIVE_SMS_ENABLED"; |
||||
|
public static final String SHARED_PREFS_TRACK_SENT_SMS_STATUS_KEY = "TRACK_SENT_SMS_STATUS"; |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
package com.vernu.sms; |
||||
|
|
||||
|
import android.Manifest; |
||||
|
import android.content.Context; |
||||
|
import android.content.Intent; |
||||
|
import android.content.pm.PackageManager; |
||||
|
import android.os.Build; |
||||
|
import android.telephony.SubscriptionInfo; |
||||
|
import android.telephony.SubscriptionManager; |
||||
|
|
||||
|
import androidx.core.app.ActivityCompat; |
||||
|
import androidx.core.content.ContextCompat; |
||||
|
|
||||
|
import com.vernu.sms.services.StickyNotificationService; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class TextBeeUtils { |
||||
|
public static boolean isPermissionGranted(Context context, String permission) { |
||||
|
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; |
||||
|
} |
||||
|
|
||||
|
public static List<SubscriptionInfo> getAvailableSimSlots(Context context) { |
||||
|
|
||||
|
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
|
||||
|
SubscriptionManager subscriptionManager = SubscriptionManager.from(context); |
||||
|
return subscriptionManager.getActiveSubscriptionInfoList(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public static void startStickyNotificationService(Context context) { |
||||
|
|
||||
|
if(!isPermissionGranted(context, Manifest.permission.RECEIVE_SMS)){ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Intent notificationIntent = new Intent(context, StickyNotificationService.class); |
||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||
|
context.startForegroundService(notificationIntent); |
||||
|
} else { |
||||
|
context.startService(notificationIntent); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void stopStickyNotificationService(Context context) { |
||||
|
Intent notificationIntent = new Intent(context, StickyNotificationService.class); |
||||
|
context.stopService(notificationIntent); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
//package com.vernu.sms.database.local; |
||||
|
// |
||||
|
//import android.content.Context; |
||||
|
//import androidx.room.Database; |
||||
|
//import androidx.room.Room; |
||||
|
//import androidx.room.RoomDatabase; |
||||
|
// |
||||
|
//@Database(entities = {SMS.class}, version = 2) |
||||
|
//public abstract class AppDatabase extends RoomDatabase { |
||||
|
// private static volatile AppDatabase INSTANCE; |
||||
|
// |
||||
|
// public static AppDatabase getInstance(Context context) { |
||||
|
// if (INSTANCE == null) { |
||||
|
// synchronized (AppDatabase.class) { |
||||
|
// if (INSTANCE == null) { |
||||
|
// INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "db1") |
||||
|
// .build(); |
||||
|
// } |
||||
|
// } |
||||
|
// } |
||||
|
// return INSTANCE; |
||||
|
// } |
||||
|
// |
||||
|
// public abstract SMSDao localReceivedSMSDao(); |
||||
|
//} |
||||
@ -0,0 +1,17 @@ |
|||||
|
//package com.vernu.sms.database.local; |
||||
|
// |
||||
|
//import androidx.room.TypeConverter; |
||||
|
// |
||||
|
//import java.util.Date; |
||||
|
// |
||||
|
//public class DateConverter { |
||||
|
// @TypeConverter |
||||
|
// public static Date toDate(Long dateLong) { |
||||
|
// return dateLong == null ? null : new Date(dateLong); |
||||
|
// } |
||||
|
// |
||||
|
// @TypeConverter |
||||
|
// public static Long fromDate(Date date) { |
||||
|
// return date == null ? null : date.getTime(); |
||||
|
// } |
||||
|
//} |
||||
@ -0,0 +1,193 @@ |
|||||
|
//package com.vernu.sms.database.local; |
||||
|
// |
||||
|
//import androidx.annotation.NonNull; |
||||
|
//import androidx.room.ColumnInfo; |
||||
|
//import androidx.room.Entity; |
||||
|
//import androidx.room.PrimaryKey; |
||||
|
//import androidx.room.TypeConverters; |
||||
|
// |
||||
|
//import java.util.Date; |
||||
|
// |
||||
|
//@Entity(tableName = "sms") |
||||
|
//@TypeConverters(DateConverter.class) |
||||
|
//public class SMS { |
||||
|
// |
||||
|
// public SMS() { |
||||
|
// type = null; |
||||
|
// } |
||||
|
// |
||||
|
// @PrimaryKey(autoGenerate = true) |
||||
|
// private int id; |
||||
|
// |
||||
|
// // This is the ID of the SMS in the server |
||||
|
// @ColumnInfo(name = "_id") |
||||
|
// private String _id; |
||||
|
// |
||||
|
// @ColumnInfo(name = "message") |
||||
|
// private String message = ""; |
||||
|
// |
||||
|
// @ColumnInfo(name = "encrypted_message") |
||||
|
// private String encryptedMessage = ""; |
||||
|
// |
||||
|
// @ColumnInfo(name = "is_encrypted", defaultValue = "0") |
||||
|
// private boolean isEncrypted = false; |
||||
|
// |
||||
|
// @ColumnInfo(name = "sender") |
||||
|
// private String sender; |
||||
|
// |
||||
|
// @ColumnInfo(name = "recipient") |
||||
|
// private String recipient; |
||||
|
// |
||||
|
// @ColumnInfo(name = "requested_at") |
||||
|
// private Date requestedAt; |
||||
|
// |
||||
|
// @ColumnInfo(name = "sent_at") |
||||
|
// private Date sentAt; |
||||
|
// |
||||
|
// @ColumnInfo(name = "delivered_at") |
||||
|
// private Date deliveredAt; |
||||
|
// |
||||
|
// @ColumnInfo(name = "received_at") |
||||
|
// private Date receivedAt; |
||||
|
// |
||||
|
// @NonNull |
||||
|
// @ColumnInfo(name = "type") |
||||
|
// private String type; |
||||
|
// |
||||
|
// @ColumnInfo(name = "server_acknowledged_at") |
||||
|
// private Date serverAcknowledgedAt; |
||||
|
// |
||||
|
// public boolean hasServerAcknowledged() { |
||||
|
// return serverAcknowledgedAt != null; |
||||
|
// } |
||||
|
// |
||||
|
// @ColumnInfo(name = "last_acknowledged_request_at") |
||||
|
// private Date lastAcknowledgedRequestAt; |
||||
|
// |
||||
|
// @ColumnInfo(name = "retry_count", defaultValue = "0") |
||||
|
// private int retryCount = 0; |
||||
|
// |
||||
|
// public int getId() { |
||||
|
// return id; |
||||
|
// } |
||||
|
// |
||||
|
// public void setId(int id) { |
||||
|
// this.id = id; |
||||
|
// } |
||||
|
// |
||||
|
// public String get_id() { |
||||
|
// return _id; |
||||
|
// } |
||||
|
// |
||||
|
// public void set_id(String _id) { |
||||
|
// this._id = _id; |
||||
|
// } |
||||
|
// |
||||
|
// public String getMessage() { |
||||
|
// return message; |
||||
|
// } |
||||
|
// |
||||
|
// public void setMessage(String message) { |
||||
|
// this.message = message; |
||||
|
// } |
||||
|
// |
||||
|
// public String getEncryptedMessage() { |
||||
|
// return encryptedMessage; |
||||
|
// } |
||||
|
// |
||||
|
// public void setEncryptedMessage(String encryptedMessage) { |
||||
|
// this.encryptedMessage = encryptedMessage; |
||||
|
// } |
||||
|
// |
||||
|
// public boolean getIsEncrypted() { |
||||
|
// return isEncrypted; |
||||
|
// } |
||||
|
// |
||||
|
// public void setIsEncrypted(boolean isEncrypted) { |
||||
|
// this.isEncrypted = isEncrypted; |
||||
|
// } |
||||
|
// |
||||
|
// public String getSender() { |
||||
|
// return sender; |
||||
|
// } |
||||
|
// |
||||
|
// public void setSender(String sender) { |
||||
|
// this.sender = sender; |
||||
|
// } |
||||
|
// |
||||
|
// public String getRecipient() { |
||||
|
// return recipient; |
||||
|
// } |
||||
|
// |
||||
|
// public void setRecipient(String recipient) { |
||||
|
// this.recipient = recipient; |
||||
|
// } |
||||
|
// |
||||
|
// public Date getServerAcknowledgedAt() { |
||||
|
// return serverAcknowledgedAt; |
||||
|
// } |
||||
|
// |
||||
|
// public void setServerAcknowledgedAt(Date serverAcknowledgedAt) { |
||||
|
// this.serverAcknowledgedAt = serverAcknowledgedAt; |
||||
|
// } |
||||
|
// |
||||
|
// |
||||
|
// |
||||
|
// public Date getRequestedAt() { |
||||
|
// return requestedAt; |
||||
|
// } |
||||
|
// |
||||
|
// public void setRequestedAt(Date requestedAt) { |
||||
|
// this.requestedAt = requestedAt; |
||||
|
// } |
||||
|
// |
||||
|
// public Date getSentAt() { |
||||
|
// return sentAt; |
||||
|
// } |
||||
|
// |
||||
|
// public void setSentAt(Date sentAt) { |
||||
|
// this.sentAt = sentAt; |
||||
|
// } |
||||
|
// |
||||
|
// public Date getDeliveredAt() { |
||||
|
// return deliveredAt; |
||||
|
// } |
||||
|
// |
||||
|
// public void setDeliveredAt(Date deliveredAt) { |
||||
|
// this.deliveredAt = deliveredAt; |
||||
|
// } |
||||
|
// |
||||
|
// public Date getReceivedAt() { |
||||
|
// return receivedAt; |
||||
|
// } |
||||
|
// |
||||
|
// public void setReceivedAt(Date receivedAt) { |
||||
|
// this.receivedAt = receivedAt; |
||||
|
// } |
||||
|
// |
||||
|
// @NonNull |
||||
|
// public String getType() { |
||||
|
// return type; |
||||
|
// } |
||||
|
// |
||||
|
// public void setType(@NonNull String type) { |
||||
|
// this.type = type; |
||||
|
// } |
||||
|
// |
||||
|
// |
||||
|
// public Date getLastAcknowledgedRequestAt() { |
||||
|
// return lastAcknowledgedRequestAt; |
||||
|
// } |
||||
|
// |
||||
|
// public void setLastAcknowledgedRequestAt(Date lastAcknowledgedRequestAt) { |
||||
|
// this.lastAcknowledgedRequestAt = lastAcknowledgedRequestAt; |
||||
|
// } |
||||
|
// |
||||
|
// public int getRetryCount() { |
||||
|
// return retryCount; |
||||
|
// } |
||||
|
// |
||||
|
// public void setRetryCount(int retryCount) { |
||||
|
// this.retryCount = retryCount; |
||||
|
// } |
||||
|
//} |
||||
@ -0,0 +1,27 @@ |
|||||
|
//package com.vernu.sms.database.local; |
||||
|
// |
||||
|
//import androidx.room.Dao; |
||||
|
//import androidx.room.Delete; |
||||
|
//import androidx.room.Insert; |
||||
|
//import androidx.room.OnConflictStrategy; |
||||
|
//import androidx.room.Query; |
||||
|
// |
||||
|
//import java.util.List; |
||||
|
// |
||||
|
//@Dao |
||||
|
//public interface SMSDao { |
||||
|
// |
||||
|
// @Query("SELECT * FROM sms") |
||||
|
// List<SMS> getAll(); |
||||
|
// |
||||
|
// @Query("SELECT * FROM sms WHERE id IN (:smsIds)") |
||||
|
// List<SMS> loadAllByIds(int[] smsIds); |
||||
|
// |
||||
|
// @Insert(onConflict = OnConflictStrategy.REPLACE) |
||||
|
// void insertAll(SMS... sms); |
||||
|
// |
||||
|
// |
||||
|
// @Delete |
||||
|
// void delete(SMS sms); |
||||
|
// |
||||
|
//} |
||||
@ -0,0 +1,42 @@ |
|||||
|
package com.vernu.sms.dtos; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
public class SMSDTO { |
||||
|
private String sender; |
||||
|
private String message = ""; |
||||
|
private Date receivedAt; |
||||
|
|
||||
|
public SMSDTO() { |
||||
|
} |
||||
|
|
||||
|
public SMSDTO(String sender, String message, Date receivedAt) { |
||||
|
this.sender = sender; |
||||
|
this.message = message; |
||||
|
this.receivedAt = receivedAt; |
||||
|
} |
||||
|
|
||||
|
public String getSender() { |
||||
|
return sender; |
||||
|
} |
||||
|
|
||||
|
public void setSender(String sender) { |
||||
|
this.sender = sender; |
||||
|
} |
||||
|
|
||||
|
public String getMessage() { |
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
public void setMessage(String message) { |
||||
|
this.message = message; |
||||
|
} |
||||
|
|
||||
|
public Date getReceivedAt() { |
||||
|
return receivedAt; |
||||
|
} |
||||
|
|
||||
|
public void setReceivedAt(Date receivedAt) { |
||||
|
this.receivedAt = receivedAt; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
package com.vernu.sms.dtos; |
||||
|
|
||||
|
public class SMSForwardResponseDTO { |
||||
|
|
||||
|
public SMSForwardResponseDTO() { |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -1,27 +1,30 @@ |
|||||
package com.vernu.sms.models; |
package com.vernu.sms.models; |
||||
|
|
||||
public class SMSPayload { |
public class SMSPayload { |
||||
|
|
||||
|
private String[] recipients; |
||||
|
private String message; |
||||
|
|
||||
|
// Legacy fields that are no longer used |
||||
private String[] receivers; |
private String[] receivers; |
||||
private String smsBody; |
private String smsBody; |
||||
|
|
||||
public SMSPayload(String[] receivers, String smsBody) { |
|
||||
this.receivers = receivers; |
|
||||
this.smsBody = smsBody; |
|
||||
|
public SMSPayload() { |
||||
} |
} |
||||
|
|
||||
public String[] getReceivers() { |
|
||||
return receivers; |
|
||||
|
public String[] getRecipients() { |
||||
|
return recipients; |
||||
} |
} |
||||
|
|
||||
public void setReceivers(String[] receivers) { |
|
||||
this.receivers = receivers; |
|
||||
|
public void setRecipients(String[] recipients) { |
||||
|
this.recipients = recipients; |
||||
} |
} |
||||
|
|
||||
public String getSmsBody() { |
|
||||
return smsBody; |
|
||||
|
public String getMessage() { |
||||
|
return message; |
||||
} |
} |
||||
|
|
||||
public void setSmsBody(String smsBody) { |
|
||||
this.smsBody = smsBody; |
|
||||
|
public void setMessage(String message) { |
||||
|
this.message = message; |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.vernu.sms.receivers; |
||||
|
|
||||
|
import android.Manifest; |
||||
|
import android.content.BroadcastReceiver; |
||||
|
import android.content.Context; |
||||
|
import android.content.Intent; |
||||
|
import android.os.Build; |
||||
|
|
||||
|
import com.vernu.sms.TextBeeUtils; |
||||
|
import com.vernu.sms.services.StickyNotificationService; |
||||
|
|
||||
|
public class BootCompletedReceiver extends BroadcastReceiver { |
||||
|
@Override |
||||
|
public void onReceive(Context context, Intent intent) { |
||||
|
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { |
||||
|
// if(TextBeeUtils.isPermissionGranted(context, Manifest.permission.RECEIVE_SMS)){ |
||||
|
// TextBeeUtils.startStickyNotificationService(context); |
||||
|
// } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
package com.vernu.sms.receivers; |
||||
|
|
||||
|
import android.content.BroadcastReceiver; |
||||
|
import android.content.Context; |
||||
|
import android.content.Intent; |
||||
|
import android.provider.Telephony; |
||||
|
import android.telephony.SmsMessage; |
||||
|
import android.util.Log; |
||||
|
import com.vernu.sms.ApiManager; |
||||
|
import com.vernu.sms.AppConstants; |
||||
|
import com.vernu.sms.dtos.SMSDTO; |
||||
|
import com.vernu.sms.dtos.SMSForwardResponseDTO; |
||||
|
import com.vernu.sms.helpers.SharedPreferenceHelper; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.Objects; |
||||
|
|
||||
|
import retrofit2.Call; |
||||
|
import retrofit2.Response; |
||||
|
|
||||
|
public class SMSBroadcastReceiver extends BroadcastReceiver { |
||||
|
private static final String TAG = "SMSBroadcastReceiver"; |
||||
|
|
||||
|
@Override |
||||
|
public void onReceive(Context context, Intent intent) { |
||||
|
Log.d(TAG, "onReceive: " + intent.getAction()); |
||||
|
|
||||
|
if (!Objects.equals(intent.getAction(), Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) { |
||||
|
Log.d(TAG, "Not Valid intent"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent); |
||||
|
if (messages == null) { |
||||
|
Log.d(TAG, "No messages found"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String deviceId = SharedPreferenceHelper.getSharedPreferenceString(context, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, ""); |
||||
|
String apiKey = SharedPreferenceHelper.getSharedPreferenceString(context, AppConstants.SHARED_PREFS_API_KEY_KEY, ""); |
||||
|
boolean receiveSMSEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean(context, AppConstants.SHARED_PREFS_RECEIVE_SMS_ENABLED_KEY, false); |
||||
|
|
||||
|
if (deviceId.isEmpty() || apiKey.isEmpty() || !receiveSMSEnabled) { |
||||
|
Log.d(TAG, "Device ID or API Key is empty or Receive SMS Feature is disabled"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// SMS receivedSMS = new SMS(); |
||||
|
// receivedSMS.setType("RECEIVED"); |
||||
|
// for (SmsMessage message : messages) { |
||||
|
// receivedSMS.setMessage(receivedSMS.getMessage() + message.getMessageBody()); |
||||
|
// receivedSMS.setSender(message.getOriginatingAddress()); |
||||
|
// receivedSMS.setReceivedAt(new Date(message.getTimestampMillis())); |
||||
|
// } |
||||
|
|
||||
|
SMSDTO receivedSMSDTO = new SMSDTO(); |
||||
|
|
||||
|
for (SmsMessage message : messages) { |
||||
|
receivedSMSDTO.setMessage(receivedSMSDTO.getMessage() + message.getMessageBody()); |
||||
|
receivedSMSDTO.setSender(message.getOriginatingAddress()); |
||||
|
receivedSMSDTO.setReceivedAt(new Date(message.getTimestampMillis())); |
||||
|
} |
||||
|
// receivedSMSDTO.setSender(receivedSMS.getSender()); |
||||
|
// receivedSMSDTO.setMessage(receivedSMS.getMessage()); |
||||
|
// receivedSMSDTO.setReceivedAt(receivedSMS.getReceivedAt()); |
||||
|
|
||||
|
Call<SMSForwardResponseDTO> apiCall = ApiManager.getApiService().sendReceivedSMS(deviceId, apiKey, receivedSMSDTO); |
||||
|
apiCall.enqueue(new retrofit2.Callback<SMSForwardResponseDTO>() { |
||||
|
@Override |
||||
|
public void onResponse(Call<SMSForwardResponseDTO> call, Response<SMSForwardResponseDTO> response) { |
||||
|
// Date now = new Date(); |
||||
|
if (response.isSuccessful()) { |
||||
|
Log.d(TAG, "SMS sent to server successfully"); |
||||
|
// receivedSMS.setLastAcknowledgedRequestAt(now); |
||||
|
// receivedSMS.setServerAcknowledgedAt(now); |
||||
|
// updateLocalReceivedSMS(receivedSMS, context); |
||||
|
} else { |
||||
|
Log.e(TAG, "Failed to send SMS to server"); |
||||
|
// receivedSMS.setServerAcknowledgedAt(null); |
||||
|
// receivedSMS.setLastAcknowledgedRequestAt(now); |
||||
|
// receivedSMS.setRetryCount(localReceivedSMS.getRetryCount() + 1); |
||||
|
// updateLocalReceivedSMS(receivedSMS, context); |
||||
|
} |
||||
|
} |
||||
|
@Override |
||||
|
public void onFailure(Call<SMSForwardResponseDTO> call, Throwable t) { |
||||
|
Log.e(TAG, "Failed to send SMS to server", t); |
||||
|
// receivedSMS.setServerAcknowledgedAt(null); |
||||
|
// receivedSMS.setLastAcknowledgedRequestAt(new Date()); |
||||
|
// updateLocalReceivedSMS(receivedSMS, context); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// private void updateLocalReceivedSMS(SMS localReceivedSMS, Context context) { |
||||
|
// Executors.newSingleThreadExecutor().execute(() -> { |
||||
|
// AppDatabase appDatabase = AppDatabase.getInstance(context); |
||||
|
// appDatabase.localReceivedSMSDao().insertAll(localReceivedSMS); |
||||
|
// }); |
||||
|
// } |
||||
|
} |
||||
@ -1,19 +1,24 @@ |
|||||
package com.vernu.sms.services; |
package com.vernu.sms.services; |
||||
|
|
||||
|
import com.vernu.sms.dtos.SMSDTO; |
||||
|
import com.vernu.sms.dtos.SMSForwardResponseDTO; |
||||
import com.vernu.sms.dtos.RegisterDeviceInputDTO; |
import com.vernu.sms.dtos.RegisterDeviceInputDTO; |
||||
import com.vernu.sms.dtos.RegisterDeviceResponseDTO; |
import com.vernu.sms.dtos.RegisterDeviceResponseDTO; |
||||
|
|
||||
import retrofit2.Call; |
import retrofit2.Call; |
||||
import retrofit2.http.Body; |
import retrofit2.http.Body; |
||||
|
import retrofit2.http.Header; |
||||
import retrofit2.http.PATCH; |
import retrofit2.http.PATCH; |
||||
import retrofit2.http.POST; |
import retrofit2.http.POST; |
||||
import retrofit2.http.Path; |
import retrofit2.http.Path; |
||||
import retrofit2.http.Query; |
|
||||
|
|
||||
public interface GatewayApiService { |
public interface GatewayApiService { |
||||
@POST("gateway/devices") |
@POST("gateway/devices") |
||||
Call<RegisterDeviceResponseDTO> registerDevice(@Query("apiKey") String apiKey, @Body() RegisterDeviceInputDTO body); |
|
||||
|
Call<RegisterDeviceResponseDTO> registerDevice(@Header("x-api-key") String apiKey, @Body() RegisterDeviceInputDTO body); |
||||
|
|
||||
@PATCH("gateway/devices/{deviceId}") |
@PATCH("gateway/devices/{deviceId}") |
||||
Call<RegisterDeviceResponseDTO> updateDevice(@Path("deviceId") String deviceId, @Query("apiKey") String apiKey, @Body() RegisterDeviceInputDTO body); |
|
||||
|
Call<RegisterDeviceResponseDTO> updateDevice(@Path("deviceId") String deviceId, @Header("x-api-key") String apiKey, @Body() RegisterDeviceInputDTO body); |
||||
|
|
||||
|
@POST("gateway/devices/{deviceId}/receiveSMS") |
||||
|
Call<SMSForwardResponseDTO> sendReceivedSMS(@Path("deviceId") String deviceId, @Header("x-api-key") String apiKey, @Body() SMSDTO body); |
||||
} |
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
package com.vernu.sms.services; |
||||
|
|
||||
|
import android.app.*; |
||||
|
import android.content.BroadcastReceiver; |
||||
|
import android.content.Context; |
||||
|
import android.content.Intent; |
||||
|
import android.content.IntentFilter; |
||||
|
import android.os.IBinder; |
||||
|
import android.provider.Telephony; |
||||
|
import android.util.Log; |
||||
|
import android.widget.Toast; |
||||
|
|
||||
|
import androidx.core.app.NotificationCompat; |
||||
|
|
||||
|
import com.vernu.sms.R; |
||||
|
import com.vernu.sms.activities.MainActivity; |
||||
|
import com.vernu.sms.receivers.SMSBroadcastReceiver; |
||||
|
|
||||
|
public class StickyNotificationService extends Service { |
||||
|
|
||||
|
private static final String TAG = "StickyNotificationService"; |
||||
|
private final BroadcastReceiver receiver = new SMSBroadcastReceiver(); |
||||
|
|
||||
|
@Override |
||||
|
public IBinder onBind(Intent intent) { |
||||
|
Log.i(TAG, "Service onBind " + intent.getAction()); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onCreate() { |
||||
|
super.onCreate(); |
||||
|
Log.i(TAG, "Service Started"); |
||||
|
|
||||
|
|
||||
|
// IntentFilter filter = new IntentFilter(); |
||||
|
// filter.addAction(Telephony.Sms.Intents.SMS_RECEIVED_ACTION); |
||||
|
// filter.addAction(android.telephony.TelephonyManager.ACTION_PHONE_STATE_CHANGED); |
||||
|
// registerReceiver(receiver, filter); |
||||
|
// |
||||
|
// Notification notification = createNotification(); |
||||
|
// startForeground(1, notification); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int onStartCommand(Intent intent, int flags, int startId) { |
||||
|
Log.i(TAG, "Received start id " + startId + ": " + intent); |
||||
|
return START_STICKY; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onDestroy() { |
||||
|
super.onDestroy(); |
||||
|
// unregisterReceiver(receiver); |
||||
|
Log.i(TAG, "StickyNotificationService destroyed"); |
||||
|
// Toast.makeText(this, "Service destroyed", Toast.LENGTH_SHORT).show(); |
||||
|
} |
||||
|
|
||||
|
private Notification createNotification() { |
||||
|
String notificationChannelId = "stickyNotificationChannel"; |
||||
|
|
||||
|
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); |
||||
|
NotificationChannel channel = null; |
||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { |
||||
|
channel = new NotificationChannel(notificationChannelId, notificationChannelId, NotificationManager.IMPORTANCE_HIGH); |
||||
|
channel.enableVibration(false); |
||||
|
channel.setShowBadge(false); |
||||
|
notificationManager.createNotificationChannel(channel); |
||||
|
|
||||
|
Intent notificationIntent = new Intent(this, MainActivity.class); |
||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); |
||||
|
|
||||
|
Notification.Builder builder = new Notification.Builder(this, notificationChannelId); |
||||
|
return builder.setContentTitle("TextBee is running").setContentText("TextBee is running in the background.").setContentIntent(pendingIntent).setOngoing(true).setSmallIcon(R.drawable.ic_launcher_foreground).build(); |
||||
|
} else { |
||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, notificationChannelId); |
||||
|
return builder.setContentTitle("TextBee is running").setContentText("TextBee is running in the background.").setOngoing(true).setSmallIcon(R.drawable.ic_launcher_foreground).build(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' |
||||
|
import { Document, Types } from 'mongoose' |
||||
|
import { User } from '../../users/schemas/user.schema' |
||||
|
import { ApiKey } from './api-key.schema' |
||||
|
|
||||
|
export type AccessLogDocument = AccessLog & Document |
||||
|
|
||||
|
@Schema({ timestamps: true }) |
||||
|
export class AccessLog { |
||||
|
_id?: Types.ObjectId |
||||
|
|
||||
|
@Prop({ type: Types.ObjectId, ref: ApiKey.name }) |
||||
|
apiKey: ApiKey |
||||
|
|
||||
|
@Prop({ type: Types.ObjectId, ref: User.name }) |
||||
|
user: User |
||||
|
|
||||
|
@Prop({ type: String }) |
||||
|
url: string |
||||
|
|
||||
|
@Prop({ type: String }) |
||||
|
method: string |
||||
|
|
||||
|
@Prop({ type: String }) |
||||
|
ip: string |
||||
|
|
||||
|
@Prop({ type: String }) |
||||
|
userAgent: string |
||||
|
} |
||||
|
|
||||
|
export const AccessLogSchema = SchemaFactory.createForClass(AccessLog) |
||||
@ -0,0 +1,4 @@ |
|||||
|
export enum SMSType { |
||||
|
SENT = 'SENT', |
||||
|
RECEIVED = 'RECEIVED', |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
import { Box, SimpleGrid } from '@chakra-ui/react' |
||||
|
|
||||
|
import React from 'react' |
||||
|
import ErrorBoundary from '../ErrorBoundary' |
||||
|
import ApiKeyList from './ApiKeyList' |
||||
|
import DeviceList from './DeviceList' |
||||
|
import GenerateApiKey from './GenerateApiKey' |
||||
|
|
||||
|
export default function APIKeyAndDevices() { |
||||
|
return ( |
||||
|
<Box backdropBlur='2xl' borderWidth='0px' borderRadius='lg'> |
||||
|
<Box maxW='xl' mx={'auto'} pt={5} px={{ base: 2, sm: 12, md: 17 }}> |
||||
|
<GenerateApiKey /> |
||||
|
</Box> |
||||
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={{ base: 5, lg: 8 }}> |
||||
|
<Box backdropBlur='2xl' borderWidth='0px' borderRadius='lg'> |
||||
|
<ErrorBoundary> |
||||
|
<ApiKeyList /> |
||||
|
</ErrorBoundary> |
||||
|
</Box> |
||||
|
<Box backdropBlur='2xl' borderWidth='0px' borderRadius='lg'> |
||||
|
<ErrorBoundary> |
||||
|
<DeviceList /> |
||||
|
</ErrorBoundary> |
||||
|
</Box> |
||||
|
</SimpleGrid> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
@ -0,0 +1,170 @@ |
|||||
|
import { |
||||
|
Alert, |
||||
|
AlertIcon, |
||||
|
Grid, |
||||
|
GridItem, |
||||
|
Spinner, |
||||
|
Stack, |
||||
|
Tab, |
||||
|
TabList, |
||||
|
TabPanel, |
||||
|
TabPanels, |
||||
|
Table, |
||||
|
TableContainer, |
||||
|
Tabs, |
||||
|
Tbody, |
||||
|
Td, |
||||
|
Th, |
||||
|
Thead, |
||||
|
Tr, |
||||
|
} from '@chakra-ui/react' |
||||
|
import { useEffect, useMemo, useState } from 'react' |
||||
|
import { useSelector } from 'react-redux' |
||||
|
import { |
||||
|
fetchReceivedSMSList, |
||||
|
selectDeviceList, |
||||
|
selectReceivedSMSList, |
||||
|
} from '../../store/deviceSlice' |
||||
|
import { useAppDispatch } from '../../store/hooks' |
||||
|
import { selectAuthUser } from '../../store/authSlice' |
||||
|
|
||||
|
export default function ReceiveSMS() { |
||||
|
return ( |
||||
|
<> |
||||
|
<Grid |
||||
|
templateColumns={{ base: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }} |
||||
|
gap={6} |
||||
|
> |
||||
|
<GridItem colSpan={2}> |
||||
|
<ReceivedSMSList /> |
||||
|
</GridItem> |
||||
|
<GridItem colSpan={1}> |
||||
|
<ReceiveSMSNotifications /> |
||||
|
</GridItem> |
||||
|
</Grid> |
||||
|
</> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const ReceiveSMSNotifications = () => { |
||||
|
return ( |
||||
|
<Stack spacing={3}> |
||||
|
<Alert status='success'> |
||||
|
<AlertIcon /> |
||||
|
You can now receive SMS and view them in the dashboard, or retreive them |
||||
|
via the API |
||||
|
</Alert> |
||||
|
|
||||
|
<Alert status='warning'> |
||||
|
<AlertIcon /> |
||||
|
To receive SMS, you need to have an active device that has receive SMS |
||||
|
option enabled <small>(Turn on the switch in the app)</small> |
||||
|
</Alert> |
||||
|
|
||||
|
<Alert status='info'> |
||||
|
<AlertIcon /> |
||||
|
Webhooks will be available soon 😉 |
||||
|
</Alert> |
||||
|
</Stack> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const ReceivedSMSList = () => { |
||||
|
const dispatch = useAppDispatch() |
||||
|
|
||||
|
const [tabIndex, setTabIndex] = useState(0) |
||||
|
|
||||
|
const { loading: receivedSMSListLoading, data: receivedSMSListData } = |
||||
|
useSelector(selectReceivedSMSList) |
||||
|
const deviceList = useSelector(selectDeviceList) |
||||
|
|
||||
|
const authUser = useSelector(selectAuthUser) |
||||
|
|
||||
|
const activeDeviceId = useMemo(() => { |
||||
|
return deviceList[tabIndex]?._id |
||||
|
}, [tabIndex, deviceList]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (authUser && activeDeviceId) { |
||||
|
dispatch(fetchReceivedSMSList(activeDeviceId)) |
||||
|
} |
||||
|
}, [dispatch, authUser, activeDeviceId]) |
||||
|
|
||||
|
if (!receivedSMSListLoading && (!deviceList || deviceList.length == 0)) { |
||||
|
return ( |
||||
|
<Alert status='warning'> |
||||
|
<AlertIcon /> |
||||
|
You dont have any devices yet. Please register a device to receive SMS |
||||
|
</Alert> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
<Tabs isLazy={false} index={tabIndex} onChange={setTabIndex}> |
||||
|
<TabList> |
||||
|
{deviceList.map(({ _id, brand, model }) => ( |
||||
|
<Tab key={_id}>{`${brand} ${model}`}</Tab> |
||||
|
))} |
||||
|
</TabList> |
||||
|
<TabPanels> |
||||
|
{deviceList.map(({ _id, brand, model }) => ( |
||||
|
<TabPanel key={_id}> |
||||
|
<TableContainer> |
||||
|
<Table variant='striped'> |
||||
|
<Thead> |
||||
|
<Tr> |
||||
|
<Th>sender</Th> |
||||
|
<Th colSpan={4}>message</Th> |
||||
|
<Th>received at</Th> |
||||
|
</Tr> |
||||
|
</Thead> |
||||
|
<Tbody> |
||||
|
{receivedSMSListLoading && ( |
||||
|
<Tr> |
||||
|
<Td colSpan={6} textAlign='center'> |
||||
|
<Spinner size='lg' /> |
||||
|
</Td> |
||||
|
</Tr> |
||||
|
)} |
||||
|
|
||||
|
{!receivedSMSListLoading && |
||||
|
receivedSMSListData.length == 0 && ( |
||||
|
<Td colSpan={6} textAlign='center'> |
||||
|
No SMS received |
||||
|
</Td> |
||||
|
)} |
||||
|
|
||||
|
{!receivedSMSListLoading && |
||||
|
receivedSMSListData.length > 0 && |
||||
|
receivedSMSListData.map( |
||||
|
({ _id, sender, message, receivedAt }) => ( |
||||
|
<Tr key={_id}> |
||||
|
<Td>{sender}</Td> |
||||
|
<Td whiteSpace='pre-wrap' colSpan={4}> |
||||
|
{message} |
||||
|
</Td> |
||||
|
<Td> |
||||
|
{new Date(receivedAt).toLocaleString('en-US', { |
||||
|
month: 'long', |
||||
|
day: 'numeric', |
||||
|
year: 'numeric', |
||||
|
hour: 'numeric', |
||||
|
minute: 'numeric', |
||||
|
hour12: true, |
||||
|
})} |
||||
|
</Td> |
||||
|
<Td></Td> |
||||
|
</Tr> |
||||
|
) |
||||
|
)} |
||||
|
</Tbody> |
||||
|
</Table> |
||||
|
</TableContainer> |
||||
|
</TabPanel> |
||||
|
))} |
||||
|
</TabPanels> |
||||
|
</Tabs> |
||||
|
</> |
||||
|
) |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue