upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main/captive_portal.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/captive_portal.c')
-rw-r--r--main/captive_portal.c91
1 files changed, 55 insertions, 36 deletions
diff --git a/main/captive_portal.c b/main/captive_portal.c
index 1f7340e..7d6c885 100644
--- a/main/captive_portal.c
+++ b/main/captive_portal.c
@@ -31,56 +31,49 @@ static const char PORTAL_HTML_TEMPLATE[] = \
31".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px}" 31".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px}"
32".price-amount{font-size:36px;font-weight:bold;color:#f7931a}" 32".price-amount{font-size:36px;font-weight:bold;color:#f7931a}"
33".price-unit{color:#888;font-size:14px}" 33".price-unit{color:#888;font-size:14px}"
34".mints{background:#252525;border-radius:12px;padding:12px;margin-bottom:16px;text-align:left}" 34"textarea{width:100%;height:80px;background:#252525;border:1px solid #333;border-radius:8px;"
35"color:#fff;padding:12px;font-family:monospace;font-size:12px;resize:none}"
36".btn{background:#f7931a;color:#000;border:none;border-radius:8px;padding:14px 28px;"
37"font-size:16px;font-weight:bold;cursor:pointer;width:100%;margin-top:8px}"
38".btn:hover{background:#e8850f}"
39".btn:disabled{background:#333;color:#666;cursor:not-allowed}"
40".mints{background:#252525;border-radius:12px;padding:12px;margin-top:16px;text-align:left}"
35".mints-title{color:#888;font-size:12px;margin-bottom:8px}" 41".mints-title{color:#888;font-size:12px;margin-bottom:8px}"
36".mint-url{font-family:monospace;font-size:11px;color:#f7931a;word-break:break-all;" 42".mint-url{font-family:monospace;font-size:11px;color:#f7931a;word-break:break-all;"
37"background:#1a1a1a;padding:8px;border-radius:6px;position:relative;cursor:pointer}" 43"background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}"
38".mint-url:active{opacity:0.7}" 44".mint-url:active{opacity:0.7}"
39".mint-hint{color:#666;font-size:10px;margin-top:4px}" 45".mint-hint{color:#666;font-size:10px;margin-top:4px}"
40"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" 46"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}"
41"#status.success{display:block;background:#1a472a;color:#4caf50}" 47"#status.success{display:block;background:#1a472a;color:#4caf50}"
42"#status.error{display:block;background:#471a1a;color:#f44336}" 48"#status.error{display:block;background:#471a1a;color:#f44336}"
43"#status.processing{display:block;background:#1a3a47;color:#2196f3}" 49"#status.processing{display:block;background:#1a3a47;color:#2196f3}"
44".btn{background:#f7931a;color:#000;border:none;border-radius:8px;padding:14px 28px;"
45"font-size:16px;font-weight:bold;cursor:pointer;width:100%;margin-top:8px}"
46".btn:hover{background:#e8850f}"
47".btn:disabled{background:#333;color:#666;cursor:not-allowed}"
48"textarea{width:100%;height:80px;background:#252525;border:1px solid #333;border-radius:8px;"
49"color:#fff;padding:12px;font-family:monospace;font-size:12px;margin-top:8px;resize:none}"
50"</style>" 50"</style>"
51"</head><body>" 51"</head><body>"
52"<div class='card'>" 52"<div class='card'>"
53"<h1>TollGate</h1>" 53"<h1>TollGate</h1>"
54"<p class='subtitle'>Pay for internet access with ecash</p>" 54"<p class='subtitle'>Pay for internet access with ecash</p>"
55"<div class='price'>" 55"<div class='price'>"
56"<div class='price-amount' id='price'>Loading...</div>" 56"<div class='price-amount'>__PRICE__</div>"
57"<div class='price-unit'>sats per minute</div>" 57"<div class='price-unit'>sats per minute</div>"
58"</div>" 58"</div>"
59"<textarea id='tokenInput' placeholder='Paste your Cashu token here (cashuA...)'></textarea>"
60"<button class='btn' id='payBtn' onclick='payToken()'>Pay & Connect</button>"
59"<div class='mints'>" 61"<div class='mints'>"
60"<div class='mints-title'>SUPPORTED MINTS</div>" 62"<div class='mints-title'>SUPPORTED MINTS</div>"
61"<div class='mint-url' id='mintUrl' onclick='copyMint()'>Loading...</div>" 63"<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>"
62"<div class='mint-hint'>Tap to copy &bull; Mint tokens at this URL before paying</div>" 64"<div class='mint-hint'>Tap to copy &bull; Mint tokens at this URL before paying</div>"
63"</div>" 65"</div>"
64"<textarea id='tokenInput' placeholder='Paste your Cashu token here (cashuA...)'></textarea>"
65"<button class='btn' id='payBtn' onclick='payToken()'>Pay & Connect</button>"
66"<div id='status'></div>" 66"<div id='status'></div>"
67"</div>" 67"</div>"
68"<script>" 68"<script>"
69"const priceEl=document.getElementById('price');" 69"const mintUrlEl=document.getElementById('mintUrl');"
70"const mintUrl=mintUrlEl.textContent;"
70"const statusEl=document.getElementById('status');" 71"const statusEl=document.getElementById('status');"
71"const payBtn=document.getElementById('payBtn');" 72"const payBtn=document.getElementById('payBtn');"
72"const tokenInput=document.getElementById('tokenInput');" 73"const tokenInput=document.getElementById('tokenInput');"
73"const mintUrlEl=document.getElementById('mintUrl');"
74"fetch('http://__AP_IP__:2121/').then(r=>r.json()).then(d=>{"
75"if(d.tags){"
76"const p=d.tags.find(t=>t[0]==='price_per_step');if(p){priceEl.textContent=p[2]||'21';"
77"if(p[4]){mintUrlEl.textContent=p[4];}}"
78"}"
79"}).catch(()=>{priceEl.textContent='21';mintUrlEl.textContent='Error loading mint URL';});"
80"function copyMint(){" 74"function copyMint(){"
81"const url=mintUrlEl.textContent;" 75"if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);"
82"if(navigator.clipboard){navigator.clipboard.writeText(url);" 76"mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}"
83"mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=url;},1000);}"
84"}" 77"}"
85"function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}" 78"function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}"
86"function payToken(){" 79"function payToken(){"
@@ -119,15 +112,32 @@ static esp_err_t portal_handler(httpd_req_t *req)
119 ESP_LOGI(TAG, "GET %s from client", req->uri); 112 ESP_LOGI(TAG, "GET %s from client", req->uri);
120 httpd_resp_set_type(req, "text/html"); 113 httpd_resp_set_type(req, "text/html");
121 114
122 char *html = NULL; 115 const tollgate_config_t *cfg = tollgate_config_get();
116 char price_str[16];
117 snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step);
118
123 const char *tpl = PORTAL_HTML_TEMPLATE; 119 const char *tpl = PORTAL_HTML_TEMPLATE;
124 size_t tpl_len = strlen(tpl); 120 size_t tpl_len = strlen(tpl);
125 int count = 0;
126 const char *p = tpl;
127 while ((p = strstr(p, "__AP_IP__")) != NULL) { count++; p += 9; }
128 121
129 size_t ip_len = strlen(s_ap_ip_str); 122 struct { const char *key; const char *val; } subs[] = {
130 html = malloc(tpl_len + count * (ip_len > 9 ? ip_len - 9 : 0) + 1); 123 { "__AP_IP__", s_ap_ip_str },
124 { "__PRICE__", price_str },
125 { "__MINT_URL__", cfg->mint_url },
126 };
127 int nsubs = sizeof(subs) / sizeof(subs[0]);
128
129 size_t extra = 0;
130 for (int i = 0; i < nsubs; i++) {
131 const char *p = tpl;
132 size_t klen = strlen(subs[i].key);
133 while ((p = strstr(p, subs[i].key)) != NULL) {
134 extra += strlen(subs[i].val) - klen;
135 p += klen;
136 }
137 }
138
139 size_t out_size = tpl_len + extra + 1;
140 char *html = malloc(out_size);
131 if (!html) { 141 if (!html) {
132 httpd_resp_send_500(req); 142 httpd_resp_send_500(req);
133 return ESP_OK; 143 return ESP_OK;
@@ -136,13 +146,22 @@ static esp_err_t portal_handler(httpd_req_t *req)
136 char *out = html; 146 char *out = html;
137 const char *src = tpl; 147 const char *src = tpl;
138 while (*src) { 148 while (*src) {
139 const char *found = strstr(src, "__AP_IP__"); 149 const char *earliest = NULL;
140 if (found) { 150 int ei = -1;
141 memcpy(out, src, found - src); 151 for (int i = 0; i < nsubs; i++) {
142 out += found - src; 152 const char *found = strstr(src, subs[i].key);
143 memcpy(out, s_ap_ip_str, ip_len); 153 if (found && (earliest == NULL || found < earliest)) {
144 out += ip_len; 154 earliest = found;
145 src = found + 9; 155 ei = i;
156 }
157 }
158 if (earliest) {
159 size_t vlen = strlen(subs[ei].val);
160 memcpy(out, src, earliest - src);
161 out += earliest - src;
162 memcpy(out, subs[ei].val, vlen);
163 out += vlen;
164 src = earliest + strlen(subs[ei].key);
146 } else { 165 } else {
147 strcpy(out, src); 166 strcpy(out, src);
148 out += strlen(src); 167 out += strlen(src);