Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
emiMobile
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mac Stephens
emiMobile
Commits
7fa82e6d
Commit
7fa82e6d
authored
Oct 22, 2026
by
Mac Stephens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix WC device management, SMS verification handling, and map marker loading
parent
13ffaf77
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
213 additions
and
152 deletions
+213
-152
.gitignore
.gitignore
+12
-0
Api.Service.pas
emiMobileServer/Source/Api.Service.pas
+1
-2
Api.ServiceImpl.pas
emiMobileServer/Source/Api.ServiceImpl.pas
+22
-61
Auth.ServiceImpl.pas
emiMobileServer/Source/Auth.ServiceImpl.pas
+24
-32
Ws.DataModel.pas
emiMobileServer/Source/Ws.DataModel.pas
+4
-0
emiMobileServer.ini
emiMobileServer/bin/emiMobileServer.ini
+1
-1
View.DeviceManager.dfm
webEMIMobile/View.DeviceManager.dfm
+9
-0
View.DeviceManager.html
webEMIMobile/View.DeviceManager.html
+16
-11
View.DeviceManager.pas
webEMIMobile/View.DeviceManager.pas
+119
-45
View.Map.pas
webEMIMobile/View.Map.pas
+5
-0
No files found.
.gitignore
View file @
7fa82e6d
...
...
@@ -28,3 +28,15 @@ emiMobileServer/Source/__recovery/
*.dll
webEMIMobile/config/__history/
emiMobileServer/Clients/BFD/Web/__history/
emiMobileServer/Clients/WC/Web/__history/
emiMobileServer/Shared/Server/__history/
emiMobileServer/Clients/WC/Web/css/__history/
emiMobileServer/Clients/BFD/Web/Win32/Debug/
emiMobileServer/Clients/WC/Web/Win32/Debug/
emiMobileServer/Source/Api.Service.pas
View file @
7fa82e6d
...
...
@@ -39,8 +39,7 @@ type
function
UnrevokeDevice
(
const
CredentialId
:
string
):
TJSONObject
;
function
AddPendingDevice
(
const
DeviceName
,
PhoneNumber
,
DeviceType
:
string
):
TJSONObject
;
function
DeletePendingDevice
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
DeleteDevice
(
CredentialId
:
string
):
TJSONObject
;
function
DeleteDevice
(
DeviceId
:
Integer
):
TJSONObject
;
function
SendAppLink
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
UpdateDeviceUsername
(
const
CredentialId
,
Username
:
string
):
TJSONObject
;
end
;
...
...
emiMobileServer/Source/Api.ServiceImpl.pas
View file @
7fa82e6d
...
...
@@ -45,8 +45,7 @@ type
function
UnrevokeDevice
(
const
CredentialId
:
string
):
TJSONObject
;
function
AddPendingDevice
(
const
DeviceName
,
PhoneNumber
,
DeviceType
:
string
):
TJSONObject
;
function
DeletePendingDevice
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
DeleteDevice
(
CredentialId
:
string
):
TJSONObject
;
function
DeleteDevice
(
DeviceId
:
Integer
):
TJSONObject
;
function
SendAppLink
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
UpdateDeviceUsername
(
const
CredentialId
,
Username
:
string
):
TJSONObject
;
end
;
...
...
@@ -1626,6 +1625,21 @@ begin
try
q
.
Connection
:=
conn
;
// Note: Device names are unique regardless of capitalization or surrounding spaces.
q
.
SQL
.
Text
:=
'SELECT id FROM lems.device_registrations '
+
'WHERE UPPER(TRIM(device_name)) = UPPER(:NAME)'
;
q
.
ParamByName
(
'NAME'
).
AsString
:=
Trim
(
DeviceName
);
q
.
Open
;
if
not
q
.
IsEmpty
then
begin
q
.
Close
;
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'A device with that name already exists. Enter a different device name.'
);
Exit
;
end
;
q
.
Close
;
// Reject if a pending entry with this phone already exists
q
.
SQL
.
Text
:=
'SELECT id FROM lems.device_registrations '
+
...
...
@@ -1661,59 +1675,7 @@ begin
end
;
end
;
function
TApiService
.
DeletePendingDevice
(
const
PhoneNumber
:
string
):
TJSONObject
;
var
conn
:
TUniConnection
;
q
:
TUniQuery
;
normalizedPhone
:
string
;
begin
RequireAdmin
;
Result
:=
TJSONObject
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
try
normalizedPhone
:=
NormalizePhoneE164Api
(
PhoneNumber
);
except
on
E
:
Exception
do
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
E
.
Message
);
Exit
;
end
;
end
;
conn
:=
OpenLemsConnection
;
try
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
conn
;
q
.
SQL
.
Text
:=
'DELETE FROM lems.device_registrations '
+
'WHERE phone_number = :PHONE AND status = ''pending'''
;
q
.
ParamByName
(
'PHONE'
).
AsString
:=
normalizedPhone
;
q
.
ExecSQL
;
if
q
.
RowsAffected
>
0
then
begin
Logger
.
Log
(
2
,
'TApiService.DeletePendingDevice - deleted phone: '
+
normalizedPhone
);
Result
.
AddPair
(
'status'
,
'ok'
);
Result
.
AddPair
(
'message'
,
'Pending device removed.'
);
end
else
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'Pending device not found.'
);
end
;
finally
q
.
Free
;
end
;
finally
conn
.
Free
;
end
;
end
;
function
TApiService
.
DeleteDevice
(
CredentialId
:
string
):
TJSONObject
;
function
TApiService
.
DeleteDevice
(
DeviceId
:
Integer
):
TJSONObject
;
var
conn
:
TUniConnection
;
q
:
TUniQuery
;
...
...
@@ -1723,10 +1685,10 @@ begin
Result
:=
TJSONObject
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
if
Trim
(
CredentialId
)
=
''
then
if
DeviceId
<=
0
then
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'
Credential
Id is required.'
);
Result
.
AddPair
(
'message'
,
'
Device
Id is required.'
);
Exit
;
end
;
...
...
@@ -1737,14 +1699,13 @@ begin
q
.
Connection
:=
conn
;
q
.
SQL
.
Text
:=
'DELETE FROM lems.device_registrations '
+
'WHERE
credential_id = :C
ID'
;
q
.
ParamByName
(
'
CID'
).
AsString
:=
Trim
(
CredentialId
)
;
'WHERE
id = :
ID'
;
q
.
ParamByName
(
'
ID'
).
AsInteger
:=
DeviceId
;
q
.
ExecSQL
;
if
q
.
RowsAffected
>
0
then
begin
Logger
.
Log
(
2
,
'TApiService.DeleteDevice - deleted credential: '
+
Copy
(
CredentialId
,
1
,
20
));
Logger
.
Log
(
2
,
'TApiService.DeleteDevice - deleted id: '
+
IntToStr
(
DeviceId
));
Result
.
AddPair
(
'status'
,
'ok'
);
Result
.
AddPair
(
'message'
,
'Device deleted.'
);
end
...
...
emiMobileServer/Source/Auth.ServiceImpl.pas
View file @
7fa82e6d
...
...
@@ -29,7 +29,7 @@ type
function
LoadUserByName
(
const
User
,
Agency
:
string
):
Boolean
;
function
Decrypt
(
inStr
,
keyStr
:
AnsiString
):
AnsiString
;
function
GenerateVerificationCode
:
string
;
procedure
SendVerificationText
(
const
PhoneNumber
,
Code
:
string
)
;
function
SendVerificationText
(
const
PhoneNumber
,
Code
:
string
):
Boolean
;
// Returns True and sets AChallengeB64 if token is valid and of the expected type
function
VerifyChallengeToken
(
const
ChallengeToken
,
ExpectedType
:
string
;
out
AChallengeB64
:
string
):
Boolean
;
...
...
@@ -209,7 +209,7 @@ begin
Result
:=
FormatFloat
(
'000000'
,
value
mod
1000000
);
end
;
procedure
TAuthService
.
SendVerificationText
(
const
PhoneNumber
,
Code
:
string
)
;
function
TAuthService
.
SendVerificationText
(
const
PhoneNumber
,
Code
:
string
):
Boolean
;
var
httpClient
:
THTTPClient
;
bodyStream
:
TStringStream
;
...
...
@@ -239,9 +239,10 @@ begin
response
:=
httpClient
.
Post
(
'https://api.twilio.com/2010-04-01/Accounts/'
+
ServerConfig
.
twilioAccountSid
+
'/Messages.json'
,
bodyStream
);
if
response
.
StatusCode
>=
300
then
raise
Exception
.
CreateFmt
(
'Twilio error %d: %s'
,
[
response
.
StatusCode
,
response
.
ContentAsString
]);
Result
:=
(
response
.
StatusCode
>=
200
)
and
(
response
.
StatusCode
<
300
);
if
not
Result
then
Logger
.
Log
(
2
,
Format
(
'Twilio rejected verification SMS, HTTP %d: %s'
,
[
response
.
StatusCode
,
response
.
ContentAsString
]));
finally
bodyStream
.
Free
;
end
;
...
...
@@ -308,6 +309,24 @@ begin
end
;
code
:=
GenerateVerificationCode
;
try
if
not
SendVerificationText
(
normalizedPhone
,
code
)
then
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'The SMS provider rejected the verification message. Contact your administrator to check the phone number and SMS configuration.'
);
Exit
;
end
;
except
on
E
:
Exception
do
begin
Logger
.
Log
(
2
,
'--Error sending registration verification code: '
+
E
.
Message
);
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'The verification code could not be sent. Please try again.'
);
Exit
;
end
;
end
;
// Note: Persist the code only after the SMS provider accepts the message.
q
:=
TUniQuery
.
Create
(
nil
);
try
...
...
@@ -330,33 +349,6 @@ begin
q
.
Free
;
end
;
try
SendVerificationText
(
normalizedPhone
,
code
);
except
on
E
:
Exception
do
begin
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
authDB
.
ucLemsOCSO
;
q
.
SQL
.
Text
:=
'UPDATE lems.device_registrations '
+
'SET verification_code = NULL, verification_expires_at = NULL '
+
'WHERE phone_number = :PHONE AND status = ''pending'' '
+
'AND verification_code = :CODE'
;
q
.
ParamByName
(
'PHONE'
).
AsString
:=
normalizedPhone
;
q
.
ParamByName
(
'CODE'
).
AsString
:=
code
;
q
.
ExecSQL
;
finally
q
.
Free
;
end
;
Logger
.
Log
(
2
,
'--Error sending registration verification code: '
+
E
.
Message
);
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'The verification code could not be sent. Please try again.'
);
Exit
;
end
;
end
;
Logger
.
Log
(
3
,
Format
(
'Verification code sent for device id %d and device name: %s'
,
[
deviceId
,
deviceName
]));
...
...
emiMobileServer/Source/Ws.DataModel.pas
View file @
7fa82e6d
...
...
@@ -79,12 +79,16 @@ end;
destructor
TWsDataModel
.
Destroy
;
begin
if
Assigned
(
FListenerTimer
)
then
FListenerTimer
.
Enabled
:=
False
;
FListenerTimer
.
Free
;
if
Assigned
(
FThread
)
then
begin
FThread
.
Terminate
;
FStopEvent
.
SetEvent
;
FThread
.
WaitFor
;
FThread
.
Free
;
end
;
FListenerDb
.
Free
;
FStopEvent
.
Free
;
inherited
;
...
...
emiMobileServer/bin/emiMobileServer.ini
View file @
7fa82e6d
[Settings]
LogFileNum
=
15
4
LogFileNum
=
15
5
webClientVersion
=
0.9.4.9
MemoLogLevel
=
3
FileLogLevel
=
4
...
...
webEMIMobile/View.DeviceManager.dfm
View file @
7fa82e6d
...
...
@@ -81,6 +81,15 @@ object FViewDeviceManager: TFViewDeviceManager
WidthPercent = 100.000000000000000000
OnClick = btnLogoutClick
end
object btnRefresh: TWebButton
Left = 620
Top = 50
Width = 75
Height = 25
Caption = 'Refresh'
ElementID = 'view.devmgr.btnrefresh'
OnClick = btnRefreshClick
end
object XDataWebClient: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 800
...
...
webEMIMobile/View.DeviceManager.html
View file @
7fa82e6d
<div
class=
"container-fluid p-3 h-100 d-flex flex-column"
>
<div
class=
"d-flex align-items-center mb-3"
>
<div
class=
"d-flex align-items-center
flex-wrap gap-2
mb-3"
>
<h5
class=
"mb-0 me-auto"
>
Device Management
</h5>
<button
id=
"view.devmgr.btnlogout"
class=
"btn btn-outline-secondary btn-sm me-2"
type=
"button"
>
Log Out
</button>
<button
id=
"view.devmgr.btnrefresh"
class=
"btn btn-outline-secondary btn-sm"
onclick=
"document.dispatchEvent(new CustomEvent('devmgr-refresh'))
"
>
type=
"button
"
>
Refresh
</button>
</div>
...
...
@@ -26,9 +26,9 @@
<!-- Add pending device -->
<div
class=
"card mb-3"
>
<div
class=
"card-body py-2"
>
<div
class=
"
d-flex align-items-start gap-2 flex-wrap
"
>
<
span
class=
"fw-semibold text-nowrap small mt-2"
>
Add Device:
</span
>
<div
style=
"width:180px;
"
>
<div
class=
"
row g-2 align-items-start
"
>
<
div
class=
"col-12 col-md-auto"
><span
class=
"fw-semibold text-nowrap small"
>
Add Device:
</span></div
>
<div
class=
"col-6 col-md-auto
"
>
<input
type=
"text"
id=
"view.devmgr.newname"
class=
"form-control form-control-sm"
...
...
@@ -36,7 +36,7 @@
required
>
<div
class=
"invalid-feedback small"
>
Device name is required.
</div>
</div>
<div
style=
"width:160px;
"
>
<div
class=
"col-6 col-md-auto
"
>
<input
type=
"tel"
id=
"view.devmgr.newphone"
class=
"form-control form-control-sm"
...
...
@@ -44,7 +44,7 @@
required
>
<div
class=
"invalid-feedback small"
>
Phone number is required.
</div>
</div>
<div
style=
"width:140px;
"
>
<div
class=
"col-6 col-md-auto
"
>
<select
id=
"view.devmgr.newtype"
class=
"form-select form-select-sm"
required
>
...
...
@@ -56,20 +56,24 @@
</select>
<div
class=
"invalid-feedback small"
>
Device type is required.
</div>
</div>
<button
id=
"view.devmgr.btnadd"
class=
"btn btn-primary btn-sm"
>
Add
</button>
<div
class=
"col-6 col-md-auto d-grid"
>
<button
id=
"view.devmgr.btnadd"
class=
"btn btn-primary btn-sm"
>
Add
</button>
</div>
</div>
</div>
</div>
<!-- Device table -->
<div
class=
"table-responsive flex-grow-1"
>
<div
class=
"flex-grow-1 overflow-auto"
>
<div
id=
"view.devmgr.cards"
class=
"d-md-none"
></div>
<div
class=
"table-responsive d-none d-md-block"
>
<table
class=
"table table-sm table-hover align-middle"
id=
"view.devmgr.table"
>
<thead
class=
"table-light sticky-top"
>
<tr>
<th
style=
"min-width:130px;"
>
Device Name
</th>
<th
style=
"min-width:100px;"
>
Device Type
</th>
<th
style=
"min-width:120px;"
>
Phone
</th>
<th
style=
"min-width:1
45
px;"
>
Verification Code
</th>
<th
style=
"min-width:1
10
px;"
>
Verification Code
</th>
<th
style=
"min-width:140px;"
>
Username
</th>
<th>
Browser / User Agent
</th>
<th
style=
"min-width:135px;"
>
Registered
</th>
...
...
@@ -82,6 +86,7 @@
<tbody
id=
"view.devmgr.tbody"
>
</tbody>
</table>
</div>
<p
id=
"view.devmgr.empty"
class=
"text-muted d-none text-center py-4"
>
No devices found.
</p>
...
...
webEMIMobile/View.DeviceManager.pas
View file @
7fa82e6d
This diff is collapsed.
Click to expand it.
webEMIMobile/View.Map.pas
View file @
7fa82e6d
...
...
@@ -98,6 +98,8 @@ end;
procedure
TFViewMap
.
UpdateDeviceLocation
(
lat
,
lng
:
Double
);
begin
lfMap
.
BeginUpdate
;
try
if
userLocationMarker
=
nil
then
begin
userLocationMarker
:=
lfMap
.
Markers
.
Add
;
...
...
@@ -109,6 +111,9 @@ begin
userLocationMarker
.
Latitude
:=
lat
;
userLocationMarker
.
Longitude
:=
lng
;
finally
lfMap
.
EndUpdate
;
end
;
end
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment