From 228f4984d873df3add93b85ceb7d962088086e15 Mon Sep 17 00:00:00 2001 From: cpu Date: Fri, 28 Mar 2025 01:47:05 +0100 Subject: [PATCH] dns retry --- .dockerignore | 4 +- .env.example | 22 +++-- .gitignore | 2 +- README.md | 80 +++++++++++----- images/flic-button-request.png | Bin 0 -> 103102 bytes {diagrams => images}/interaction-flow.mmd | 0 {diagrams => images}/interaction-flow.png | Bin {diagrams => images}/subscription-flow.mmd | 0 {diagrams => images}/subscription-flow.png | Bin labels | 32 +++++++ labels.example | 29 ------ server.js | 104 ++++++++++++++++----- 12 files changed, 192 insertions(+), 81 deletions(-) create mode 100644 images/flic-button-request.png rename {diagrams => images}/interaction-flow.mmd (100%) rename {diagrams => images}/interaction-flow.png (100%) rename {diagrams => images}/subscription-flow.mmd (100%) rename {diagrams => images}/subscription-flow.png (100%) create mode 100644 labels delete mode 100644 labels.example diff --git a/.dockerignore b/.dockerignore index 8b30b24..f9beaeb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,4 +5,6 @@ Dockerfile .git .gitignore README.md -*.example \ No newline at end of file +*.example +.env.example +.labels.example diff --git a/.env.example b/.env.example index e9cb250..5ee6229 100644 --- a/.env.example +++ b/.env.example @@ -10,11 +10,6 @@ VAPID_PRIVATE_KEY= # Example: mailto:admin@yourdomain.com or https://yourdomain.com/contact VAPID_SUBJECT=mailto:admin@virtonline.eu -# Flic Button Configuration -FLIC_BUTTON1_SERIAL=your_button1_serial -FLIC_BUTTON2_SERIAL=your_button2_serial -FLIC_BUTTON3_SERIAL=your_button3_serial - # Subscription Storage SUBSCRIPTIONS_FILE=subscriptions.json @@ -30,4 +25,19 @@ LOG_LEVEL=INFO # If you want to add a simple security layer between Flic and this app. # If set, configure Flic's HTTP request to include an "Authorization: Bearer YOUR_SECRET_VALUE" header. # use e.g.: openssl rand -hex 32 -FLIC_SECRET= \ No newline at end of file +FLIC_SECRET= + +# --- DNS and Network Configuration --- +# These settings help with Docker DNS resolution issues (EAI_AGAIN errors) + +# Maximum number of retry attempts for failed DNS resolutions +MAX_NOTIFICATION_RETRIES=3 + +# Initial delay in milliseconds before first retry (will increase with backoff) +INITIAL_RETRY_DELAY_MS=1000 + +# DNS resolution timeout in milliseconds +DNS_TIMEOUT_MS=5000 + +# HTTP request timeout in milliseconds +HTTP_TIMEOUT_MS=10000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 115d993..d4f67a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ myenv .vscode +.cursor # Node.js node_modules/ @@ -8,7 +9,6 @@ yarn-debug.log* yarn-error.log* .env subscriptions.json -labels # OS generated files .DS_Store diff --git a/README.md b/README.md index 4bdb3cc..416a2bb 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for ## Features * Receives POST requests on `/flic-webhook`. -* Parses `button_id` and `click_type` from the Flic request body. -* Looks up the target PWA push subscription based on `button_id` in a JSON file. +* Uses HTTP headers `Button-Name` and `Timestamp` from the Flic request. +* Parses `click_type` from the Flic request body. +* Looks up the target PWA push subscription based on the `Button-Name` header in a JSON file. * Sends a Web Push notification containing the click details (action, button, timestamp) to the corresponding PWA subscription. * Integrates with Traefik v3 via Docker labels. * Configurable via environment variables (`.env` file). @@ -27,10 +28,10 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for ## System Architecture ### Subscription Flow -![Subscription Flow](/diagrams/subscription-flow.png) +Subscription Flow ### Interaction Flow -![Interaction Flow](/diagrams/interaction-flow.png) +Interaction Flow ## Project Structure @@ -64,6 +65,10 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for * `ALLOWED_ORIGINS`: Comma-separated list of domains allowed by CORS. Include your PWA's domain if it needs to interact directly (e.g., for setup). Example: `https://my-pwa.com`. * `ALLOWED_METHODS`: (Default: `POST,OPTIONS`) Standard methods needed. * `ALLOWED_HEADERS`: (Default: `Content-Type,Authorization`) Standard headers needed. + * `MAX_NOTIFICATION_RETRIES`: (Default: `3`) Number of retry attempts for failed push notifications. Must be a number. + * `INITIAL_RETRY_DELAY_MS`: (Default: `1000`) Initial delay in milliseconds before first retry. Must be a number. + * `DNS_TIMEOUT_MS`: (Default: `5000`) DNS resolution timeout in milliseconds. Must be a number. + * `HTTP_TIMEOUT_MS`: (Default: `10000`) HTTP request timeout in milliseconds. Must be a number. * `TRAEFIK_SERVICE_HOST`: Your public domain for this service (e.g., `webpush.virtonline.eu`). * `TRAEFIK_CERT_RESOLVER`: The name of your TLS certificate resolver configured in Traefik (e.g., `le`, `myresolver`). @@ -134,25 +139,27 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for In your Flic app or Flic Hub SDK interface: 1. Select your Flic button. -2. Add an "Internet Request" action (or similar HTTP request action) for Single Click, Double Click, and/or Hold events. -3. **URL:** `https:///flic-webhook` (e.g., `https://webpush.virtonline.eu/flic-webhook`) -4. **Method:** `POST` -5. **Body Type:** `JSON` (or `application/json`) -6. **Body:** Configure the JSON body to include the button's serial number and the click type. Flic usually provides variables for these. The backend expects `button_id` and `click_type`. Adapt the keys if needed, or modify `server.js` to expect different keys (e.g., `serialNumber`). +2. Add an "Internet Request" action. +3. Fill in the following details: + * Set `POST` method. + * Set URL: `https://webpush.virtonline.eu/flic-webhook` + * Add headers: + * Key: `Authorization` + * Value: `Bearer ` (Replace `` with the actual secret from your `.env` file). + * **Body:** Configure the JSON body to include the click type. ```json { - "button_id": "{serialNumber}", - "click_type": "{clickType}", - "timestamp": "{timestamp}" + "click_type": "SingleClick" } ``` - *(Verify the exact variable names like `{serialNumber}`, `{clickType}`, `{timestamp}` within your specific Flic interface.)* -7. **Headers:** - * Add `Content-Type: application/json`. - * **(Optional - if `FLIC_SECRET` is set):** Add an `Authorization` header: - * Key: `Authorization` - * Value: `Bearer ` (Replace `` with the actual secret from your `.env` file). + * Set Content-Type: `application/json`. + * It should look like this: + + + * Tap on `Save action`. +4. Repeat for Double Click and/or Hold events. + ## API Endpoint * **`POST /flic-webhook`** @@ -161,16 +168,14 @@ In your Flic app or Flic Hub SDK interface: * **Request Body (JSON):** ```json { - "button_id": "SERIAL_NUMBER_OF_FLIC_BUTTON", - "click_type": "SingleClick | DoubleClick | Hold", - "timestamp": "ISO_8601_TIMESTAMP_STRING (Optional)" + "click_type": "SingleClick | DoubleClick | Hold" } ``` * **Responses:** * `200 OK`: Webhook received, push notification sent successfully. - * `400 Bad Request`: Missing `button_id` or `click_type` in the request body. + * `400 Bad Request`: Missing `Button-Name` header or `click_type` in the request body. * `401 Unauthorized`: Missing or invalid Bearer token (if `FLIC_SECRET` is enabled). - * `404 Not Found`: No subscription found in `subscriptions.json` for the given `button_id`. + * `404 Not Found`: No subscription found in `subscriptions.json` for the given `Button-Name`. * `410 Gone`: The push subscription associated with the button was rejected by the push service (likely expired or revoked). * `500 Internal Server Error`: Failed to send the push notification for other reasons. @@ -184,6 +189,35 @@ In your Flic app or Flic Hub SDK interface: } ``` +## Testing the Webhook + +Once your service is up and running, you can test the webhook endpoint using curl or any API testing tool: + +**Note:** In the command below, replace `a74181969a613c545d66c1e436e75c1e4a6` with your actual FLIC_SECRET value from your .env file. + +```bash +curl -X POST https://webpush.virtonline.eu/flic-webhook \ + -H "Authorization: Bearer a74181969a613c545d66c1e436e75c1e4a6" \ + -H "Content-Type: application/json" \ + -H "Button-Name: Game" \ + -H "Timestamp: 2025-03-26T01:10:20Z" \ + -d '{ + "click_type": "SingleClick" +}' +``` + +The expected response should be: +```json +{"message":"Push notification sent successfully"} +``` + +If successful, the above response indicates that: +1. Your webhook endpoint is properly configured +2. The button ID was found in your subscriptions.json file +3. The web push notification was successfully sent to the registered PUSH API endpoint (e.g. https://jmt17.google.com/fcm/send/cf907M...) + +If you receive a different response, refer to the Troubleshooting section below. + ## Troubleshooting * **Check Backend Logs:** `docker logs flic-webhook-webpush`. Look for errors related to configuration, file access, JSON parsing, authentication, or sending push notifications. diff --git a/images/flic-button-request.png b/images/flic-button-request.png new file mode 100644 index 0000000000000000000000000000000000000000..b7ff5ce01932c6442e29f0f5db7977b78cf4c751 GIT binary patch literal 103102 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
J-MUq4emCoHbk&6=bxaNM)oi9R<}4N|@P{ zu^SY;i-ft!_CR?`W)MDsP}VNyz`rxTyuY`~g=Yc!zFT+=e=FEWi`WaF-dU#%tx`J! z_x9p#BlT+eo2qk?k}vg=&A=UF^eBlxWE=_+>W%*+foF1W!4k447AM$g27*ld z6t!;6v58%@y{ots|J1{GlMH(%6%eXqQ9qoriO&ViP-L~8js+aviCYKN*D=tQeDpJb ziyuv1>6$XuRR?i1AR7|eeyvj$WMo4L5v%Gut-xfh#aEoc3YN5X$24HND2_J!j}}E9 z&u{5JF8G)d0Kk``Y@`O$TYFmz-x1!Iq}LNe2D)3>Hre& z&dN=H15HFO0bCzv7ROW`Gw}BxJeJx51zAALmvZ5L<<>$)F@!Rw;)6(~5<^RiZ3>t@ z5Yv$LAo#Q3@;!vZ6a47(z@T%NST>6I2t|+*iMV216v;WO_=QWp8arI5^+Ic%N}W*Q&!f#2 zIyK&pZYyLkkjl=^9qBTJqrin8cMN{ZP;N03f<^)jgiN!Czzv_*i7! zfV`)gmTBO*YG>81U4rex;!ChIns%3kk~N?fXue6Yf;wlKwpIn%w=~C~AP1ILlNYc$ zp4Eta%?EP~|Il{x7ZU?CDLQJw*Wri9^IuGYy zY&p=$VF0KJokDE*WQOv&W600l)9Q+}4q~{uMDu+SI4W8I2?awhqbN#Xrjgeh13j&l z?1DtdGCox{o>M1kxA16wbkkO7QRb}u*7&HuiA7!!#C*uZ81v37Om8cgUn*o@ODg4D z)_?9Zko~J@6I@iT=V>V$(JOucZVWkieVJ91J+aXWM{*&76%$rty#n;Bh1yegm23L% z-ShWTW8^kQvppI>Wr)PSJjceE-o5UL{QNa3eB&m-CcCa@tkL&Rz6`e6P^>~ zN$s{f$T?KAU5e%ZkYEk2Hy~~pdf1BpWD38_bmCdqu|w^#gvcV+%3mt<3OGzwed&q& z<3FDQRcG3=`F;5hbhGI_cFnC!35x=#R=q95{Y?&MO})DqXE@C=Sca}ZQqqRJGvfLk z78e{+DV{+yF*7#Fvf~^i-Js7Lu~}c-QM8`VraQ9HxE+C1){hNCPbiO=zORu~8OfQ2 zpO3*?C!aOV{9sT{WM14iN;!eAe7Gc$xY0y*sjEh409v=(+SEyFbh?`R;rpS&)v!oz0FeWN>?HHF-}qh`b6 zSZD?ClAn1%v;txnz&@r1o)J-4c zfrs_7gK*_X!=@%}b&0!}I)Z&DdD@JU$zgwV-9g`3U;$|CRg4&)& zP=g*cgu)5DD|P9?ME!$mz2n{zQQtxJrz?%K15iJ|hO0AZZedSZPwqsy=j9T8o+~+p z-PTS?PNkKr=T1k#7=;o>b2_U1BEDhk`2wSENRQhNlP!=9w_l(M4K25 zcLL^iU6&|dKNAQ$J<_`#Q)slP{uAH`=~h@X+;=27M@2Wn5`BB-H+5lYLZDrp&5=dE zF?Wd{gglkbOq;J#SN8hP6|*@7yBTYXTR&U9>>$Pq?_a;8rWc`*+S#_2JuYcF@F?8F z+lIKL57zzU3YYx!uC78mlO2UZ-QxKG8H=4bm16g@`(Sp+I$z4KAElf;&*Rd%t$H-~ zrmXCk_woA=OiX~VfAW5KOFVvHq5itGIad3i*yZtQR64n+ermAR`Vc&Phx22FA-r$^ zX02K|_2bRh8QazLQI){rMraek)pMI|oirbf=K*IZphP&3w9_$6Ob#}sTLF5aw^n^q zwo{Uv%1&zv3Q!oHOJ;z2Trpa(lruMV`IuI%RL4LPK#P5B4f&6}G zE+LG?<|nh1LxHNr)1}yQMw|)(7{J-vvRjB`bB*)Ihm2(PuLIAuTtI3Mme#gkEa;RV zRuAzAyi3Z#VsHaQl)rvgRDETqLT;gMqV4?W$vIv1eW9;r+_(3FdX0~D_;Qkb+P|4H zB5;;hc}A3J49B`u<Ch+UGLKVTi_v-Z8_z?qz=w)S@7KO9gC4T1%huU`5 ziQOn^-W)dCmkY4V3#h_S4e)~=dW4NOstD@7nMZV+f-j`iNN$mV5Tgz36L7wo;KN#4 z4c7w;JEZ5nQ}x#bUqeO=Q^;O}lF0MLD%Qvaac=1fZ;Rn%SYJhTkj!e%K6kvqz=xQq z9R3d{!Pe%(KSjXi9kbENJ<6K#&Iq;Q5@;u7#_j~%Uq+|HS6yGc+uf%kI8uqRs8}37 zj%$dr99WxGNto&l!@c2qfTw=ixBpzHj;X0K2P{TkS3_?au`_@dJ!~qb4Jx{YP=$}^ z7xTQsl4ja0>Z&Iq#RewzkB28kv1?P|8^H~$(-VQA zr&rAz6Mr$CxbGoY4A8#701R%82Xq_sw(1dId+ZdhNU- z-#D_C1GC?lRZTJPG&P!yR~vT3+wLB7|6+Rb!S)Qxv-RB`Be@oH#5qs%CD|mAQUCq? zin%nLEUYPEqNHhCW!5>hT*Eu;WZhU|N2+*&lR+$5_AqT5FgFDPax;Hg{miZW&ch5U z$?(lDQ1#Xxm281D?anGdIX}PV;^dPVkx=+)=2hRLoTCD0>v+REqAPaoSQyC2(BFqp zk9gc{r@3KPw@-_+og0%^?n=?knz+))$Zd6d_wluV+UAgSEOIH&y_ASEWaZ5$q;~Wp z8=^~^RMTG_u$`UIRyHka*>x*X!29l~>i-6)i^)^X*PJ#71A2>$&FhOS+$K% zB^wI}M9AaEcL|0_Fe5Zz=6>38`dg2R35T|{Egiy6%zTfaVb|UOkZOB?~-7aoxY*$#8rvwAtxg`cy7H zEWqWv#vaDJWzVWQM@@M0&Vb3+70udj%}7?xX@0S0>pL5wvCrkmc_p$DrL>r&?aKgjZ&$HI5 zp1A6vfta7sv1#Ye$9#d?q{oK2La49h4~1i^e2^ZbCAfRNX}>v$Fgh8B7p_c2ccN%U z;(L@L`y(1ME#_@Z0BK1BDj^0P4aVZOjD?$Q?+>5UkCXJHxr?D|hh6!6%8Fjtv5SGB zj&@hP7^K^VONpgDVK?fh+;1A9S~v=b1C49Qcjih_GoaBKWKFJAWP{)16Cd4j+P#}6 z5ddqbEY2mKZm9B_s@WOm?+IVb(g}Eb_8^3%bvUr7Sx79ccjRnusaAz!vPag6O=b5u zA=HxhjiVSqd@uIY^o8>WnI5^NrllnY)TlXW@OaGH+Tt#`Dn9pj+=HaEuQTLifL41m z2qgk&8U&iY3*Nz4Tjz6Z))E&0b-hViqzhf(h`@$ept%NQELM_IWGIPMJx=p&!N( znzEZ1=cF9Ad+byu5U4ky1!h5`S;U5H6L50civTLfoKMvfST%Xi}Ti zlI&*B%0N5Xg`}KxHzk2(3 zw7k{bk5%XAC8f=Qz(f*GK2VhD&Z|G=`eK(p?Dcr~4&hnVMYJ<2|3KUGh2NZZgYZNx zDnDcG>zKti0|Yl7@KvhlFU^Qck7}E>^VBl#1D>*E*YfztrEPzXqDq$bpl%6mcV~WF z6D;b1l@UEI?_wKViWj)6%WLD-^ycDwCOJ&yk755*B8!SyOH7|onm;D!edpYdYnpt1 z4v=^n9418m+dEG^COm!pt`k~oUny`^Hb$P%RymJs!p|(SVbnG zppd4UkX6qGbILWz#|LxVaQ3kg-m9Lh;=3uG2BW4oXH|G(7T$>nz>;&9>DMG zK}8(6zT6RtQGnC}S75{y9 zefrO3RJu%W>tAey@aL*%Af%{vzA$>ILTu2edcE);H+m=cfA4M2acQn3iXSwr#ox5I zzr-O4N!4ZLiW}=>8`{Q-5=In1j!BR56L3YW|hWa(u+Lphrodv@Ai z|KNQ+`Z`zpgn8!MG5Q>IyW)KKuqMpCs{hNEKib5_hZ36kIfdN-QQXO*Vpo3dK0Ar9 zRKbVCOAbYej;GP5DtjfWcTj}3Xgs*cj|c+KMTFeaI42Qt{rO(U!~7rr{Gx#;j6}eIezl} zXflR(5G-BpX~y)Qw~^QU$KvC^=6GPGQS*`1^}Fp>l5jA5WCLMRJV!su(h$e8MD1#W0=~|)F=uL}9GjCiPDR}U6+GopaC3^og{)(} zFb8j_mP22kr9O!F+1R-kTkxiv3?up}S}&gL`aJlb&s2LAiaperS>T$N)FajS}4=#1@)ry!|MU7gq3IUjy^QZ zR(@rXf4{W!JOZv3^~4;F54E_tGX{;%I?q1yiYKrP(&bL@-Ei;>xT>yLg8Wzz1&e-M z_m1=Njq%>CjMFM+{YQwkfdr-WF|zXv>dW{E*KXy6#`e@Z8Q@Vlm6 z=HurxYi5`LR~_CKcLp}eiY{BvQR@cpL-m`wY$Zy28lzh%Q-%YJ+by`;^^jm#NEL|% z!epCWvH&dVxJ@U6;6|99_iCJ81&&ZH$iCPeA2$gMPL>WS-lFK)Q|G5g&99Zq4nNACDwp)h7^;j9*Prh~DuRo;Pop!_ z+)1*yO*gu8& zc`Sp`_~*;hP5<}c805|w(gz9q{@SinT+^+BB0$Z1QHWO-h#t$)15x+31e1hItV^Vl zNd3XN(+Y<;bb-}|Qza#VQAdtR9f_p{0kgq>#KbTo;_af|@ObGr9C9|LqFZG2=(mE2 zsCOfVqsKzUmA$5;9s}j_@U?tbJBKS}mS}PdNyrp3K?8|h=iXT~b}Ta=TSZe~dgm7t z!X->KiZxs+XT*IOfsIO@0MGH;QnlkWDm5$AjndFS1cg>TIZWUbW+=rNm*5)<%hB5& z2-V;6>&@!El;q=e*p-o@`w1xO4kUZ%Y$w-2$zx{+4GEe#RH6XiK4P`}mWjPj#hI(a zl_9Cg(a2-)&lFrm?cVN!xXv(d3@bx!eVZmCun(KxdxHzp^^=@SD) z$Xlr6Rw8f>BmFg|h{R;Q^Paxm%09kV5Mi)T!9MWMDW8#t^_p3)44fKyZ5#@4U_5CN z!M>`)$xk!2RFu{wQ>K|lat;0AT&dhHtom`!c$?CTRxvL9xNhe?(@%vgtt~SD8vk=i zC57mLq2NhD3EO%-Ii#N0x3rqU=BAf_oIOPn2=i)Da!Q~)XxlXF%|g71#|q;@Ohx?3 zo90x@#pw>*#iZBU^QosS=L={9+J}X3+b9fea?00XjhRIUPbb zx3@GnE2nDj3TAXBK6X;?lgJ4#o0y+Ppb}Kabkpv6HL|f&U{yb%e}cD;f3N6}e;b|= zmd;92fcU*7r>H^<`~6FuiC)ajqthLx?E~Lcy+pvVSYFjFSL+9g^S#T3BSe4!Z(90A9 z%4~$({J)&}wTKeB1-11YM2!myY8#>s`o)AwjZZrG@!r&~wmq#|W+}Bqh=U=>=^er} zED8K$U>kIz^x61G=n26RTWYy^Qx0np1Esv{eEc69OY?>B^Wfj`*Ha4xaOTQ#9r9)j zm=n+E$^t1D$I{ORA6+aK)k{2X{3LV`sh$YiGQIe9+G+J3p4`|)O?$dP`c~G9MXSew!aDF0{F;+D= zL;3O&(Q(VVp4;!GOq`&vUC;E@;7v^F0LgJ>SFu{enKpTdH7Gh-Htk?gi{dV@$Ft;7G=+n2= z`3y+X^h1h=kHtV2A{t*Kb2Sq^164IV_iCNDul4k&epCL81yC1^8Y_IcBLtNeQuEWG z<)yB8xa!9;k4uM6RdgtdIwn5+XiD!-1(K&YK>dCBw47xCwopOv{);Re1= zQGuMlDlvKl6d?3+7%g3XCz;HSJ7Y($fvzaeHSR03Fu*#;_M=U$M?eAxx6+_67W*N1 zMEcummk_uRGQfPZY1pSLtdrYm;8WE*ek(hZX7U+f0LIjdzm0cG{1k&o)@U8OZ24f> zb*B1@n>41~Dr50e`iSadL5;h8Hi93`Of>3CRbqbDXKSA7?W-FDFp+y|5?{CZlgF;E zKASJn$HbWBfAFnge}U1v;f`Q1+H%$Zl-GM+Txbej|5i?{h)o59CrW9QNsJ6Oc3KOn zN{+IG|MI$X++_|^!pN`$yj@S54%KbxK@U3rVxvAR+9eyey{j#MTrXJh$W2o!iO=Tz zAFayku`c^_R20((Oa3_*@yE9aQW44MEMODuuB7mc>Q{FhsVmnNYHCnD-)XXK=?7`WOK$v3dqH0sNb3q5Ph+^?+M>USm z=l$$(c`Z3XbAVU3z)|Zjk5=PEbDAIuKa0%1yRUp8>ZaGkTEpAo#%qG5*T?SpItyj} zbh~u&GpyDOu4mDl{@VTRuHC{Mq6Y%q6!eC<)-^ya%Fa#&+mcnR%BxuU$oxP0T0IYlGVgwTwae5C3~}&YVWijO$=CtC;5Zx&54~&OY5_ z?5yN?GL2A?r)S2!3#pkO3ZDg&d4#}rxM4bvbdXSDqFQoZ;qHsE-RVq`pCJp;m#xI& zk$Nf*0>Y$);4BwAvc2q=vxvyfgc)egV8c8G!qa0#xc@yt;@h zPIdPUW6)?yY8BQUv+ebweh|Tp78qVzh#u+k*UzKL;JDRZSfZEC-;#&COv{_s{w%SB zaiZ)YsdMv66m!0&5$pVd8oQeG(aS^Da-_`i{>2M11LFlp4zt^&1=>8`J96jpyAmhd z22rluINCQNiW*#gt}_39L4t6cMgOFymKG`z!Z1~gsr70!GdCWeyn^Yy;M z)9DvH#+Vk1q>Pp5RLso>ah_EmGDrxZUtZzSiwvoN+e#tKN3&JIx5ySH9sJdVyVxDl zj<~gp8}9^oBl(r!tFb{l2^CTnBl%EEH9>G1y$;Z_867Fb{ihc>?~OuVp6NI2h@3#+ z_66|;zOM|+5dQj66ykRd?Br{KITg89C4{YU=1-R)pu)AJ7d4}IRoF($pHdZ=ko!&Q z>dXa$$2s|Zo)TdrN$Df&Ref^6?(3&K+_tA@IOoAQ+vp0&ec#YDYK1!HW=^=MrGq7d zNJZK6cj>Zqwp;ZH;PQ}|jZ5^F@}HukXtA9*JJDc3ptdbk%|vih>fjH~C-G)Jib#PDK zt|PCSW_7|0q&(#P&a=ZLDj*|qT0NMwn6xLh@5x)NS2EvX_bz^-=vF_QJ*^hC-)Hpw znsOp>ZO*KtEjtewT>{JN*MI+zSy@C7yfM9~~+oB?3+)6nOOod&K=bRIwT~}lk-y=;Vzr`tRUh*Wm719eKm5gCgiTnO#5n^=NE|CnE}6j7Ox)0d0@=st zdxsZTeM9SSs^}d}T;8RN7A>vn+Iv1D6T*{C8PYoeuZOuL_XTuvBoWxE>D6);ipL}I z5d^KU44z118YYWuI^#hkY7pr}*cBGD|3RMt0u8?ik%zJH|ETKVtf!*eB4yAY_-dd+ ztGCT%8cc13V1a!(9|Q%c3skbGZoNQ%qyG@dI!>;4r{8{5@(saMD1YUB!ZrEy{3Bm2 z)JW{yL~PR9li{PA4>#$ZfMOtD^Z3zj*z@|;+kj#q`|@|(*=lNh{UYe+jMrC!P2_G+ zWXTy&tM6c}0r*iBDv8~^pj6SJE67x;+i)ME^mXRs&no*15Ntslemtsx>4#l-^@4pV zD`dxTP&Bg)-A0*He97~u*YjdAdU?Y?R@j$0utMR=SEHJfNfiMS1SNtI(m;uPWF0G2 zef}4tH&s?`-gXeCLFE_vwDkr3C08q&Bz4X+Ncx_8GS=z+cQ?b{Ww7CO zk=1KsQtQ3*@+S+ zFGDEbf}p-5IOW~o%7_52!sj~4Gc!cAxTL_cXm!BCB0lq6MwMGXQZTOtqaMm0ePiUKeSTMt+*mg^z-EaiM4DOj|c!I zP63n1r0dL)%XyXk8bxPk`H!Tgql@?k281#b#uqU_iNm%N?cmM%V!$!hNwA!yf?tg& z??pg3%=w6Iv%#QkX$paBUS2#7yzPgt#Q)%b)T9v2N}|ha84d;u?HEZiser1E?BOkn zyB#M=XJG3sQ;?`{6;wjM?pQCUupx2I=r`Hp_~L+ZCqO|k@Zm4EJ1c4de>x--#B*EN z?1LeN9xdDm#EcfqJ_CK;Wsei`Il=Lub8=C9P5Ig5Zwh^@{p{A(&_4@|F#~2gLgSz2 zpfZS3$$@`HQ2(#5 zPV|fw*GYxnDYFxIN~th!%Za%)nv{vxVjxD##u*7mpOtKqFOPXuyy=GtZS1*Og7uON zu{oBC!ZX{cTW34@8~}Z3!Tmo?LeXJG)&wR9hY|Cs22qB`p~QV~%EtEZ0aF5kmXQvH zeonV)Crv)^;yhZMbrUd)bu{vPM#xWHZ^3_-na~>ZcDenl$2RHr;%04;NI}T>_PyZ+ zUjQnSwP6)g*X{{pOhD9MTV#BFQ4;qno}q4^q|;Rhy|95J7|9>7nE9pjXJ-GX4vF?X3H! zs`o6Im<{7IHp0q^fSt#OpPGos6|z_pSHy!P-XnR z-HHxX`fwU&8^&_z!=By+Nh>J?M5h_l%#ewc2`)5WObRBaIN6?-%Hh&pPK0*Dx~)>+ z$d83L3-4|0=DwP0e5*pK0-lB?7mGFD*Lv+CH>0RDhF+jBJzRS_q?d+q)?Zr{MI_TKStki z_c!R2&D4Zk3-%vMC75RP>E>hpI7$n#SKdTEXa7A$jo zGsvg|dSFy^acL&@;^d?OQW!>5_LugV^|`SxP4(2W5p!%hZJ)V*Q#$eXm_-ra9@t`SeHrcSNdC7!kKD%>G>y zvmWbcWTNz}WDFWoGx?}#qqHe+&kGAm)Sh4NOz z^W?D|!_x7ZH)XpKPbIeIKUT39JPNPXIt(M444Nr#Ly!o+1a4%oV=}=gq_k+NFyh9` zU8PFbJ^bX2p2@XDWb13L3E`%L%$xT5KP55`TSnHCm;=@0J6$8+=L+o(dQJWH$+2~u zabDh|>la*2o%^JJbL_3_(;z`ZjcvZB7B*kVPVH9Nc0#slMgGAf-8zm$nN5@-I#Py@$5BJ{ zlOiS31Aje5(67D}=0p*ofi@VjU2r)DH@9hgkk?Xlp;PQuDMZ$2@Q-?g9r!I@fW0kA zO+Q}CY-))n6Ly1XoOVfi^yLQK(V=Q}erD12(@xuhwMV&^?xe5>5Z97PBr;igEx9o- zE%?HtKR2zeIVy;L+$SG)NSfXuR30)ox|moXu?_;lEm)7R8RC!GUVb?7_NO+W!Wn&X zuY_yRbr}~PRRUJeo73e3E-8%3?LO=5QuJaKlUZ#dU7?PW%z$iGbm@q7=5~&#G|axT zm6~K9-q?Fx^U^>aJ&}XdKeP(#a`$$k0mXb(pxozDD?m05f##h?9kV_jMj_Q>Q8%C3 z9l>n4N1-DDd$MR-(O>}kaUHvKhPCiGYFk^_V-zPLXu?E$9$V$noVUxiGIl*;yf58o z>1pKM`5*yC^L4n>WCS%i08QQwRkt?eD~3egFqspFDS;LB`6T^BL+@6CV=1krBhbQk z&!xQ5&V%A9I_?rz^{@@)>%KTQqM0*TxXpJZ76sSioy9d-9b!WI{5w}&v zQi?hdF{~BRzdii{&c3gYWt2S5uIY;%J5fn$ggEdDL#?C`I>=s{ktTzh+yQg@%gU}v~xU1-=*&mSg3>cSo&bGELO@`!EarFyh8SNnFSn&JFPXMI~_7!{34; zd<9jiE{h7;uaU(Cx1^hZ^nVcZIwpLqd3gC>kmYlN&XCajM$tdh3e!IG|pEp&O*^~+{# z--n@3-M}lTMD?!gh+Un_afg@Q8Wk{f6T&#KDrB{cc7EgDIMX-&oEx3=<$xlkm^JnW z`Rg~&;Q0&~YM+XV#UGr%lKn%hU?BhbRMWtB(}_aF9ToPsJ33^EUI-!xi%Lshs|8#7 z`3<@jBQKZsYeE+E6}o=mls5Dw516@C<*W_XKk>S6;JHS;wo~WXY=1iOhB2>V!raV9cz>3){ag~XPKFGDZUqIdE+u_oB7JvXd#opS{@s) z@v)$Kbw|0>+?23IlP;(WQ&KbIbVFYe){RrHOe|}Z+>-Z+CJW4AD#ukI$g~18ukJjT znj1@#&<{V=7^Y&n4AC78Bu&Hdo~qmWNKCV9K5g82=s~}tjisN!x>=9p4hCFw6U5c> zdbi5C*&O^z>ghGNx{0mkOuIESC-0!B5dxVBtm3CO54`xIK>3kh2DBJ<{)>0kbs(WG zxd33vF>)JPiV0dbdsE?wS)+)5aK|=nK)V1Yo!g^ga?_9jeN2tt z3u7-n8s$c$7ir}ZNrqV=z@j|7U;_31%6UVsDlgypeeoCd$iLXWowvW#fh9I;N*(^{ zx=|EKG3Am-y)J6qTQe9yxHt61oCvm#+hz&&9_3&4%O!jGb!%_x@(y%}Y^~5HkPu~~ z8bjVglpGA^^to5cNs=zz9+wW_l1LjP%6GLxG3j%rGFsJVB)OgO`ZOiz zeGjGk-t;D#Oy`b+#vEa;uBn0&0u4;hIfjPb`GRcuhjZ`y7ZM70s09`01WoxGSf2Ae zf3f+gG}7CRVc$o0Gf6+Pq!=4Tev)zXOrIG;ddAKgFu%AVm+wNqRU;L%KQc`e34DW1 zv^~htJ+$K;*JFZW5m4}bJ6YM#Dk2-Ux+4@}_?tyA^s|rM*iBo_37FVDcC22SLKCRDXr+FAR1B_iePZ6B}QBoymB&qjdCCX z_#pu?RF&)9jpD!99yPqrs{tRg|HU@?;pmdo$iFYEQ`X!u=CC-g%Z4iwGMC*lYBABN z33vCo=MCiwEG+Wm1*7 z9bmCN1Vc8WySsGt)Tzs(lb90g^&O^xrHGX3?|xeK#|Sq#B>5A~$@zI~Uyf>aW96kQ z3y)K?_1E%TM(g_ZW?+W^H+jwoa!;wgt)qh~&&7MDb0EKNVlm!I*mjQOme95sP-ad# zAkIrNe)r^W+<2DKL2<8=5DK`qG{fNORqDDtr9#mpduvMuoNFF5+Hh`&IAix0+jLcJ zt>t_(9^?u8cILMnKaXegfP$pI-{7iPWT)BDlsP)~&8ysAnA*Ch34+-rH{L$4gK-{c zlKPajhNd`bM=yVHORzOSsL6OqAkan0re7)>v$kyfHyE{Un*4sQtG9&63}saGrQ@pF z8g|n&tEdpMJK64Tg~mEwamn&?*eq50&`xKo1u6FIqnOdI=J4E8AyK_pO9$KFrY-WG zs9W3FQ@NpNOD^9)iSuMe)aE-AG?ei$64-m(0e%BWD7iL(PxX_0pcs3fh&{I-e2W>wnwDb%~Rd$pEgqAt-(&?=f?toh@I+1J2dfI_B z`~UR8EtOz_;*|={!D^%on+}FJd zRAw&=<=50(2&zfrM4Zag`w^ye*m9lftUfCh=TGqULPGB&;d*+eLS)ex9`)BGx*B0a zi&1OPIw94_mhVf!RI`>%wfB6@xc#`^5M=2jmk&O}?BG^>U0aG`bl{KXa^P}=LPfrH z@X`b|<{6cQ$LHvn!MLPDeuw%7NsL7&1}4YXT0gmj)T`EbCopx`A-}1c?QhcGnV->a z{)4&gO*-J#XLV#7w4C`6)0I`A?l;X9UFz75mrqi8ku!%SlSPBX_<(;CNj#YCR!5Rr zGf~IayzYkOhB-#Djsg#&I`udWpmsbhbHzYpvL{2S!1w zDbqilhP;;i*ap;$!&|4fXZQ7RNh0E66i7n6WA>Vwv(=QbvwMs3cz8q98=z6+pNPARSzgl7ll* z@{giiZ;9uS$w}$_0=HX=LIy2s67uVezMSY>Y9R)z3r>?UZH~`?vo8Q=7XoK%nVF%N z@+WS~_d*emd&7Uvs_HjJAS8cH`1}iIzs_%z#+N#c1D&s`tT(;!%)v-kg{Zsl{Ka-D{6uD&Oq92@GAb-y zyqc>-yTn?G(s~NP4M~e_rXk~l$>g~d5D!4mK1pj)(WZPJftEaa`}hdvz#|C5R4SVy zE!&w)Ljn&#jD^6)H)LCYVS9NlZUEO~hFt+xd%!)T8(I|Q6`0B4%Eu3&U3G?%rX8!u z-|B}O=W%(ojnH?dXh&3;*|ykKufwbp20w~t;_Hk=zlwYlAv}{Iyp6rA>0LccUaPpO zyl$nEWtI>Xiw7^TsK**Yp=?>+74NP*lmhj{-A<<)6jEoJ99fg>;5u&Ejge^Ljx!N7 zM<3oWu?+c5D)Eovk>YL;nND^X(J`%1rD`E`!1FVhWZjdwU%EMJKL^y#8andZHSxS6 zwue^rf36WN3JQS8&T3(t>&&|PlZ2Z5L)F>^ZaUKjQIy_JE26%0`J%`(C6yMBRS+_^Z^e6}YM2tgS|$Tm=Pd1SuoJvuUaf#%!1Qr%yy!!^HMQZhDnY zz$#j8!@R!q176r$RG&um24Njr(j2qcrhLm@mTGFF#xFCN!@K%U2J&dqM;`?6_-(x^iHeiP3U zSTP+pe$7r7%h1m<*Fz>>1pyLa*)9O!0br6ep5cJQo)2da=ipFExV*&1b}G5ghVv!g z)W{FXBHb)P-hqf&k2By&T@;%T%ASpW>09X6$XY50*8N=NKhO*LJ+3|pK!Oa7x6K2! z_~Y#{wEYBwxf5Smq7AmY{U5i!@t2{bEL?Xg^?%@egJZr`_%O95VjDz&oCAV0T23ZL z)tyVh!oyc(V0X(jTTS9-Jq~bVGut9vf6p#G9$VrWo0Bn!D-2M(@2n?qXx$9*a4;v6 zR~jI*lTBnm0_mEtGM|(BLO3VvOx)wT8S<=xcPTlC%i-}t#$Rk$vgbj2^x^ro6Q5lI zPU}70dhS9I)!XosrISm|5N4JFz_O%>4+Ihor4V=0+t@CO%GVAI-DY~UJR3&(6-3iC z^$hs4LgNt(MF=+*P;>`zaE(9FKr{x7!R?v?)bt{HCq(M(_y)luCjAEd594J5Th<1p z_itVWq_f3!LnS&Bxtu}xA6J*y9u1f_`AUY!q3y%bP9__*@f+9;b-gIzmrijgPZOk!2b8g(9U5rV z&$Ij5Z^MB?=aSvS`z^lGRm#O%A?SLojcL0DVtZ_zBB0zxAc43)!RN_o1NZVV%VF^V zy2i7@|3(M~vv`E1!@UD;{VYX{)s)SdjIj9xLz@yGN#3*#&Z%N}C#CS^pW-%E8;d5A zL37a=ZZ)rg*cPfF3B1q@rL7Mo$IY$}h?>f({?54f6sOW;0P5>5001Ey1~!*JxaXHRx?8 zyXqCmZ4;9c;GxwOB-h1(MZ52dOV+)<#`s|2Vd2byKqF*5gz`ty2SMOW0(Ki>kafaz z$@$lbM>P}PIkLDCyYnqDM-A)c>G4ElZ4EKSn*@=YAp5GIT9`_gFE#^^Mutx);bNrK zpoBFa7`&4&W09}X22RhnlNW$IVFcffrAYVPLVTZ`GyTv$bXy{4B1 zeua*a4~W{RaEDoksk%Bkn0ZVl>6TD2=|`hXWV~d30h`J3nnmWOX>=_kEZS{)^v`{3 z?u%jQ3#{)z);&HeRXq@4I#DAJ?NZZqzsDs41H4f+tNYfm6(e9TUfE61fWbuxzpSjp zVCn{t?s2#LSq$^! zDjh`HPT*%GnpP23bAYgKBCoR_OO{BZB(06r&6M|t4#jO}wSzIQghFbjtyPDG9%knx z*i(W_1jt3&U`JwRP=HXPKPF6=Eju;mf*~u@n)FH^r^Vn9N*4Z$ZCEs`Z$mVL%=?bC zrpM_JVpXb)pBq4QKn|0y^6>soMwJMq)bq=rq*+_P&%E6#Nh;1=7s=FwLL4s@p_{M* zU}MV0=cFE<!?fu3lubscntn`LN_vUG5*in4z-JaG z&$MGT9huE^p=HT?Pe*Ak#;CRegLgM6!$lm$6IgHG>bj z*61Pj+}$gD%b>r=(&Q|L=u3I&>z!V3-zWd&E&E^iLZQ42u#0d8NgH~>1F63055tJ@ zk&CWg<@VUu95{IK6^RII=8d5~3mU)cmXvRD22^9o8%3KNm@zD!Ib}TbbKmBU(d!cV zi#Dt=&s*(CGT1Hi8nh?k>DsEv%I$(mGQS`-W85_-E-B^=fUkAMgTnXb5$F}C|6lz0 z|Aqegmz;7ZFig>4sVJ`=X>5SD2>$YUF3k$zrCIO{NtC_h4+7d_IJPheNOJ?)~cr^xs$bKfMP|S%=XThK-nRev1iM+`HOgf#%OE2kmiUn{IqD(bHPU6Ov{; zrYsd z8|cChgLXnW!7C*^4U>>BmTq7>J&GsfLOLUZ!s8PP$k6Yp-1cFSN5}@GQE6EZgsD=t z4MeE90>M>bb{?<{SfJ!FQE7Sk0OqoB`F;<>p)lW$0ml-+JmH38KsNBzBRV~X9^9^y zFIf4qT2F2wWg#v6^gTH@&sgKY9FlGVLl(5U>X@`=B=dWjQYx-ew`jn3)lzjF1j?eK z#>Q8h2mG#-Nck@ds_hsK_1Y8#FHojVDG4-lt9$HG$$1ofMmoYoX$Dnwb0~*gI{q*_ zKhF;;^2c23T^yx(W%X3fSx78~3^>hQOWA~WA>m}2@;~gkyz&3fre1o&G9SN|u?^Eo z5W+h^2U#6bD`_^G=Pxn`5dADTQ+K^w{gYnCZjb!z7MSmU*w(Y|Iu8cxGt8Tg^aPmN zHAI@HiA0!OzIdNlkMJsyS{qAD`OQVuI`{`>Qt^PVL(GGEPNmt~M*$N>KVpSCJNPJx zO&}vY?gsL!qD4NP8(SF}$bfk24j7iA`h&{uNN?*w=>GTiOVOyI0tB>6@vVh<2{Wm3 z{RTBjOhSN612W-HtIw*MsxjY^!7h#o#f<~LQG)=KuOT7=cjd)C+@e#@_#zk8Eu%>b zLta^+N(!N*?IrIDH834&rtW* z`Z+;wOA&RQv}NP}obu@}wjLg@O`g3p--a_;A-lv!OnZ)mt}4-&~CD z=^{6w_D_(?zMvc{8xKP=x<#OjsIk3(ZBAo5YCfLFZ0ssJu_859so_V$LI!@_$!9jx z9=y!$qfMSGl+DkqVVOgvtSa%p4yvc}KqBH_T|gm!F1j9nJ$6dJli9?@4e?`r7oQz; zFz#9{h{ijm?``onDyPZ(-1a}-j<9U~_V3NUt^k;hSQA<-X9wbt3(YKOmmFfw_T1u? zF9?RU0s7uRXz_+Qczd(!uKv^dGg6gSDftO|#g*56Z032?TOOQB`KYl|^TYK=_uJk_ z7@Sd}`FS2~n)>_mDu52u=NqtusS2PdU`fD9%H&^B93D znM?D8{>8T0{mX&Am_8xkoM}x4w4*u0?Q2md^zq?W(($K=POriNhK}Yh=Z>^VGLb>y zKEwv7Ae{n#3_Zr$JzAJe9C%%Ww6TIKHY3DPGYUTBQL9+-5On=@nRCF z^!Nu0jZ=v@F!M){g9DtdXT!RaPox2~fk3m%_Xc`rthqgqXEN)o!C;>bM{bl?c(^dC ztC%f9Adf{!%Zo~7B45X`zXfynbLvQ=g5z`?2Y$)&<7|eVTF6gQ`e5g@*f!^8mA;3V zQI~wBcNG!>UrEXInaH0}T9^CDZ5Q8UbJo^a4RKo*6J#kVV>og!7Uz~&ACuN zMF0VNu|kEn2Ipf`RQ%kk7Mo($D&R<|HVTn=bTO2BI-<$`=uCUnnd!qZQ&`kW;cG@v z4n_e?Ksu#(gR9gLW^)L`FPEO2y0qw5HA2#pChWd}7jcqB-CgRD;1h2zsCx8G`bIlc zB2CP-)4Xz1O2w=ZhK+1|Np{v;gxX80%o1kGe6fBW%Mh^C%6RDUgzchKEZ1<+Abx(#--kx~`u2`W_a>DnZHmF3=?`pq!@Z*ZVmOPvGADgjWQM7)JsYxwN zsN07UQe*jz@A7+o#FhC2=y;qY6VY}$4@mrZ0 zYfHtgj&4;i^{1bt&(8r=*ITxrgtZt&4IRiUzz+}by3LS78blXB2q-@MzFS7FS|uFO zvIy{7JO5!Y4%s&tC-+lEt!j z{woJGxB1&`zXkq^+|yI7LU%5Xb9d+c;1m4b&{)@ztf7UOuX|9TuTKhGQU!mQUcu>) z{-L8woncMV)LL`jTu2SD|HkX5(r^Vawz03wsZ5^BGbaPmaL#>lnnuzZ&$z(D4qRp2 zEF$3JzCz`hR`YdN7ZDvu7h~89lZjB~{P^x>eFh;Sx# ze8vq`e@_L+{qy>?F~fJV)b%Ut?E410P(R$`3R06Nk$F$cdJZ_4q{C`-+^F=%YyWb<^k>s9uhg17==cYE71z z)HZM32y~xjTo9*h!6JL_A$>-G2iJz#rxQ58r+$*m4=Gc-B$?FuZwik4$0gDZI(1 zKC3aRelct7OU=(}tUQc-mK1juBW0Q>!onVuB5VwMIi*&tS#6Gaz3Ia8-`BIs@4V_r zD<+9`d#lG1Zo^2#z~9YAO%X7G^?4nM3c!+v_TCY>bn}GSmjbSDw7dl^M#UATekmm6 zsGq>o{?h)1NB;AzP_rtD3fRXt9O^qPgtRBqaI9X-NLszkS4=!R&?&DsLo6nRyN^Du zpbNMFLJ>vtqvhl;TQF! z=xD@qDpHkuM!1_VYK0r9g za#PWzayfwR{|8f4B-cTasA|2mQlY*UZ&w28Z=7RttNSwe(T3-z_T4X!Ey@lp9yTy! z!wW40HKaYz3u*+`cHO0_b`BCz3N;!yvxj>w~$D=vl+mFv{_+UL9XbbVCEIWNw{~}e5@39BmVSG#>}Fk} z?Bz5Q|62l7D&4>)>q@B|sFG9rPCq=aOwAtsY~@ z!5-#T5BnY|ETy?r0WYS9IvVDA!0n1**}~lv;5J!g*Q%qn#+)1f*$P+oNagW7IzqaI zd(*;P5q3j7d_!wt--8BEWdVyADz)gO z^KEO3FlCQEuMpxX#@d4T>0cgKi_IxPPnj*dwhfzYLZrEi9+Z=imgDlinM*(q{=-&= z?Lk6L^lPN~9KZsmFpUi?2o0R|Z{Uy% z3G~_pySDk=D}4(1bV=V1Nn)LuMLv<_>XX_i*-kA2FNus^7LGJwo50kHP>zs44FcH- z=pHLm)l1b|@tF1`^{yGkn(!lvk4!>G^?+Qzcx3iFRpXq%i7u zQck;L$`r`fvK{?~bpd~HA^fA5@ic|2zDJILy2$hkiwql2c`XN6ONo|cVm8<;x zMh}CkToahvt@|T;X<4uFql794igHSN_3BN-eDTr9lP&}Nt?Q&Q<5}J~qUxFwM3+)K zXAn_Mj~LwGVp~#8SDx<6?jC{|u=Y58fPTz60>B#&6eJ9KN4Ox+4#K&AYd!F)8q|Mo zeIG5*%cKb5%20Ba9ZgoWxUOWJV%V}-Q^=GBUC`#m%qNnGernFv@*ds?v!1_b6v%C< zmqm6C&&gzA`0aN+Tg>Vuhpi${IGSC&-l}i7Wtb$-(#Uv>*+_vrPC!rCg1xxx00b}r zfY6E>K1k?4Q}N^?n_0QhMmb_~~2hr|9+bF4RFg^KEzOm%$Rj?#RrP^@D(zoIT9f$JcZW5wQN~d5~Qc z&tFW!jd>80M>i_r0?>4oa&^fKu(ex1oYr?oS#U=^j8eXLAWk;_P3MtPuMt$0J5P_s z!ovMNDeROFdsj|4){aL?*_L9?`9d7djA0Ba31Y|l_XaZXtU^~kcda)7&Gq$5OF#B7o-ct;_U)x>u2d6mLpkA|GHh$W@IFn9mI?G1M zBU^g;3Pz|6C;JR5nGF(ss6hxUq$&`4iMz1iVu{)@zLut^JnocX{UU;=$Wn$t#j=`E zw%h=-nu^YVshVLvy~vW6X(I;j?=r{o!_tFauRi=>N=OcGZkY7-WKe{4K$5#m-lS`1 zd}TG!WiB#Dpde~_T^I}GKPfkt4-8)3BzF$0KjJ@>uPQmX=uWA*^eYPMXM>}zD~Shj z)i8{WKE3l>tSK6Gz|QRxpF=6LEo8L{U6v@ZO0IijFURhP%?8dn3Y^K;b#tTeS^HFL zhd?ST7QY&)b4_Z>X56oJMsm?Mxo zS0>GMZy$8pl2(oJEa_z`dwE@>6B{>YS%XuHV4&S#*X(6Tw|7mypuEAehdWM zusk{~|7txRTfl%D^gMLC_xbu_tS!%)23XZXLl4l4<5(jy5!@Luf4)7xxg5UnU@?en z6#ERBpX#ncu5evvbc@S(0*ns+NaPnN2m!oVIz1N|%AuM`L&Bv%# z2xfUBz_4(4*CULtP($rd^M@|B*o%-u4#@`NnR8S4yf{kZdNBaje|EN5CUF1u~E}*Pb!IB=(dK zgYyHejr>vERQ`k@PMI2f<_i$dNzfh|f_jXbaVjo+!@&N>(fb64ysB;FXh}mCO-$rX z*DC4`*hqGK0jp_|I=fE|5!0UFK#AClZ(dz@V-n8@X=A)<5Cjv_G}M)6pCOPf1JJ)^ z{{d^et7#~ThPmLe|vPS<_`{OF8H zZ+7nYE#K|(-5I`LlK&7H{t>Hg0eAF)J)0Q5FKB@&>wT+NPoy{=h=CSxvySdC2O|zcGFMF zsiQ=e_zVMGhpmcQ;g$QF*XXhtlS|;g^CC_PJbA<9IozvMoTq@8my+{;L2ne7BT=k! wR42TR0MZMfW81#BJdcSCcF|7bAPBf@g*?SaNB-ME#cvTs{y)w<`g{N10cK^rDF6Tf literal 0 HcmV?d00001 diff --git a/diagrams/interaction-flow.mmd b/images/interaction-flow.mmd similarity index 100% rename from diagrams/interaction-flow.mmd rename to images/interaction-flow.mmd diff --git a/diagrams/interaction-flow.png b/images/interaction-flow.png similarity index 100% rename from diagrams/interaction-flow.png rename to images/interaction-flow.png diff --git a/diagrams/subscription-flow.mmd b/images/subscription-flow.mmd similarity index 100% rename from diagrams/subscription-flow.mmd rename to images/subscription-flow.mmd diff --git a/diagrams/subscription-flow.png b/images/subscription-flow.png similarity index 100% rename from diagrams/subscription-flow.png rename to images/subscription-flow.png diff --git a/labels b/labels new file mode 100644 index 0000000..5451eca --- /dev/null +++ b/labels @@ -0,0 +1,32 @@ +# Enable Traefik for this container +traefik.enable=true + +# Docker Network +traefik.docker.network=traefik + +# Route requests based on Host +traefik.http.routers.flic-webhook-webpush.rule=Host(`webpush.virtonline.eu`) +# Specify the entrypoint ('websecure' for HTTPS) +traefik.http.routers.flic-webhook-webpush.entrypoints=web-secure +traefik.http.routers.flic-webhook-webpush.tls=true # Enable TLS +traefik.http.routers.flic-webhook-webpush.tls.certResolver=default +# Link the router to the service defined below +traefik.http.routers.flic-webhook-webpush.service=flic-webhook-webpush + +# Point the service to the container's port +traefik.http.services.flic-webhook-webpush.loadbalancer.server.port=3000 + +# Middleware CORS +traefik.http.middlewares.cors-headers.headers.accesscontrolallowmethods=POST,GET,OPTIONS # Allow POST, GET, and OPTIONS requests +traefik.http.middlewares.cors-headers.headers.accesscontrolalloworiginlist=https://game-timer.virtonline.eu # Allow requests from game-timer.virtonline.eu +traefik.http.middlewares.cors-headers.headers.accesscontrolallowheaders=Content-Type,Authorization # Allow Content-Type and Authorization headers +traefik.http.middlewares.cors-headers.headers.accesscontrolmaxage=600 # Cache preflight responses for 10 minutes +traefik.http.middlewares.cors-headers.headers.addvaryheader=true # Add Vary header to responses +# Apply the middleware to the router +traefik.http.routers.flic-webhook-webpush.middlewares=cors-headers + +# Middleware Rate Limiting +traefik.http.middlewares.flic-ratelimit.ratelimit.average=10 # requests per second +traefik.http.middlewares.flic-ratelimit.ratelimit.burst=20 +# Apply the middleware to the router +traefik.http.routers.flic-webhook-webpush.middlewares=flic-ratelimit diff --git a/labels.example b/labels.example deleted file mode 100644 index 88fce42..0000000 --- a/labels.example +++ /dev/null @@ -1,29 +0,0 @@ -# Traefik v3 Labels for flic-webhook-webpush service - -# Enable Traefik for this container -traefik.enable=true - -# --- HTTP Router Definition --- -# Define an HTTP router named 'flic-webhook-http' -# Route requests based on Host and PathPrefix -traefik.http.routers.flic-webhook.rule=Host(`webpush.virtonline.eu`) -# Specify the entrypoint (e.g., 'websecure' for HTTPS) -traefik.http.routers.flic-webhook.entrypoints=websecure -# Specify the TLS certificate resolver -traefik.http.routers.flic-webhook.tls.certresolver=default -# Link this router to the service defined below -traefik.http.routers.flic-webhook.service=flic-webhook - -# --- HTTP Service Definition --- -# Define an HTTP service named 'flic-webhook' -# Point the service to the container's port (default 3000) -traefik.http.services.flic-webhook.loadbalancer.server.port=3000 - -# --- Middleware (Optional Example: Rate Limiting - Uncomment to enable) --- -# traefik.http.middlewares.flic-ratelimit.ratelimit.average=10 # requests per second -# traefik.http.middlewares.flic-ratelimit.ratelimit.burst=20 -# traefik.http.routers.flic-webhook.middlewares=flic-ratelimit - -# --- Docker Network --- -# Ensure Traefik uses the correct network to communicate with the container -traefik.docker.network=traefik \ No newline at end of file diff --git a/server.js b/server.js index 2973f47..36942bd 100644 --- a/server.js +++ b/server.js @@ -3,6 +3,7 @@ const webpush = require('web-push'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); +const dns = require('dns'); // Add DNS module // Load environment variables from .env file require('dotenv').config(); @@ -19,7 +20,26 @@ const flicSecret = process.env.FLIC_SECRET; // Optional Bearer token secret for const allowedOrigins = (process.env.ALLOWED_ORIGINS || "").split(',').map(origin => origin.trim()).filter(origin => origin); const allowedMethods = (process.env.ALLOWED_METHODS || "POST,OPTIONS,GET").split(',').map(method => method.trim()).filter(method => method); const allowedHeaders = (process.env.ALLOWED_HEADERS || "Content-Type,Authorization").split(',').map(header => header.trim()).filter(header => header); +// Retry configuration for DNS resolution issues +const maxRetries = parseInt(process.env.MAX_NOTIFICATION_RETRIES || 3, 10); +const initialRetryDelay = parseInt(process.env.INITIAL_RETRY_DELAY_MS || 1000, 10); // 1 second +// HTTP request timeout configuration +const httpTimeout = parseInt(process.env.HTTP_TIMEOUT_MS || 10000, 10); // 10 seconds +// Configure global HTTP agent with timeouts to prevent hanging requests +const https = require('https'); +const http = require('http'); + +// Custom HTTPS agent with timeout +const httpsAgent = new https.Agent({ + keepAlive: true, + timeout: httpTimeout, + maxSockets: 50, // Limit concurrent connections +}); + +// Apply the agent to the webpush module if possible +// Note: The web-push library might use its own agent, but this is a precaution +https.globalAgent = httpsAgent; // --- Validation --- if (!vapidPublicKey || !vapidPrivateKey || !vapidSubject) { @@ -34,6 +54,43 @@ webpush.setVapidDetails( vapidPrivateKey ); +// Configure DNS settings for more reliable resolution in containerized environments +// These settings can help with temporary DNS resolution failures +dns.setDefaultResultOrder('ipv4first'); // Prefer IPv4 to avoid some IPv6 issues in containers +const dnsTimeout = parseInt(process.env.DNS_TIMEOUT_MS || 5000, 10); +dns.setServers(dns.getServers()); // Reset DNS servers (can help in some Docker environments) + +// You can optionally configure a specific DNS server if needed: +// Example: dns.setServers(['8.8.8.8', '1.1.1.1']); + +// --- Utility function for retrying web push notifications with exponential backoff --- +async function sendWebPushWithRetry(subscription, payload, retryCount = 0, delay = initialRetryDelay) { + try { + return await webpush.sendNotification(subscription, payload); + } catch (error) { + // Check if the error is a DNS resolution error that might be temporary + const isDnsError = error.code === 'EAI_AGAIN' || + error.code === 'ENOTFOUND' || + error.code === 'ETIMEDOUT'; + + if (isDnsError && retryCount < maxRetries) { + console.log(`DNS resolution failed (${error.code}). Retrying notification in ${delay}ms (attempt ${retryCount + 1}/${maxRetries})...`); + + // Wait for the delay + await new Promise(resolve => setTimeout(resolve, delay)); + + // Exponential backoff with jitter + const nextDelay = delay * (1.5 + Math.random() * 0.5); + + // Retry recursively with increased count and delay + return sendWebPushWithRetry(subscription, payload, retryCount + 1, nextDelay); + } + + // If we've exhausted retries or it's not a DNS error, rethrow + throw error; + } +} + // --- Subscription Loading and Management --- let subscriptions = {}; // In-memory cache of subscriptions @@ -159,51 +216,54 @@ app.post('/subscribe', async (req, res) => { // --- Flic Webhook Endpoint --- // Apply Flic-specific authentication ONLY to this route app.post('/flic-webhook', authenticateFlicRequest, async (req, res) => { - // Assuming Flic sends 'button_id' which is the serial number - const { button_id, click_type, timestamp } = req.body; + // Get buttonName from Header 'Button-Name' and timestamp from Header 'Timestamp' + const buttonName = req.headers['button-name']; + const timestamp = req.headers['timestamp']; + // Still get click_type from the request body + const { click_type } = req.body; - console.log(`Received webhook: Button=${button_id}, Type=${click_type}, Timestamp=${timestamp || 'N/A'}`); + console.log(`Received webhook: Button=${buttonName}, Type=${click_type}, Timestamp=${timestamp || 'N/A'}`); // Basic validation - if (!button_id || !click_type) { - console.warn(`Webhook Error: Missing button_id or click_type`); - return res.status(400).json({ message: 'Bad Request: Missing button_id or click_type' }); + if (!buttonName || !click_type) { + console.warn(`Webhook Error: Missing Button-Name header or click_type in body`); + return res.status(400).json({ message: 'Bad Request: Missing Button-Name header or click_type in request body' }); } - const normalizedButtonId = button_id.toLowerCase(); // Use lowercase for lookup consistency + const normalizedButtonName = buttonName.toLowerCase(); // Use lowercase for lookup consistency - // Find the subscription associated with this button ID - const subscription = subscriptions[normalizedButtonId]; + // Find the subscription associated with this normalized button name + const subscription = subscriptions[normalizedButtonName]; if (!subscription) { - console.warn(`Webhook: No subscription found for button ID: ${normalizedButtonId} (original: ${button_id})`); - return res.status(404).json({ message: `Not Found: No subscription configured for button ${normalizedButtonId}` }); + console.warn(`Webhook: No subscription found for button ID: ${normalizedButtonName} (original: ${buttonName})`); + return res.status(404).json({ message: `Not Found: No subscription configured for button ${normalizedButtonName}` }); } // --- Send Web Push Notification --- const payload = JSON.stringify({ title: 'Flic Button Action', - body: `Button ${click_type}`, // Simplified body + body: `Button ${normalizedButtonName}: ${click_type}`, // Simplified body data: { action: click_type, - button: normalizedButtonId, // Send normalized ID + button: normalizedButtonName, // Send normalized button name timestamp: timestamp || new Date().toISOString() } // icon: '/path/to/icon.png' }); try { - console.log(`Sending push notification for ${normalizedButtonId} to endpoint: ${subscription.endpoint.substring(0, 40)}...`); - await webpush.sendNotification(subscription, payload); - console.log(`Push notification sent successfully for button ${normalizedButtonId}.`); + console.log(`Sending push notification for ${normalizedButtonName} to endpoint: ${subscription.endpoint.substring(0, 40)}...`); + await sendWebPushWithRetry(subscription, payload); + console.log(`Push notification sent successfully for button ${normalizedButtonName}.`); res.status(200).json({ message: 'Push notification sent successfully' }); } catch (error) { - console.error(`Error sending push notification for button ${normalizedButtonId}:`, error.body || error.message || error); + console.error(`Error sending push notification for button ${normalizedButtonName}:`, error.body || error.message || error); if (error.statusCode === 404 || error.statusCode === 410) { - console.warn(`Subscription for button ${normalizedButtonId} is invalid or expired (404/410). Removing it.`); + console.warn(`Subscription for button ${normalizedButtonName} is invalid or expired (404/410). Removing it.`); // Optionally remove the stale subscription - delete subscriptions[normalizedButtonId]; + delete subscriptions[normalizedButtonName]; saveSubscriptions(); // Attempt to save the updated list res.status(410).json({ message: 'Subscription Gone' }); } else { @@ -223,7 +283,6 @@ app.get('/health', (req, res) => { // --- Start Server --- // Use http.createServer to allow graceful shutdown -const http = require('http'); const server = http.createServer(app); server.listen(port, () => { @@ -234,6 +293,9 @@ server.listen(port, () => { console.log(`Flic Webhook Auth: ${flicSecret ? 'Enabled (Bearer Token)' : 'Disabled'}`); console.log(`Subscription Endpoint Auth: Disabled`); console.log(`Subscriptions File: ${subscriptionsFilePath}`); + console.log(`Push Notification Retry Config: ${maxRetries} retries, ${initialRetryDelay}ms initial delay`); + console.log(`DNS Config: IPv4 first, timeout ${dnsTimeout}ms`); + console.log(`HTTP Timeout: ${httpTimeout}ms`); }); // --- Graceful Shutdown --- @@ -249,7 +311,7 @@ const closeGracefully = (signal) => { setTimeout(() => { console.error('Could not close connections in time, forcefully shutting down'); process.exit(1); - }, 10000); // 10 seconds timeout + }, 10000); // 10 seconds timeout - This is a literal so no need to parse } process.on('SIGTERM', () => closeGracefully('SIGTERM'));