Skip to content

Commit af9e4bd

Browse files
committed
Stable version 0.7.1
1 parent 3d26c53 commit af9e4bd

File tree

13 files changed

+207
-130
lines changed

13 files changed

+207
-130
lines changed

docs/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22
0.7.0 → Unreleased
33
- Added ability to delete a conversation from the web interface
4+
- Added ability to easily export TLS certificate (e.g. to add to client device store)
45
- Web interface now works much better with mobile devices
56
- Certificates are now generated automatically when you install the app from the `.deb` file, making the TLS connection more likely to be secure.
67
- Websocket now attempts to reconnect when it disconnects from the host device (fixes issue of web interface not receiving updates after a while)

docs/FAQ.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ location /smserver_websocket/ {
3838
The above block defines that you will be running the main SMServer server at the subdirectory `/smserver/`, and that you will be running the websocket at the subdirectory `/smserver_websocket/`. You are free to change these directories, but make sure to adjust the next step accordingly if you do. \
3939
3\. Open the settings of the SMServer app on your host device, and enable 'WebSocket Proxy Compatibility' under 'Web interface Settings'. In the box that appears, enter the subdirectory that the websocket resides at in your reverse proxy (in this case, it is `/smserver_websocket/`, but if you change it you'll need to type in what you set it to instead).
4040

41+
### The website stops automatically updating after a while and I have to refresh it to see new texts/an accurate battery percentage.
42+
43+
Try disabling the option "Restart server on network change" in the host device's settings. If you're running SMServer behind a reverse proxy, you may also want to look at [this comment](https://github.com/iandwelker/smserver/issues/73#issuecomment-762618203) to see if it helps.
44+
If neither of these fix it, feel free to file an issue.
45+
4146
### How did you make this?
4247

4348
In this same directory, there's a document called `IMCore_and_ChatKit.md` that details how I used ChatKit and IMCore for the backend of this app. It should have most things that you'd be interested in. If there are still other questions you'd like to ask or things you don't understand, feel free to DM me at u/Janshai on reddit, or @Janshaidev on twitter.

docs/IMCore_and_ChatKit.md

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,37 @@ else
117117
     If you haven't yet texted the person in the previous block of code (the one using `CKConversation`), the `CKConversation` from `[CKConversationList conversationForExistingChatWithGroupID]` will be nil. Just check for that, then execute the code in the most previous block, assuming that `chat` is nil (so that you have to go into the if and create a new chat).
118118

119119
## Typing indicators
120-
     This is probably one of the most unsure things I figured out, meaning that there's definitely a better way but I'm just doing the thing that seems to work semi-ok for me. To hijack when someone starts typing using my method, you'll have to first have the MobileSMS app open, or just running in the background. There may be a more reliable way by working through IMDaemon, but I have yet to find it (or even really look for it). Here's the code:
120+
     This isn't the most elegant solution, but it isn't that bad either. In a nutshell, whenever `isCancelTypingMessage` returns `YES`, someone stopped typing, and whenever `isIncomingTypingMessage` returns `YES`, someone started typing. So if you check their values every time, you can do whever you want each time they return `YES`. The full code is below.
121121

122122
```objectivec
123-
%hook IMTypingChatItem
123+
%hook IMMessageItem
124124

125-
- (id)_initWithItem:(id)arg1 {
126-
id orig = %orig;
125+
- (bool)isCancelTypingMessage {
126+
bool orig = %orig;
127127

128-
NSString *chat = [(IMMessageItem *)arg1 sender];
129-
/// Do whatever you want with the chat. Personally, I send it to my app via IPC.
128+
/// if `orig` is true here, someone stopped typing.
129+
if (orig) {
130130

131+
/// warning: the `IMMessageItem` that this function is running in gets deallocated very quickly after when
132+
/// this function is called and returns `YES`, so if you do any dispatch_async stuff here and try to call
133+
/// anything on `self` in the block, it will crash the process this is running in.
134+
__block NSString* sender = [self sender];
135+
/// do whatever you want with the conversation in which the other party stopped typing
136+
}
137+
138+
return orig;
139+
}
140+
141+
- (bool)isIncomingTypingMessage {
142+
bool orig = %orig;
143+
144+
/// if `orig` is true here, somebody started typing.
145+
if (orig) {
146+
147+
__block NSString* sender = [self sender];
148+
/// `sender` is the chat identifier of the conversation in which the other party started typing.
149+
/// you can do whatever you want with it here.
150+
}
131151
return orig;
132152
}
133153

@@ -165,9 +185,7 @@ long long int tapback = 2000;
165185
166186
IMChat *chat = [[%c(IMChatRegistry) sharedInstance] existingChatWithChatIdentifier:address];
167187
168-
/// You may need to load more than 1000. I haven't yet tested this for texts
169-
/// more than 1000 back so it may not work.
170-
[chat loadMessagesUpToGUID:nil date:nil limit:1000 loadImmediately:YES];
188+
[chat loadMessagesUpToGUID:guid date:nil limit:nil loadImmediately:YES];
171189
IMMessageItem *item = [chat messageItemForGUID:guid];
172190
173191
IMTextMessagePartChatItem *pci = [[%c(IMTextMessagePartChatItem) alloc] _initWithItem:item text:[item body] index:0 messagePartRange:NSMakeRange(0, [[item body] length]) subject:[item subject]];
@@ -181,16 +199,27 @@ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 14.0)
181199
     I don't yet know how to send tapbacks on iOS 13-, since the last method used in the code snippet above (`[chat sendMessageAcknowledgment: forChatItem: withAssociatedMessageInfo:]`) doesn't exist in anything before iOS 14. However, there are very similar methods, so I'm fairly certain that it wouldn't be too hard to figure it out. Let me know if you do, and I can add the information here (if you'd like).
182200

183201
## Getting pinned chats
184-
     I am actually fairly certain this is the best way to get the list of pinned chats. It's short and easy, so here's the code:
202+
     I am actually fairly certain this is the best way to get the list of pinned chats. It's not as short and easy as I previously thought, but it's not too bad either. The following code gets us an array of all the chat identifiers that correspond to the currently pinned chats.
185203

186204
```objectivec
187205
/// Pinned chats are only available for iOS 14+, so check that first
188206
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 14.0) {
189207
IMPinnedConversationsController* pinnedController = [%c(IMPinnedConversationsController) sharedInstance];
190-
NSOrderedSet* set = [pinnedController pinnedConversationIdentifierSet];
208+
NSArray* pins = [[pinnedController pinnedConversationIdentifierSet] array];
209+
210+
CKConversationList* list = [%c(CKConversationList) sharedConversationList];
211+
NSMutableArray* convos = [NSMutableArray arrayWithCapacity:[pins count]];
212+
213+
/// So `pins` contains an array of pinning identifiers, not chat identifiers. So we have to iterate through
214+
/// and get the chat identifiers that correspond with the pinning identifiers, since SMServer parses them by chat identifier.
215+
for (id obj in pins) {
216+
CKConversation* convo = (CKConversation *)[list conversationForExistingChatWithPinningIdentifier:obj];
217+
if (convo == nil) continue;
218+
NSString* identifier = [[convo chat] chatIdentifier];
219+
[convos addObject:identifier];
220+
}
191221

192-
/// I used it as an array, but obviously you can return it as the NSOrderedSet as well
193-
return [set array];
222+
return convos;
194223
}
195224

196225
return [NSArray array];
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>PreviewsEnabled</key>
6+
<false/>
7+
</dict>
8+
</plist>
Binary file not shown.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>BuildLocationStyle</key>
6+
<string>UseAppPreferences</string>
7+
<key>CustomBuildLocationType</key>
8+
<string>RelativeToDerivedData</string>
9+
<key>DerivedDataLocationStyle</key>
10+
<string>Default</string>
11+
<key>IssueFilterStyle</key>
12+
<string>ShowActiveSchemeOnly</string>
13+
<key>LiveSourceIssuesEnabled</key>
14+
<true/>
15+
<key>ShowSharedSchemesAutomaticallyEnabled</key>
16+
<true/>
17+
</dict>
18+
</plist>

src/SMServer/html/chats.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@
692692
title_doc.insertBefore(img, title_doc.firstChild);
693693

694694
let back_arrow = '<i class="fas fa-chevron-left" onclick="goBackArrow();"></i>';
695-
let trash = '<i class="fas fa-trash" onclick="deleteCurrentConvo();"></i>';
695+
let trash = '<i class="fas fa-trash-alt" onclick="deleteCurrentConvo();"></i>';
696696

697697
message_title.innerHTML = back_arrow + title_doc.outerHTML + trash;
698698

@@ -1041,9 +1041,9 @@
10411041
}
10421042

10431043
function goBackArrow() {
1044-
if (!mobile) {
1045-
clearTextContent();
1046-
} else {
1044+
clearTextContent();
1045+
1046+
if (mobile) {
10471047
document.getElementsByClassName("messages")[0].style.display = "none";
10481048
document.getElementsByClassName("chats")[0].style.display = "block";
10491049
}

src/SMServer/ios/ContentView.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,25 @@ struct ContentView: View {
88
let geo_width: CGFloat = 0.6
99
let font_size: CGFloat = 25
1010

11-
@State var debug: Bool = UserDefaults.standard.object(forKey: "debug") as? Bool ?? false
12-
1311
@State var view_settings: Bool = false
1412
@State var server_running: Bool = false
1513
@State var show_picker: Bool = false
1614
@State var show_oseven_update: Bool = false
1715

1816
func loadServer() {
1917
/// This starts the server at port $port_num
20-
Const.log("Attempting to load server and socket...", debug: self.debug)
18+
Const.log("Attempting to load server and socket...")
2119

2220
self.server_running = server.startServers()
2321

24-
Const.log(self.server_running ? "Successfully started server and socket" : "Failed to start server and socket", debug: self.debug, warning: !self.server_running)
22+
Const.log(self.server_running ? "Successfully started server and socket" : "Failed to start server and socket", warning: !self.server_running)
2523
}
2624

2725
func enteredBackground() {
2826
/// Just waits a minute and then kills the app if you disabled backgrounding. A not graceful way of doing what the system does automatically
2927
//if !background || !self.server.isListening {
3028
if !settings.background || !self.server.isRunning() {
31-
Const.log("sceneDidEnterBackground, starting kill timer", debug: self.debug)
29+
Const.log("sceneDidEnterBackground, starting kill timer")
3230
DispatchQueue.main.asyncAfter(deadline: .now() + 60, execute: {
3331
if UIApplication.shared.applicationState == .background {
3432
exit(0)
@@ -40,12 +38,10 @@ struct ContentView: View {
4038
func loadFuncs() {
4139
/// All the functions that run on scene load
4240

43-
self.debug = settings.debug
44-
4541
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized {
4642
PHPhotoLibrary.requestAuthorization({ auth in
4743
if auth != PHAuthorizationStatus.authorized {
48-
Const.log("App is not authorized to view photos. Please grant access.", debug: self.debug, warning: true)
44+
Const.log("App is not authorized to view photos. Please grant access.", warning: true)
4945
}
5046
})
5147
}
@@ -61,7 +57,6 @@ struct ContentView: View {
6157
}
6258

6359
func reloadVars() {
64-
self.debug = settings.debug
6560
self.server.reloadVars()
6661
}
6762

@@ -236,7 +231,7 @@ struct ContentView: View {
236231
do {
237232
try FileManager.default.copyItem(at: url, to: Const.custom_css_path)
238233
} catch {
239-
Const.log("Couldn't move custom css", debug: self.debug, warning: true)
234+
Const.log("Couldn't move custom css", warning: true)
240235
}
241236
}
242237
)
@@ -254,9 +249,9 @@ struct ContentView: View {
254249
Button(action: {
255250
do {
256251
try FileManager.default.removeItem(at: Const.custom_css_path)
257-
Const.log("Removed custom css file", debug: self.debug)
252+
Const.log("Removed custom css file")
258253
} catch {
259-
Const.log("Failed to remove custom css file", debug: self.debug, warning: true)
254+
Const.log("Failed to remove custom css file", warning: true)
260255
}
261256
}) {
262257
Image(systemName: "trash")

src/SMServer/ios/SettingsView.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct SettingsView: View {
1010
private let cl_red = 0.40
1111
private let cl_blu = 0.65
1212

13+
@State private var show_share_sheet: Bool = false
1314
@State private var show_alert: Bool = false
1415
@State private var alert_title: String = ""
1516
@State private var alert_text: String = ""
@@ -318,6 +319,14 @@ struct SettingsView: View {
318319

319320
Spacer().frame(height: 20)
320321

322+
HStack {
323+
Text("Export TLS certificate")
324+
Spacer()
325+
Image(systemName: "link")
326+
}
327+
328+
Spacer().frame(height: 20)
329+
321330
HStack {
322331
Text("Reset Settings to Default")
323332
Spacer()
@@ -350,6 +359,15 @@ struct SettingsView: View {
350359
}
351360
}.padding(.init(top: 6, leading: 0, bottom: 6, trailing: 0))
352361

362+
Button(action: {
363+
self.show_share_sheet = true
364+
}) {
365+
HStack {
366+
Text("hidden text :)").foregroundColor(Color.clear)
367+
Spacer()
368+
}
369+
}.padding(.init(top: 6, leading: 0, bottom: 6, trailing: 0))
370+
353371
Button(action: {
354372
self.resetDefaults()
355373
}) {
@@ -363,6 +381,9 @@ struct SettingsView: View {
363381
}.padding(10)
364382
.background(grey_box)
365383
.cornerRadius(8)
384+
.sheet(isPresented: $show_share_sheet, content: {
385+
ShareSheet(activityItems: [Bundle.main.url(forResource: "cert", withExtension: "der")!])
386+
})
366387

367388
Text("Compatible with libSMServer 0.6.1")
368389
.font(.callout)
@@ -373,3 +394,26 @@ struct SettingsView: View {
373394
}.coordinateSpace(name: "frameLayer")
374395
}
375396
}
397+
398+
/// stolen from https://developer.apple.com/forums/thread/123951
399+
struct ShareSheet: UIViewControllerRepresentable {
400+
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
401+
402+
let activityItems: [Any]
403+
let applicationActivities: [UIActivity]? = nil
404+
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
405+
let callback: Callback? = nil
406+
407+
func makeUIViewController(context: Context) -> UIActivityViewController {
408+
let controller = UIActivityViewController(
409+
activityItems: activityItems,
410+
applicationActivities: applicationActivities)
411+
controller.excludedActivityTypes = excludedActivityTypes
412+
controller.completionWithItemsHandler = callback
413+
return controller
414+
}
415+
416+
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
417+
// nothing to do here
418+
}
419+
}

0 commit comments

Comments
 (0)