Skip to content

Commit c784d5c

Browse files
authored
Merge pull request #1495 from simplex-chat/short-links
smp protocol: short 1-time invitations and contact address links
2 parents 9abc0fa + c2c4730 commit c784d5c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3643
-587
lines changed

.github/workflows/build.yml

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,23 @@ jobs:
4646
uses: actions/checkout@v3
4747

4848
- name: Set up Docker Buildx
49-
uses: docker/setup-buildx-action@v3
49+
uses: simplex-chat/docker-setup-buildx-action@v3
50+
51+
- name: Install PostgreSQL 15 client tools
52+
if: matrix.os == '22.04'
53+
shell: bash
54+
run: |
55+
# Import the repository signing key
56+
sudo install -d /usr/share/postgresql-common/pgdg
57+
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
58+
# Add the PostgreSQL APT repository
59+
sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
60+
# Update repository and install postgresql tools
61+
sudo apt update
62+
sudo apt -y install postgresql-client-15
5063
5164
- name: Build and cache Docker image
52-
uses: docker/build-push-action@v6
65+
uses: simplex-chat/docker-build-push-action@v6
5366
with:
5467
context: .
5568
load: true
@@ -82,7 +95,7 @@ jobs:
8295
build/${{ matrix.platform_name }}:latest
8396
8497
- name: Build smp-server (postgresql) and tests
85-
shell: docker exec -t builder sh {0}
98+
shell: docker exec -t builder sh -eu {0}
8699
run: |
87100
cabal update
88101
cabal build --jobs=$(nproc) --enable-tests -fserver_postgres
@@ -106,7 +119,7 @@ jobs:
106119
docker cp builder:/out/smp-server ./smp-server-postgres-ubuntu-${{ matrix.platform_name }}
107120
108121
- name: Build everything else (standard)
109-
shell: docker exec -t builder sh {0}
122+
shell: docker exec -t builder sh -eu {0}
110123
run: |
111124
cabal build --jobs=$(nproc)
112125
mkdir -p /out
@@ -127,7 +140,7 @@ jobs:
127140
- name: Build changelog
128141
if: startsWith(github.ref, 'refs/tags/v')
129142
id: build_changelog
130-
uses: mikepenz/release-changelog-builder-action@v5
143+
uses: simplex-chat/release-changelog-builder-action@v5
131144
with:
132145
configuration: .github/changelog_conf.json
133146
failOnError: true
@@ -138,7 +151,7 @@ jobs:
138151

