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
6a8f477c
Commit
6a8f477c
authored
Oct 02, 2026
by
Mac Stephens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update to 0.9.4.7 - Secure WebSockets and API access; add SMS device verification
parent
7fed875b
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
351 additions
and
53 deletions
+351
-53
.gitignore
.gitignore
+1
-0
Api.Service.pas
emiMobileServer/Source/Api.Service.pas
+2
-0
Auth.ServiceImpl.pas
emiMobileServer/Source/Auth.ServiceImpl.pas
+114
-19
Common.Config.pas
emiMobileServer/Source/Common.Config.pas
+6
-6
Common.Ini.pas
emiMobileServer/Source/Common.Ini.pas
+26
-0
WebSocket.Manager.pas
emiMobileServer/Source/WebSocket.Manager.pas
+184
-15
emiMobileServer.ini
emiMobileServer/bin/emiMobileServer.ini
+7
-2
emiMobileServer.json
emiMobileServer/bin/emiMobileServer.json
+1
-4
emiMobileServer.dproj
emiMobileServer/emiMobileServer.dproj
+2
-2
twilioinfo.txt
twilioinfo.txt
+4
-0
ConnectionModule.pas
webEMIMobile/ConnectionModule.pas
+1
-1
View.DeviceRegistration.dfm
webEMIMobile/View.DeviceRegistration.dfm
+0
-1
View.DeviceRegistration.pas
webEMIMobile/View.DeviceRegistration.pas
+1
-1
wcEmiMobile.dproj
webEMIMobile/wcEmiMobile.dproj
+2
-2
No files found.
.gitignore
View file @
6a8f477c
...
@@ -11,6 +11,7 @@ emiMobileServer/*.log
...
@@ -11,6 +11,7 @@ emiMobileServer/*.log
emiMobileServer/*.txt
emiMobileServer/*.txt
emiMobileServer/bin/logs/
emiMobileServer/bin/logs/
emiMobileServer/bin/static/
emiMobileServer/bin/static/
emiMobileServer/bin/emiMobileServer.ini
emiMobileServer/Source/__history
emiMobileServer/Source/__history
emiMobileServer/Source/__recovery/
emiMobileServer/Source/__recovery/
...
...
emiMobileServer/Source/Api.Service.pas
View file @
6a8f477c
...
@@ -4,6 +4,7 @@ interface
...
@@ -4,6 +4,7 @@ interface
uses
uses
XData
.
Service
.
Common
,
XData
.
Service
.
Common
,
XData
.
Security
.
Attributes
,
Aurelius
.
Mapping
.
Attributes
,
Aurelius
.
Mapping
.
Attributes
,
System
.
JSON
,
System
.
JSON
,
System
.
Generics
.
Collections
,
System
.
Generics
.
Collections
,
...
@@ -15,6 +16,7 @@ const
...
@@ -15,6 +16,7 @@ const
type
type
[
ServiceContract
,
Model
(
API_MODEL
)]
[
ServiceContract
,
Model
(
API_MODEL
)]
[
Authorize
]
IApiService
=
interface
(
IInvokable
)
IApiService
=
interface
(
IInvokable
)
[
'{4FCB7FAF-44E5-49D6-9C0F-EE44BFB33313}'
]
[
'{4FCB7FAF-44E5-49D6-9C0F-EE44BFB33313}'
]
[
HttpGet
]
function
GetBadgeCounts
:
TJSONObject
;
[
HttpGet
]
function
GetBadgeCounts
:
TJSONObject
;
...
...
emiMobileServer/Source/Auth.ServiceImpl.pas
View file @
6a8f477c
...
@@ -46,11 +46,8 @@ type
...
@@ -46,11 +46,8 @@ type
function
GetAgencieslist
():
TAgenciesList
;
function
GetAgencieslist
():
TAgenciesList
;
function
GetAgencyConfiglist
:
TAgencyConfigList
;
function
GetAgencyConfiglist
:
TAgencyConfigList
;
function
BeginRegistration
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
BeginRegistration
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
VerifyRegistrationCode
(
const
PhoneNumber
,
VerificationCode
,
function
VerifyRegistrationCode
(
const
PhoneNumber
,
VerificationCode
,
VerificationToken
:
string
):
TJSONObject
;
VerificationToken
:
string
):
TJSONObject
;
function
CompleteRegistration
(
const
PhoneNumber
,
CredentialId
,
AttestationObject
,
ClientDataJSON
,
ChallengeToken
:
string
):
TJSONObject
;
function
CompleteRegistration
(
const
PhoneNumber
,
CredentialId
,
AttestationObject
,
ClientDataJSON
,
ChallengeToken
:
string
):
TJSONObject
;
function
BeginAuthentication
(
const
CredentialId
:
string
):
TJSONObject
;
function
BeginAuthentication
(
const
CredentialId
:
string
):
TJSONObject
;
function
GetDeviceUser
(
const
CredentialId
:
string
):
TJSONObject
;
function
GetDeviceUser
(
const
CredentialId
:
string
):
TJSONObject
;
function
LoginAutomatic
(
const
CredentialId
,
ChallengeToken
,
function
LoginAutomatic
(
const
CredentialId
,
ChallengeToken
,
...
@@ -82,6 +79,21 @@ uses
...
@@ -82,6 +79,21 @@ uses
Webauthn
.
Crypto
,
Webauthn
.
Crypto
,
Webauthn
.
Cbor
;
Webauthn
.
Cbor
;
const
VERIFICATION_RESEND_SECONDS
=
60
;
MAX_VERIFICATION_ATTEMPTS
=
5
;
type
TVerificationAttempt
=
record
Attempts
:
Integer
;
ExpiresAt
:
TDateTime
;
end
;
var
VerificationLock
:
TObject
;
VerificationSentAt
:
TDictionary
<
string
,
TDateTime
>;
VerificationAttempts
:
TDictionary
<
string
,
TVerificationAttempt
>;
{ TAuthService }
{ TAuthService }
...
@@ -215,9 +227,6 @@ begin
...
@@ -215,9 +227,6 @@ begin
(
Cardinal
(
bytes
[
2
])
shl
8
)
or
(
Cardinal
(
bytes
[
2
])
shl
8
)
or
Cardinal
(
bytes
[
3
]);
Cardinal
(
bytes
[
3
]);
Result
:=
FormatFloat
(
'000000'
,
value
mod
1000000
);
Result
:=
FormatFloat
(
'000000'
,
value
mod
1000000
);
// ADD THIS DELETE BEFORE DEPLOY
Logger
.
Log
(
3
,
'---TAuthService.GenerateVerificationCode initiated with Verification Code: '
+
Result
);
Logger
.
Log
(
3
,
'---TAuthService.GenerateVerificationCode End'
);
end
;
end
;
function
TAuthService
.
CreateVerificationToken
(
const
PhoneNumber
,
function
TAuthService
.
CreateVerificationToken
(
const
PhoneNumber
,
...
@@ -314,6 +323,10 @@ function TAuthService.BeginRegistration(const PhoneNumber: string): TJSONObject;
...
@@ -314,6 +323,10 @@ function TAuthService.BeginRegistration(const PhoneNumber: string): TJSONObject;
var
var
code
,
normalizedPhone
,
verificationToken
,
deviceName
:
string
;
code
,
normalizedPhone
,
verificationToken
,
deviceName
:
string
;
deviceId
:
Integer
;
deviceId
:
Integer
;
secondsSinceLastSend
:
Integer
;
secondsUntilRetry
:
Integer
;
sentAt
:
TDateTime
;
attempt
:
TVerificationAttempt
;
q
:
TUniQuery
;
q
:
TUniQuery
;
begin
begin
Logger
.
Log
(
3
,
'---TAuthService.BeginRegistration initiated with phone number: '
+
PhoneNumber
);
Logger
.
Log
(
3
,
'---TAuthService.BeginRegistration initiated with phone number: '
+
PhoneNumber
);
...
@@ -363,6 +376,31 @@ begin
...
@@ -363,6 +376,31 @@ begin
q
.
Free
;
q
.
Free
;
end
;
end
;
secondsUntilRetry
:=
0
;
TMonitor
.
Enter
(
VerificationLock
);
try
if
VerificationSentAt
.
TryGetValue
(
normalizedPhone
,
sentAt
)
then
begin
secondsSinceLastSend
:=
SecondsBetween
(
Now
,
sentAt
);
if
secondsSinceLastSend
<
VERIFICATION_RESEND_SECONDS
then
secondsUntilRetry
:=
VERIFICATION_RESEND_SECONDS
-
secondsSinceLastSend
;
end
;
if
secondsUntilRetry
=
0
then
VerificationSentAt
.
AddOrSetValue
(
normalizedPhone
,
Now
);
finally
TMonitor
.
Exit
(
VerificationLock
);
end
;
if
secondsUntilRetry
>
0
then
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
Format
(
'Please wait %d seconds before requesting another verification code.'
,
[
secondsUntilRetry
]));
Exit
;
end
;
code
:=
GenerateVerificationCode
;
code
:=
GenerateVerificationCode
;
verificationToken
:=
CreateVerificationToken
(
normalizedPhone
,
code
);
verificationToken
:=
CreateVerificationToken
(
normalizedPhone
,
code
);
...
@@ -371,6 +409,12 @@ begin
...
@@ -371,6 +409,12 @@ begin
except
except
on
E
:
Exception
do
on
E
:
Exception
do
begin
begin
TMonitor
.
Enter
(
VerificationLock
);
try
VerificationSentAt
.
Remove
(
normalizedPhone
);
finally
TMonitor
.
Exit
(
VerificationLock
);
end
;
Logger
.
Log
(
2
,
'--Error sending registration verification code: '
+
E
.
Message
);
Logger
.
Log
(
2
,
'--Error sending registration verification code: '
+
E
.
Message
);
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'The verification code could not be sent. Please try again.'
);
Result
.
AddPair
(
'message'
,
'The verification code could not be sent. Please try again.'
);
...
@@ -378,10 +422,18 @@ begin
...
@@ -378,10 +422,18 @@ begin
end
;
end
;
end
;
end
;
// ADD THIS DELETE BEFORE DEPLOY
attempt
.
Attempts
:=
0
;
attempt
.
ExpiresAt
:=
IncMinute
(
Now
,
5
);
TMonitor
.
Enter
(
VerificationLock
);
try
VerificationAttempts
.
AddOrSetValue
(
normalizedPhone
,
attempt
);
finally
TMonitor
.
Exit
(
VerificationLock
);
end
;
Logger
.
Log
(
3
,
Format
(
Logger
.
Log
(
3
,
Format
(
'Verification code se
t to %s for phone number: %s with
device id %d and device name: %s'
,
'Verification code se
nt for
device id %d and device name: %s'
,
[
code
,
normalizedPhone
,
deviceId
,
deviceName
]));
[
deviceId
,
deviceName
]));
Result
.
AddPair
(
'status'
,
'ok'
);
Result
.
AddPair
(
'status'
,
'ok'
);
Result
.
AddPair
(
'message'
,
'Verification code sent.'
);
Result
.
AddPair
(
'message'
,
'Verification code sent.'
);
Result
.
AddPair
(
'verificationToken'
,
verificationToken
);
Result
.
AddPair
(
'verificationToken'
,
verificationToken
);
...
@@ -392,13 +444,11 @@ function TAuthService.VerifyRegistrationCode(const PhoneNumber,
...
@@ -392,13 +444,11 @@ function TAuthService.VerifyRegistrationCode(const PhoneNumber,
VerificationCode
,
VerificationToken
:
string
):
TJSONObject
;
VerificationCode
,
VerificationToken
:
string
):
TJSONObject
;
var
var
normalizedPhone
,
token
,
challengeB64
:
string
;
normalizedPhone
,
token
,
challengeB64
:
string
;
attempt
:
TVerificationAttempt
;
verificationValid
:
Boolean
;
tooManyAttempts
:
Boolean
;
q
:
TUniQuery
;
q
:
TUniQuery
;
begin
begin
// ADD THIS DELETE BEFORE DEPLOY
Logger
.
Log
(
3
,
Format
(
'---TAuthService.VerifyRegistrationCode initiated with phone number: %s and verification code: %s'
,
[
PhoneNumber
,
VerificationCode
]));
Result
:=
TJSONObject
.
Create
;
Result
:=
TJSONObject
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
...
@@ -433,12 +483,49 @@ begin
...
@@ -433,12 +483,49 @@ begin
q
.
Free
;
q
.
Free
;
end
;
end
;
if
(
Length
(
Trim
(
VerificationCode
))
<>
6
)
or
verificationValid
:=
False
;
not
VerifyVerificationToken
(
normalizedPhone
,
Trim
(
VerificationCode
),
tooManyAttempts
:=
False
;
VerificationToken
)
then
TMonitor
.
Enter
(
VerificationLock
);
try
if
VerificationAttempts
.
TryGetValue
(
normalizedPhone
,
attempt
)
and
(
attempt
.
ExpiresAt
<
Now
)
then
VerificationAttempts
.
Remove
(
normalizedPhone
);
if
VerificationAttempts
.
TryGetValue
(
normalizedPhone
,
attempt
)
and
(
attempt
.
Attempts
>=
MAX_VERIFICATION_ATTEMPTS
)
then
tooManyAttempts
:=
True
else
begin
verificationValid
:=
(
Length
(
Trim
(
VerificationCode
))
=
6
)
and
VerifyVerificationToken
(
normalizedPhone
,
Trim
(
VerificationCode
),
VerificationToken
);
if
verificationValid
then
VerificationAttempts
.
Remove
(
normalizedPhone
)
else
begin
if
not
VerificationAttempts
.
TryGetValue
(
normalizedPhone
,
attempt
)
then
begin
attempt
.
Attempts
:=
0
;
attempt
.
ExpiresAt
:=
IncMinute
(
Now
,
5
);
end
;
Inc
(
attempt
.
Attempts
);
VerificationAttempts
.
AddOrSetValue
(
normalizedPhone
,
attempt
);
tooManyAttempts
:=
attempt
.
Attempts
>=
MAX_VERIFICATION_ATTEMPTS
;
end
;
end
;
finally
TMonitor
.
Exit
(
VerificationLock
);
end
;
if
not
verificationValid
then
begin
begin
Logger
.
Log
(
2
,
'--Verification failed for phone number: '
+
normalizedPhone
);
Logger
.
Log
(
2
,
'--Verification failed for phone number: '
+
normalizedPhone
);
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'status'
,
'error'
);
if
tooManyAttempts
then
Result
.
AddPair
(
'message'
,
'Too many incorrect attempts. Request a new verification code.'
)
else
Result
.
AddPair
(
'message'
,
'Invalid or expired verification code.'
);
Result
.
AddPair
(
'message'
,
'Invalid or expired verification code.'
);
Exit
;
Exit
;
end
;
end
;
...
@@ -1697,6 +1784,14 @@ begin
...
@@ -1697,6 +1784,14 @@ begin
end
;
end
;
initialization
initialization
VerificationLock
:=
TObject
.
Create
;
VerificationSentAt
:=
TDictionary
<
string
,
TDateTime
>.
Create
;
VerificationAttempts
:=
TDictionary
<
string
,
TVerificationAttempt
>.
Create
;
RegisterServiceType
(
TAuthService
);
RegisterServiceType
(
TAuthService
);
finalization
VerificationAttempts
.
Free
;
VerificationSentAt
.
Free
;
VerificationLock
.
Free
;
end
.
end
.
emiMobileServer/Source/Common.Config.pas
View file @
6a8f477c
...
@@ -45,7 +45,7 @@ implementation
...
@@ -45,7 +45,7 @@ implementation
uses
uses
System
.
JSON
,
System
.
SysUtils
,
System
.
IOUtils
,
System
.
StrUtils
,
System
.
JSON
,
System
.
SysUtils
,
System
.
IOUtils
,
System
.
StrUtils
,
Common
.
Logging
;
Common
.
Logging
,
Common
.
Ini
;
procedure
LoadServerConfig
;
procedure
LoadServerConfig
;
...
@@ -65,6 +65,9 @@ var
...
@@ -65,6 +65,9 @@ var
jsonObj
:
TJSONObject
;
jsonObj
:
TJSONObject
;
begin
begin
Logger
.
Log
(
1
,
'--LoadServerConfig - start'
);
Logger
.
Log
(
1
,
'--LoadServerConfig - start'
);
serverConfig
.
twilioAccountSid
:=
IniEntries
.
TwilioAccountSid
;
serverConfig
.
twilioAuthToken
:=
IniEntries
.
TwilioAuthToken
;
serverConfig
.
twilioFromNumber
:=
IniEntries
.
TwilioFromNumber
;
configFile
:=
TPath
.
ChangeExtension
(
ParamStr
(
0
),
'.json'
);
configFile
:=
TPath
.
ChangeExtension
(
ParamStr
(
0
),
'.json'
);
Logger
.
Log
(
1
,
'-- Config file: '
+
configFile
);
Logger
.
Log
(
1
,
'-- Config file: '
+
configFile
);
...
@@ -93,9 +96,6 @@ begin
...
@@ -93,9 +96,6 @@ begin
s
:=
GetStr
(
jsonObj
,
'reportsFolder'
);
if
s
<>
''
then
serverConfig
.
reportsFolder
:=
s
;
s
:=
GetStr
(
jsonObj
,
'reportsFolder'
);
if
s
<>
''
then
serverConfig
.
reportsFolder
:=
s
;
s
:=
GetStr
(
jsonObj
,
'rpId'
);
if
s
<>
''
then
serverConfig
.
rpId
:=
s
;
s
:=
GetStr
(
jsonObj
,
'rpId'
);
if
s
<>
''
then
serverConfig
.
rpId
:=
s
;
s
:=
GetStr
(
jsonObj
,
'rpName'
);
if
s
<>
''
then
serverConfig
.
rpName
:=
s
;
s
:=
GetStr
(
jsonObj
,
'rpName'
);
if
s
<>
''
then
serverConfig
.
rpName
:=
s
;
serverConfig
.
twilioAccountSid
:=
GetStr
(
jsonObj
,
'twilioAccountSid'
);
serverConfig
.
twilioAuthToken
:=
GetStr
(
jsonObj
,
'twilioAuthToken'
);
serverConfig
.
twilioFromNumber
:=
GetStr
(
jsonObj
,
'twilioFromNumber'
);
serverConfig
.
auditEnabled
:=
jsonObj
.
GetValue
<
Boolean
>(
'auditEnabled'
,
serverConfig
.
auditEnabled
);
serverConfig
.
auditEnabled
:=
jsonObj
.
GetValue
<
Boolean
>(
'auditEnabled'
,
serverConfig
.
auditEnabled
);
finally
finally
jsonObj
.
Free
;
jsonObj
.
Free
;
...
@@ -104,8 +104,8 @@ begin
...
@@ -104,8 +104,8 @@ begin
Logger
.
Log
(
1
,
''
);
Logger
.
Log
(
1
,
''
);
Logger
.
Log
(
1
,
'--- Server Config Values ---'
);
Logger
.
Log
(
1
,
'--- Server Config Values ---'
);
Logger
.
Log
(
1
,
'-- url: '
+
serverConfig
.
url
+
IfThen
(
serverConfig
.
url
=
defaultServerUrl
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- url: '
+
serverConfig
.
url
+
IfThen
(
serverConfig
.
url
=
defaultServerUrl
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- adminPassword: '
+
serverConfig
.
adminPassword
+
IfThen
(
serverConfig
.
adminPassword
=
'whatisthisusedfor'
,
' [default]'
,
' [from config
]'
));
Logger
.
Log
(
1
,
'-- adminPassword: '
+
IfThen
(
serverConfig
.
adminPassword
<>
''
,
'[configured]'
,
'[not set
]'
));
Logger
.
Log
(
1
,
'-- jwtTokenSecret: '
+
serverConfig
.
jwtTokenSecret
+
IfThen
(
serverConfig
.
jwtTokenSecret
=
'super_secret0123super_secret4567'
,
' [default]'
,
' [from config
]'
));
Logger
.
Log
(
1
,
'-- jwtTokenSecret: '
+
IfThen
(
serverConfig
.
jwtTokenSecret
<>
''
,
'[configured]'
,
'[not set
]'
));
Logger
.
Log
(
1
,
'-- webAppFolder: '
+
serverConfig
.
webAppFolder
+
IfThen
(
serverConfig
.
webAppFolder
=
'static'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- webAppFolder: '
+
serverConfig
.
webAppFolder
+
IfThen
(
serverConfig
.
webAppFolder
=
'static'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- rpId: '
+
serverConfig
.
rpId
+
IfThen
(
serverConfig
.
rpId
=
'wcemimobile.em-sys.net'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- rpId: '
+
serverConfig
.
rpId
+
IfThen
(
serverConfig
.
rpId
=
'wcemimobile.em-sys.net'
,
' [default]'
,
' [from config]'
));
Logger
.
Log
(
1
,
'-- auditEnabled: '
+
BoolToStr
(
serverConfig
.
auditEnabled
,
True
));
Logger
.
Log
(
1
,
'-- auditEnabled: '
+
BoolToStr
(
serverConfig
.
auditEnabled
,
True
));
...
...
emiMobileServer/Source/Common.Ini.pas
View file @
6a8f477c
...
@@ -18,6 +18,14 @@ type
...
@@ -18,6 +18,14 @@ type
FWebClientVersion
:
string
;
FWebClientVersion
:
string
;
FWebClientVersionFromIni
:
Boolean
;
FWebClientVersionFromIni
:
Boolean
;
// [Twilio]
FTwilioAccountSid
:
string
;
FTwilioAccountSidFromIni
:
Boolean
;
FTwilioAuthToken
:
string
;
FTwilioAuthTokenFromIni
:
Boolean
;
FTwilioFromNumber
:
string
;
FTwilioFromNumberFromIni
:
Boolean
;
// [Database]
// [Database]
FDatabaseServer
:
string
;
FDatabaseServer
:
string
;
FDatabaseServerFromIni
:
Boolean
;
FDatabaseServerFromIni
:
Boolean
;
...
@@ -44,6 +52,14 @@ type
...
@@ -44,6 +52,14 @@ type
property
WebClientVersion
:
string
read
FWebClientVersion
;
property
WebClientVersion
:
string
read
FWebClientVersion
;
property
WebClientVersionFromIni
:
Boolean
read
FWebClientVersionFromIni
;
property
WebClientVersionFromIni
:
Boolean
read
FWebClientVersionFromIni
;
// [Twilio]
property
TwilioAccountSid
:
string
read
FTwilioAccountSid
;
property
TwilioAccountSidFromIni
:
Boolean
read
FTwilioAccountSidFromIni
;
property
TwilioAuthToken
:
string
read
FTwilioAuthToken
;
property
TwilioAuthTokenFromIni
:
Boolean
read
FTwilioAuthTokenFromIni
;
property
TwilioFromNumber
:
string
read
FTwilioFromNumber
;
property
TwilioFromNumberFromIni
:
Boolean
read
FTwilioFromNumberFromIni
;
// [Database]
// [Database]
property
DatabaseServer
:
string
read
FDatabaseServer
;
property
DatabaseServer
:
string
read
FDatabaseServer
;
property
DatabaseServerFromIni
:
Boolean
read
FDatabaseServerFromIni
;
property
DatabaseServerFromIni
:
Boolean
read
FDatabaseServerFromIni
;
...
@@ -93,6 +109,16 @@ begin
...
@@ -93,6 +109,16 @@ begin
FWebClientVersion
:=
iniFile
.
ReadString
(
'Settings'
,
'webClientVersion'
,
''
);
FWebClientVersion
:=
iniFile
.
ReadString
(
'Settings'
,
'webClientVersion'
,
''
);
FWebClientVersionFromIni
:=
iniFile
.
ValueExists
(
'Settings'
,
'webClientVersion'
);
FWebClientVersionFromIni
:=
iniFile
.
ValueExists
(
'Settings'
,
'webClientVersion'
);
// [Twilio]
FTwilioAccountSid
:=
iniFile
.
ReadString
(
'Twilio'
,
'AccountSid'
,
''
);
FTwilioAccountSidFromIni
:=
iniFile
.
ValueExists
(
'Twilio'
,
'AccountSid'
);
FTwilioAuthToken
:=
iniFile
.
ReadString
(
'Twilio'
,
'AuthToken'
,
''
);
FTwilioAuthTokenFromIni
:=
iniFile
.
ValueExists
(
'Twilio'
,
'AuthToken'
);
FTwilioFromNumber
:=
iniFile
.
ReadString
(
'Twilio'
,
'FromNumber'
,
''
);
FTwilioFromNumberFromIni
:=
iniFile
.
ValueExists
(
'Twilio'
,
'FromNumber'
);
// [Database]
// [Database]
FDatabaseServer
:=
iniFile
.
ReadString
(
'Database'
,
'Server'
,
''
);
FDatabaseServer
:=
iniFile
.
ReadString
(
'Database'
,
'Server'
,
''
);
FDatabaseServerFromIni
:=
iniFile
.
ValueExists
(
'Database'
,
'Server'
);
FDatabaseServerFromIni
:=
iniFile
.
ValueExists
(
'Database'
,
'Server'
);
...
...
emiMobileServer/Source/WebSocket.Manager.pas
View file @
6a8f477c
...
@@ -29,6 +29,7 @@ type
...
@@ -29,6 +29,7 @@ type
private
private
FConnectionId
:
string
;
FConnectionId
:
string
;
FUserId
:
string
;
FUserId
:
string
;
FCredentialId
:
string
;
FClientVersion
:
string
;
FClientVersion
:
string
;
FConnectedAt
:
TDateTime
;
FConnectedAt
:
TDateTime
;
FLastSeenAt
:
TDateTime
;
FLastSeenAt
:
TDateTime
;
...
@@ -38,6 +39,7 @@ type
...
@@ -38,6 +39,7 @@ type
public
public
property
ConnectionId
:
string
read
FConnectionId
write
FConnectionId
;
property
ConnectionId
:
string
read
FConnectionId
write
FConnectionId
;
property
UserId
:
string
read
FUserId
write
FUserId
;
property
UserId
:
string
read
FUserId
write
FUserId
;
property
CredentialId
:
string
read
FCredentialId
write
FCredentialId
;
property
ClientVersion
:
string
read
FClientVersion
write
FClientVersion
;
property
ClientVersion
:
string
read
FClientVersion
write
FClientVersion
;
property
ConnectedAt
:
TDateTime
read
FConnectedAt
write
FConnectedAt
;
property
ConnectedAt
:
TDateTime
read
FConnectedAt
write
FConnectedAt
;
property
LastSeenAt
:
TDateTime
read
FLastSeenAt
write
FLastSeenAt
;
property
LastSeenAt
:
TDateTime
read
FLastSeenAt
write
FLastSeenAt
;
...
@@ -59,11 +61,17 @@ type
...
@@ -59,11 +61,17 @@ type
FStarted
:
Integer
;
FStarted
:
Integer
;
FOnClientsChanged
:
TClientsChangedEvent
;
FOnClientsChanged
:
TClientsChangedEvent
;
function
GetQueryParameter
(
AQuery
,
AName
:
string
):
string
;
function
IsDeviceActive
(
const
ACredentialId
:
string
):
Boolean
;
function
AuthenticateConnection
(
AConnection
:
TTMSFNCWebSocketServerConnection
;
out
AUserId
,
ACredentialId
,
AReason
:
string
):
Boolean
;
function
TrySendTextToClient
(
const
AConnectionId
,
AMessage
:
string
;
function
TrySendTextToClient
(
const
AConnectionId
,
AMessage
:
string
;
ALogFailure
:
Boolean
=
True
):
Boolean
;
ALogFailure
:
Boolean
=
True
):
Boolean
;
function
TryCloseClient
(
const
AConnectionId
:
string
):
Boolean
;
function
TryCloseClient
(
const
AConnectionId
:
string
):
Boolean
;
procedure
HeartbeatSweep
;
procedure
HeartbeatSweep
;
procedure
NotifyClientsChanged
;
procedure
NotifyClientsChanged
;
procedure
AllowConnection
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
;
var
AAllow
:
Boolean
);
procedure
HandshakeResponseSent
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
);
procedure
HandshakeResponseSent
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
);
procedure
MessageReceived
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketConnection
;
const
AMessage
:
string
);
procedure
MessageReceived
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketConnection
;
const
AMessage
:
string
);
procedure
ClientDisconnected
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketConnection
);
procedure
ClientDisconnected
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketConnection
);
...
@@ -85,12 +93,37 @@ implementation
...
@@ -85,12 +93,37 @@ implementation
uses
uses
System
.
DateUtils
,
System
.
DateUtils
,
System
.
NetEncoding
,
IdStackConsts
,
IdStackConsts
,
DBAccess
,
Uni
,
Bcl
.
JOSE
.
Core
.
JWT
,
Bcl
.
JOSE
.
Core
.
Builder
,
Common
.
Config
,
Common
.
Ini
,
Common
.
Logging
;
Common
.
Logging
;
type
type
TWebSocketServerConnectionAccess
=
class
(
TTMSFNCWebSocketServerConnection
);
TWebSocketServerConnectionAccess
=
class
(
TTMSFNCWebSocketServerConnection
);
function
TWebSocketManager
.
GetQueryParameter
(
AQuery
,
AName
:
string
):
string
;
var
queryPart
:
string
;
nameValue
:
TArray
<
string
>;
begin
Result
:=
''
;
for
queryPart
in
AQuery
.
Split
([
'&'
])
do
begin
nameValue
:=
queryPart
.
Split
([
'='
],
2
);
if
(
Length
(
nameValue
)
=
2
)
and
SameText
(
TNetEncoding
.
URL
.
Decode
(
nameValue
[
0
]),
AName
)
then
begin
Result
:=
TNetEncoding
.
URL
.
Decode
(
nameValue
[
1
]);
Exit
;
end
;
end
;
end
;
constructor
TWebSocketManager
.
Create
;
constructor
TWebSocketManager
.
Create
;
begin
begin
inherited
Create
;
inherited
Create
;
...
@@ -102,6 +135,7 @@ begin
...
@@ -102,6 +135,7 @@ begin
FServer
:=
TTMSFNCWebSocketServer
.
Create
;
FServer
:=
TTMSFNCWebSocketServer
.
Create
;
FServer
.
Port
:=
WEBSOCKET_PORT
;
FServer
.
Port
:=
WEBSOCKET_PORT
;
FServer
.
UseSSL
:=
False
;
FServer
.
UseSSL
:=
False
;
FServer
.
OnAllow
:=
AllowConnection
;
FServer
.
OnHandshakeResponseSent
:=
HandshakeResponseSent
;
FServer
.
OnHandshakeResponseSent
:=
HandshakeResponseSent
;
FServer
.
OnMessageReceived
:=
MessageReceived
;
FServer
.
OnMessageReceived
:=
MessageReceived
;
FServer
.
OnDisconnect
:=
ClientDisconnected
;
FServer
.
OnDisconnect
:=
ClientDisconnected
;
...
@@ -124,6 +158,113 @@ begin
...
@@ -124,6 +158,113 @@ begin
FHeartbeatThread
.
Start
;
FHeartbeatThread
.
Start
;
end
;
end
;
function
TWebSocketManager
.
IsDeviceActive
(
const
ACredentialId
:
string
):
Boolean
;
var
connection
:
TUniConnection
;
query
:
TUniQuery
;
begin
Result
:=
False
;
connection
:=
TUniConnection
.
Create
(
nil
);
try
connection
.
ProviderName
:=
'PostgreSQL'
;
connection
.
Server
:=
IniEntries
.
DatabaseServer
;
connection
.
Port
:=
IniEntries
.
DatabasePort
;
connection
.
Database
:=
IniEntries
.
DatabaseName
;
connection
.
Username
:=
IniEntries
.
DatabaseUsername
;
connection
.
Password
:=
IniEntries
.
DatabasePassword
;
connection
.
LoginPrompt
:=
False
;
connection
.
Connect
;
query
:=
TUniQuery
.
Create
(
nil
);
try
query
.
Connection
:=
connection
;
query
.
SQL
.
Text
:=
'SELECT id FROM lems.device_registrations '
+
'WHERE credential_id = :CID AND status = ''active'''
;
query
.
ParamByName
(
'CID'
).
AsString
:=
ACredentialId
;
query
.
Open
;
Result
:=
not
query
.
IsEmpty
;
finally
query
.
Free
;
end
;
finally
connection
.
Free
;
end
;
end
;
function
TWebSocketManager
.
AuthenticateConnection
(
AConnection
:
TTMSFNCWebSocketServerConnection
;
out
AUserId
,
ACredentialId
,
AReason
:
string
):
Boolean
;
var
token
:
string
;
jwt
:
TJWT
;
begin
Result
:=
False
;
AUserId
:=
''
;
ACredentialId
:=
''
;
AReason
:=
''
;
token
:=
GetQueryParameter
(
AConnection
.
HandshakeRequest
.
QueryParams
,
'token'
);
if
token
=
''
then
begin
AReason
:=
'missing token'
;
Exit
;
end
;
jwt
:=
nil
;
try
try
jwt
:=
TJOSE
.
Verify
(
ServerConfig
.
jwtTokenSecret
,
token
);
except
AReason
:=
'invalid token'
;
Exit
;
end
;
if
not
Assigned
(
jwt
)
or
not
jwt
.
Verified
then
begin
AReason
:=
'invalid token'
;
Exit
;
end
;
if
(
jwt
.
Claims
.
Expiration
=
0
)
or
(
jwt
.
Claims
.
Expiration
<=
Now
)
then
begin
AReason
:=
'expired token'
;
Exit
;
end
;
if
jwt
.
Claims
.
JSON
.
GetValue
<
Boolean
>(
'device_management_only'
,
False
)
then
begin
AReason
:=
'device manager token'
;
Exit
;
end
;
AUserId
:=
Trim
(
jwt
.
Claims
.
JSON
.
GetValue
<
string
>(
'user_name'
,
''
));
ACredentialId
:=
Trim
(
jwt
.
Claims
.
JSON
.
GetValue
<
string
>(
'credential_id'
,
''
));
if
AUserId
=
''
then
begin
AReason
:=
'missing username'
;
Exit
;
end
;
if
ACredentialId
=
''
then
begin
AReason
:=
'missing credential id'
;
Exit
;
end
;
if
not
IsDeviceActive
(
ACredentialId
)
then
begin
AReason
:=
'device is not active'
;
Exit
;
end
;
Result
:=
True
;
finally
jwt
.
Free
;
end
;
end
;
destructor
TWebSocketManager
.
Destroy
;
destructor
TWebSocketManager
.
Destroy
;
begin
begin
Stop
;
Stop
;
...
@@ -345,6 +486,41 @@ begin
...
@@ -345,6 +486,41 @@ begin
FOnClientsChanged
;
FOnClientsChanged
;
end
;
end
;
procedure
TWebSocketManager
.
AllowConnection
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
;
var
AAllow
:
Boolean
);
var
client
:
TConnectedClient
;
userId
:
string
;
credentialId
:
string
;
reason
:
string
;
begin
AAllow
:=
False
;
client
:=
nil
;
try
if
not
AuthenticateConnection
(
AConnection
,
userId
,
credentialId
,
reason
)
then
begin
Logger
.
Log
(
2
,
Format
(
'WebSocket connection rejected from %s: %s'
,
[
AConnection
.
PeerIP
,
reason
]));
Exit
;
end
;
client
:=
TConnectedClient
.
Create
;
client
.
UserId
:=
userId
;
client
.
CredentialId
:=
credentialId
;
AConnection
.
OwnsUserData
:=
True
;
AConnection
.
UserData
:=
client
;
client
:=
nil
;
AAllow
:=
True
;
except
on
E
:
Exception
do
Logger
.
Log
(
1
,
Format
(
'WebSocket authentication failed from %s: %s'
,
[
AConnection
.
PeerIP
,
E
.
Message
]));
end
;
client
.
Free
;
end
;
procedure
TWebSocketManager
.
HandshakeResponseSent
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
);
procedure
TWebSocketManager
.
HandshakeResponseSent
(
Sender
:
TObject
;
AConnection
:
TTMSFNCWebSocketServerConnection
);
var
var
client
:
TConnectedClient
;
client
:
TConnectedClient
;
...
@@ -352,10 +528,13 @@ var
...
@@ -352,10 +528,13 @@ var
begin
begin
CreateGUID
(
guid
);
CreateGUID
(
guid
);
client
:=
TConnectedClient
(
AConnection
.
UserData
);
if
not
Assigned
(
client
)
then
Exit
;
TWebSocketServerConnectionAccess
(
AConnection
).
Connection
.
Socket
.
Binding
.
SetSockOpt
(
TWebSocketServerConnectionAccess
(
AConnection
).
Connection
.
Socket
.
Binding
.
SetSockOpt
(
Id_SOL_SOCKET
,
Id_SO_SNDTIMEO
,
WEBSOCKET_SEND_TIMEOUT_MS
);
Id_SOL_SOCKET
,
Id_SO_SNDTIMEO
,
WEBSOCKET_SEND_TIMEOUT_MS
);
client
:=
TConnectedClient
.
Create
;
client
.
ConnectionId
:=
GUIDToString
(
guid
);
client
.
ConnectionId
:=
GUIDToString
(
guid
);
client
.
ConnectedAt
:=
Now
;
client
.
ConnectedAt
:=
Now
;
client
.
LastSeenAt
:=
client
.
ConnectedAt
;
client
.
LastSeenAt
:=
client
.
ConnectedAt
;
...
@@ -366,14 +545,12 @@ begin
...
@@ -366,14 +545,12 @@ begin
TMonitor
.
Enter
(
FClientsLock
);
TMonitor
.
Enter
(
FClientsLock
);
try
try
FClients
.
Add
(
client
);
FClients
.
Add
(
client
);
AConnection
.
OwnsUserData
:=
False
;
finally
finally
TMonitor
.
Exit
(
FClientsLock
);
TMonitor
.
Exit
(
FClientsLock
);
end
;
end
;
AConnection
.
OwnsUserData
:=
False
;
Logger
.
Log
(
1
,
'WebSocket client connected: '
+
client
.
ConnectionId
+
' - '
+
client
.
UserId
);
AConnection
.
UserData
:=
client
;
Logger
.
Log
(
1
,
'WebSocket client connected: '
+
client
.
ConnectionId
);
NotifyClientsChanged
;
NotifyClientsChanged
;
end
;
end
;
...
@@ -381,7 +558,6 @@ procedure TWebSocketManager.MessageReceived(Sender: TObject; AConnection: TTMSFN
...
@@ -381,7 +558,6 @@ procedure TWebSocketManager.MessageReceived(Sender: TObject; AConnection: TTMSFN
var
var
json
:
TJSONValue
;
json
:
TJSONValue
;
messageType
:
string
;
messageType
:
string
;
userId
:
string
;
clientVersion
:
string
;
clientVersion
:
string
;
connectionId
:
string
;
connectionId
:
string
;
client
:
TConnectedClient
;
client
:
TConnectedClient
;
...
@@ -399,10 +575,6 @@ begin
...
@@ -399,10 +575,6 @@ begin
(
not
SameText
(
messageType
,
'heartbeat'
))
then
(
not
SameText
(
messageType
,
'heartbeat'
))
then
Exit
;
Exit
;
if
SameText
(
messageType
,
'identify'
)
and
(
not
json
.
TryGetValue
<
string
>(
'userId'
,
userId
))
then
Exit
;
clientVersion
:=
''
;
clientVersion
:=
''
;
if
SameText
(
messageType
,
'identify'
)
then
if
SameText
(
messageType
,
'identify'
)
then
json
.
TryGetValue
<
string
>(
'clientVersion'
,
clientVersion
);
json
.
TryGetValue
<
string
>(
'clientVersion'
,
clientVersion
);
...
@@ -421,10 +593,7 @@ begin
...
@@ -421,10 +593,7 @@ begin
connectionId
:=
client
.
ConnectionId
;
connectionId
:=
client
.
ConnectionId
;
if
SameText
(
messageType
,
'identify'
)
then
if
SameText
(
messageType
,
'identify'
)
then
begin
client
.
ClientVersion
:=
clientVersion
client
.
UserId
:=
userId
;
client
.
ClientVersion
:=
clientVersion
;
end
else
else
client
.
HeartbeatSeen
:=
True
;
client
.
HeartbeatSeen
:=
True
;
finally
finally
...
@@ -435,7 +604,7 @@ begin
...
@@ -435,7 +604,7 @@ begin
begin
begin
Logger
.
Log
(
1
,
Format
(
Logger
.
Log
(
1
,
Format
(
'WebSocket client identified: %s - %s - version %s'
,
'WebSocket client identified: %s - %s - version %s'
,
[
connectionId
,
u
serId
,
clientVersion
]));
[
connectionId
,
client
.
U
serId
,
clientVersion
]));
NotifyClientsChanged
;
NotifyClientsChanged
;
end
end
else
else
...
...
emiMobileServer/bin/emiMobileServer.ini
View file @
6a8f477c
[Settings]
[Settings]
LogFileNum
=
123
LogFileNum
=
130
webClientVersion
=
0.9.4.6
webClientVersion
=
0.9.4.7
[Twilio]
AccountSid
=
AuthToken
=
FromNumber
=
[Database]
[Database]
--Server
=
192.168.91.129
--Server
=
192.168.91.129
...
...
emiMobileServer/bin/emiMobileServer.json
View file @
6a8f477c
...
@@ -2,8 +2,5 @@
...
@@ -2,8 +2,5 @@
"url"
:
"http://localhost:2001/emsys/emiMobile/"
,
"url"
:
"http://localhost:2001/emsys/emiMobile/"
,
"jwtTokenSecret"
:
"super_secret0123super_secret4567"
,
"jwtTokenSecret"
:
"super_secret0123super_secret4567"
,
"adminPassword"
:
"whatisthisusedfor?"
,
"adminPassword"
:
"whatisthisusedfor?"
,
"webAppFolder"
:
"static"
,
"webAppFolder"
:
"static"
"twilioAccountSid"
:
""
,
"twilioAuthToken"
:
""
,
"twilioFromNumber"
:
""
}
}
emiMobileServer/emiMobileServer.dproj
View file @
6a8f477c
...
@@ -108,9 +108,9 @@
...
@@ -108,9 +108,9 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.
6
;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.2.0;Comments=</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.
7
;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.2.0;Comments=</VerInfo_Keys>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Build>
6
</VerInfo_Build>
<VerInfo_Build>
7
</VerInfo_Build>
</PropertyGroup>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
...
...
twilioinfo.txt
0 → 100644
View file @
6a8f477c
AccountSid=AC717f06b3c10bbecff42487426e34fdb3
AuthToken=a0dec128c1b5a3231909ba81da8947b6
FromNumber=+18775346578
\ No newline at end of file
webEMIMobile/ConnectionModule.pas
View file @
6a8f477c
...
@@ -21,7 +21,7 @@ type
...
@@ -21,7 +21,7 @@ type
procedure
VerifyVersion
(
SuccessProc
:
TSuccessProc
);
procedure
VerifyVersion
(
SuccessProc
:
TSuccessProc
);
public
public
property
WsUrl
:
string
read
FWsUrl
;
property
WsUrl
:
string
read
FWsUrl
;
const
clientVersion
=
'0.9.4.
6
'
;
const
clientVersion
=
'0.9.4.
7
'
;
procedure
InitApp
(
SuccessProc
:
TSuccessProc
;
procedure
InitApp
(
SuccessProc
:
TSuccessProc
;
UnauthorizedAccessProc
:
TUnauthorizedAccessProc
);
UnauthorizedAccessProc
:
TUnauthorizedAccessProc
);
end
;
end
;
...
...
webEMIMobile/View.DeviceRegistration.dfm
View file @
6a8f477c
...
@@ -44,7 +44,6 @@ object FViewDeviceRegistration: TFViewDeviceRegistration
...
@@ -44,7 +44,6 @@ object FViewDeviceRegistration: TFViewDeviceRegistration
Height = 21
Height = 21
ElementID = 'view.devicereg.edtverificationcode'
ElementID = 'view.devicereg.edtverificationcode'
HeightPercent = 100.000000000000000000
HeightPercent = 100.000000000000000000
TabOrder = 0
TextHint = 'Six-digit code'
TextHint = 'Six-digit code'
WidthPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
end
...
...
webEMIMobile/View.DeviceRegistration.pas
View file @
6a8f477c
...
@@ -173,7 +173,7 @@ begin
...
@@ -173,7 +173,7 @@ begin
end
;
end
;
for
i
:=
1
to
Length
(
verificationCode
)
do
for
i
:=
1
to
Length
(
verificationCode
)
do
if
not
CharInSet
(
verificationCode
[
i
],
[
'0'
..
'9'
]
)
then
if
(
verificationCode
[
i
]
<
'0'
)
or
(
verificationCode
[
i
]
>
'9'
)
then
begin
begin
ShowNotification
(
'Please enter the six-digit verification code.'
);
ShowNotification
(
'Please enter the six-digit verification code.'
);
Exit
;
Exit
;
...
...
webEMIMobile/wcEmiMobile.dproj
View file @
6a8f477c
...
@@ -91,12 +91,12 @@
...
@@ -91,12 +91,12 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.
6
;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.3.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.
7
;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.3.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Build>
6
</VerInfo_Build>
<VerInfo_Build>
7
</VerInfo_Build>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebOutputPath>..\emiMobileServer\bin\static</TMSWebOutputPath>
<TMSWebOutputPath>..\emiMobileServer\bin\static</TMSWebOutputPath>
...
...
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