139152
- name: Create release
140153
if: startsWith(github.ref, 'refs/tags/v') && matrix.ghc != '8.10.7'
141-
uses: softprops/action-gh-release@v2
154+
uses: simplex-chat/action-gh-release@v2
142155
with:
143156
body: |
144157
See full changelog [here](https://github.com/simplex-chat/simplexmq/blob/master/CHANGELOG.md).

.github/workflows/docker-image.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ jobs:
2222
uses: actions/checkout@v4
2323

2424
- name: Log in to Docker Hub
25-
uses: docker/login-action@v3
25+
uses: simplex-chat/docker-login-action@v3
2626
with:
2727
username: ${{ secrets.DOCKERHUB_USERNAME }}
2828
password: ${{ secrets.DOCKERHUB_PASSWORD }}
2929

3030
- name: Extract metadata for Docker image
3131
id: meta
32-
uses: docker/metadata-action@v5
32+
uses: simplex-chat/docker-metadata-action@v5
3333
with:
3434
images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}
3535
flavor: |
@@ -40,7 +40,7 @@ jobs:
4040
type=semver,pattern=v{{major}}
4141
4242
- name: Build and push Docker image
43-
uses: docker/build-push-action@v6
43+
uses: simplex-chat/docker-build-push-action@v6
4444
with:
4545
push: true
4646
build-args: |
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"applinks": {
3+
"details": [
4+
{
5+
"appIDs": [
6+
"5NN7GUYB6T.chat.simplex.app"
7+
],
8+
"components": [
9+
{
10+
"/": "/contact/*"
11+
},
12+
{
13+
"/": "/contact"
14+
},
15+
{
16+
"/": "/invitation/*"
17+
},
18+
{
19+
"/": "/invitation"
20+
},
21+
{
22+
"/": "/a/*"
23+
},
24+
{
25+
"/": "/a"
26+
},
27+
{
28+
"/": "/c/*"
29+
},
30+
{
31+
"/": "/c"
32+
},
33+
{
34+
"/": "/g/*"
35+
},
36+
{
37+
"/": "/g"
38+
},
39+
{
40+
"/": "/i/*"
41+
},
42+
{
43+
"/": "/i"
44+
}
45+
]
46+
}
47+
]
48+
}
49+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[
2+
{
3+
"relation": [
4+
"delegate_permission/common.handle_all_urls"
5+
],
6+
"target": {
7+
"namespace": "android_app",
8+
"package_name": "chat.simplex.app",
9+
"sha256_cert_fingerprints": [
10+
"5E:3E:DC:C2:00:FB:A8:D5:F4:88:F3:CA:4C:32:5B:05:78:C5:6A:9C:03:A1:CC:B5:92:9C:D7:5C:7E:57:E2:4D",
11+
"3C:52:C4:FD:3C:AD:1C:07:C9:B0:0A:70:80:E3:58:FA:B9:FE:FC:B8:AF:5A:EC:14:77:65:F1:6D:0F:21:AD:85",
12+
"AE:C1:95:DC:FD:46:14:BD:3A:91:EC:26:D1:D5:14:C8:75:71:C5:CC:8D:CF:48:08:3F:92:83:14:3C:A2:B9:A6"
13+
]
14+
}
15+
}
16+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../link.html
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../link.html
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../link.html

apps/smp-server/static/link.html

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,7 @@
142142
class="hidden xl:block h-screen pt-[66px] bg-white dark:bg-gradient-radial-mobile dark:lg:bg-gradient-radial">
143143
<div class="container m-auto h-full flex items-center justify-between px-5">
144144
<div class="flex flex-col items-start justify-center w-full">
145-
<h1 class="text-[38px] leading-[43px] font-bold max-w-[500px] mb-[30px] primary-header-contact">You received a
146-
1-time link to connect on SimpleX Chat</h1>
145+
<h1 class="text-[38px] leading-[43px] font-bold max-w-[500px] mb-[30px] primary-header-contact">This is a one-time link of the SimpleX&nbsp;network user</h1>
147146
<h2
148147
class="text-[20px] leading-[28px] text-[#606C71] dark:text-white font-bold max-w-[475px] mb-[80px] secondary-header-contact">
149148
Scan the QR code with the SimpleX Chat app on your phone or tablet.</h2>
@@ -184,10 +183,8 @@ <h1 class="text-[38px] leading-[43px] font-bold max-w-[500px] mb-[30px] primary-
184183
class="block xl:hidden pt-[106px] py-[90px] bg-white dark:bg-gradient-radial-mobile dark:lg:bg-gradient-radial">
185184
<div class="container m-auto px-5">
186185
<div class="flex flex-col items-center">
187-
<h1 class="text-[28px] font-bold text-center max-w-[602px] mb-[40px] primary-header-contact">You received a
188-
1-time link to connect on SimpleX Chat</h1>
189-
<p class="text-[20px] leading-[28px] text-grey-black dark:text-white font-medium mb-[30px]">To make a
190-
connection:</p>
186+
<h1 class="text-[28px] font-bold text-center max-w-[602px] mb-[40px] primary-header-contact">This is a one-time link of the SimpleX&nbsp;network user</h1>
187+
<p class="text-[20px] leading-[28px] text-grey-black dark:text-white font-medium mb-[30px]">To make a connection:</p>
191188
<div
192189
class="flex flex-col justify-center items-center p-4 w-full max-w-[468px] min-h-[131px] rounded-[30px] border-[1px] border-[#A8B0B4] dark:border-white border-opacity-60 mb-6 relative">
193190
<p class="text-xl font-medium text-grey-black dark:text-white mb-4">Install SimpleX app</p>
@@ -506,13 +503,15 @@ <h2 class="text-xl font-bold">If you already installed SimpleX Chat for the term
506503
const url = window.location.href
507504
const messageElements = document.getElementsByClassName('primary-header-contact')
508505

509-
if (url.includes('/invitation')) {
510-
for (let element of messageElements) {
511-
element.textContent = 'You received a 1-time link to connect on SimpleX Chat'
512-
}
513-
} else {
514-
for (let element of messageElements) {
515-
element.textContent = 'You received an address to connect on SimpleX Chat'
506+
for (let element of messageElements) {
507+
if (url.includes('/g') || url.includes('&data=%7B%22groupLinkId%22%3A')) {
508+
element.innerHTML = 'This is a public group address on SimpleX&nbsp;network'
509+
} else if (url.includes('/a') || url.includes('/contact')) {
510+
element.innerHTML = 'This is a public address of the SimpleX&nbsp;network user'
511+
} else if (url.includes('/i') || url.includes('/invitation')) {
512+
element.innerHTML = 'This is a one-time link of the SimpleX&nbsp;network user'
513+
} else if (url.includes('/c')) {
514+
element.innerHTML = 'This is a public channel address on SimpleX&nbsp;network'
516515
}
517516
}
518517
</script>

apps/smp-server/static/media/contact.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
2020
parsedURI.pathname = "/" + action
2121
connURI = parsedURI.toString()
2222
console.log("connection URI: ", connURI)
23-
mobileConnURIanchor.href = "simplex:" + parsedURI.pathname + parsedURI.hash
23+
const hash = parsedURI.hash
24+
const hostname = parsedURI.hostname
25+
let appURI = "simplex:" + parsedURI.pathname
26+
appURI += action.length > 1 // not short link
27+
? hash
28+
: !hash.includes("?") // otherwise add server hostname
29+
? hash + "?h=" + hostname // no parameters
30+
: !hash.includes("?h=") && !hash.includes("&h=")
31+
? hash + "&h=" + hostname // no "h" parameter
32+
: hash.replace(/([?&])h=([^&]+)/, `$1h=${hostname},$2`) // add as the first hostname to "h" parameter
33+
mobileConnURIanchor.href = appURI
34+
console.log("app URI: ", appURI)
2435
connURIel.innerText = "/c " + connURI
2536
for (const connQRCode of connQRCodes) {
2637
try {

apps/smp-server/web/Static.hs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import Data.Maybe (fromMaybe)
1414
import Data.String (fromString)
1515
import Data.Text.Encoding (encodeUtf8)
1616
import Network.Socket (getPeerName)
17-
import Network.Wai (Application)
17+
import Network.Wai (Application, Request (..))
18+
import Network.Wai.Application.Static (StaticSettings (..))
1819
import qualified Network.Wai.Application.Static as S
1920
import qualified Network.Wai.Handler.Warp as W
2021
import qualified Network.Wai.Handler.Warp.Internal as WI
@@ -31,12 +32,13 @@ import System.Directory (createDirectoryIfMissing)
3132
import System.FilePath
3233
import UnliftIO.Concurrent (forkFinally)
3334
import UnliftIO.Exception (bracket, finally)
35+
import qualified WaiAppStatic.Types as WAT
3436

3537
serveStaticFiles :: EmbeddedWebParams -> IO ()
3638
serveStaticFiles EmbeddedWebParams {webStaticPath, webHttpPort, webHttpsParams} = do
3739
forM_ webHttpPort $ \port -> flip forkFinally (\e -> logError $ "HTTP server crashed: " <> tshow e) $ do
3840
logInfo $ "Serving static site on port " <> tshow port
39-
W.runSettings (mkSettings port) (S.staticApp $ S.defaultFileServerSettings webStaticPath)
41+
W.runSettings (mkSettings port) app
4042
forM_ webHttpsParams $ \WebHttpsParams {port, cert, key} -> flip forkFinally (\e -> logError $ "HTTPS server crashed: " <> tshow e) $ do
4143
logInfo $ "Serving static site on port " <> tshow port <> " (TLS)"
4244
WT.runTLS (WT.tlsSettings cert key) (mkSettings port) app
@@ -72,23 +74,44 @@ warpSettings :: W.Settings
7274
warpSettings = W.setGracefulShutdownTimeout (Just 1) W.defaultSettings
7375

7476
staticFiles :: FilePath -> Application
75-
staticFiles root = S.staticApp settings
77+
staticFiles root = S.staticApp settings . changeWellKnownPath
7678
where
77-
settings = (S.defaultFileServerSettings root)
78-
{ S.ssListing = Nothing
79-
}
79+
settings = defSettings {ssListing = Nothing, ssGetMimeType = getMimeType}
80+
defSettings = S.defaultFileServerSettings root
81+
getMimeType f
82+
| WAT.fromPiece (WAT.fileName f) == "apple-app-site-association" = pure "application/json"
83+
| otherwise = (ssGetMimeType defSettings) f
84+
changeWellKnownPath req = case pathInfo req of
85+
".well-known" : rest ->
86+
req
87+
{ pathInfo = "well-known" : rest,
88+
rawPathInfo = "/well-known/" <> B.drop pfxLen (rawPathInfo req)
89+
}
90+
_ -> req
91+
pfxLen = B.length "/.well-known/"
8092

8193
generateSite :: ServerInformation -> Maybe TransportHost -> FilePath -> IO ()
8294
generateSite si onionHost sitePath = do
8395
createDirectoryIfMissing True sitePath
8496
B.writeFile (sitePath </> "index.html") $ serverInformation si onionHost
85-
createDirectoryIfMissing True $ sitePath </> "media"
86-
forM_ E.mediaContent $ \(path, bs) -> B.writeFile (sitePath </> "media" </> path) bs
87-
createDirectoryIfMissing True $ sitePath </> "contact"
88-
B.writeFile (sitePath </> "contact" </> "index.html") E.linkHtml
89-
createDirectoryIfMissing True $ sitePath </> "invitation"
90-
B.writeFile (sitePath </> "invitation" </> "index.html") E.linkHtml
97+
copyDir "media" E.mediaContent
98+
-- `.well-known` path is re-written in changeWellKnownPath,
99+
-- staticApp does not allow hidden folders.
100+
copyDir "well-known" E.wellKnown
101+
createLinkPage "contact"
102+
createLinkPage "invitation"
103+
createLinkPage "a"
104+
createLinkPage "c"
105+
createLinkPage "g"
106+
createLinkPage "i"
91107
logInfo $ "Generated static site contents at " <> tshow sitePath
108+
where
109+
copyDir dir content = do
110+
createDirectoryIfMissing True $ sitePath </> dir
111+
forM_ content $ \(path, s) -> B.writeFile (sitePath </> dir </> path) s
112+
createLinkPage path = do
113+
createDirectoryIfMissing True $ sitePath </> path
114+
B.writeFile (sitePath </> path </> "index.html") E.linkHtml
92115

93116
serverInformation :: ServerInformation -> Maybe TransportHost -> ByteString
94117
serverInformation ServerInformation {config, information} onionHost = render E.indexHtml substs

0 commit comments

Comments
 (0)