{"id":17635946,"url":"https://github.com/mohawk2/mojolicious-plugin-webpush","last_synced_at":"2025-03-30T03:43:09.214Z","repository":{"id":56833992,"uuid":"265352386","full_name":"mohawk2/Mojolicious-Plugin-WebPush","owner":"mohawk2","description":"Mojolicious plugin to aid real-time web push","archived":false,"fork":false,"pushed_at":"2021-02-24T16:43:38.000Z","size":44,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-05T06:14:23.327Z","etag":null,"topics":["webpush"],"latest_commit_sha":null,"homepage":"https://metacpan.org/pod/Mojolicious::Plugin::WebPush","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mohawk2.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-19T20:02:38.000Z","updated_at":"2021-02-24T16:43:35.000Z","dependencies_parsed_at":"2022-09-13T08:42:05.396Z","dependency_job_id":null,"html_url":"https://github.com/mohawk2/Mojolicious-Plugin-WebPush","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mohawk2%2FMojolicious-Plugin-WebPush","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mohawk2%2FMojolicious-Plugin-WebPush/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mohawk2%2FMojolicious-Plugin-WebPush/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mohawk2%2FMojolicious-Plugin-WebPush/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mohawk2","download_url":"https://codeload.github.com/mohawk2/Mojolicious-Plugin-WebPush/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246273515,"owners_count":20750904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["webpush"],"created_at":"2024-10-23T02:24:46.261Z","updated_at":"2025-03-30T03:43:09.189Z","avatar_url":"https://github.com/mohawk2.png","language":"Perl","readme":"# NAME\n\nMojolicious::Plugin::WebPush - plugin to aid real-time web push\n\n# SYNOPSIS\n\n    # Mojolicious::Lite\n    my $sw = plugin 'ServiceWorker' =\u003e { debug =\u003e 1 };\n    my $webpush = plugin 'WebPush' =\u003e {\n      save_endpoint =\u003e '/api/savesubs',\n      subs_session2user_p =\u003e \\\u0026subs_session2user_p,\n      subs_create_p =\u003e \\\u0026subs_create_p,\n      subs_read_p =\u003e \\\u0026subs_read_p,\n      subs_delete_p =\u003e \\\u0026subs_delete_p,\n      ecc_private_key =\u003e 'vapid_private_key.pem',\n      claim_sub =\u003e \"mailto:admin@example.com\",\n    };\n\n    sub subs_session2user_p {\n      my ($c, $session) = @_;\n      return Mojo::Promise-\u003ereject(\"Session not logged in\") if !$session-\u003e{user_id};\n      Mojo::Promise-\u003eresolve($session-\u003e{user_id});\n    }\n\n    sub subs_create_p {\n      my ($c, $session, $subs_info) = @_;\n      app-\u003edb-\u003esave_subs_p($session-\u003e{user_id}, $subs_info);\n    }\n\n    sub subs_read_p {\n      my ($c, $user_id) = @_;\n      app-\u003edb-\u003elookup_subs_p($user_id);\n    }\n\n    sub subs_delete_p {\n      my ($c, $user_id) = @_;\n      app-\u003edb-\u003edelete_subs_p($user_id);\n    }\n\n# DESCRIPTION\n\n[Mojolicious::Plugin::WebPush](https://metacpan.org/pod/Mojolicious%3A%3APlugin%3A%3AWebPush) is a [Mojolicious](https://metacpan.org/pod/Mojolicious) plugin. In\norder to function, your app needs to have first installed\n[Mojolicious::Plugin::ServiceWorker](https://metacpan.org/pod/Mojolicious%3A%3APlugin%3A%3AServiceWorker) as shown in the synopsis above.\n\n# METHODS\n\n[Mojolicious::Plugin::WebPush](https://metacpan.org/pod/Mojolicious%3A%3APlugin%3A%3AWebPush) inherits all methods from\n[Mojolicious::Plugin](https://metacpan.org/pod/Mojolicious%3A%3APlugin) and implements the following new ones.\n\n## register\n\n    my $p = $plugin-\u003eregister(Mojolicious-\u003enew, \\%conf);\n\nRegister plugin in [Mojolicious](https://metacpan.org/pod/Mojolicious) application, returning the plugin\nobject. Takes a hash-ref as configuration, see [\"OPTIONS\"](#options) for keys.\n\n# OPTIONS\n\n## save\\_endpoint\n\nRequired. The route to be added to the app for the service worker to\nregister users for push notification. The handler for that will call\nthe [\"subs\\_create\\_p\"](#subs_create_p). If success is indicated, it will return JSON:\n\n    { \"data\": { \"success\": true } }\n\nIf failure:\n\n    { \"errors\": [ { \"message\": \"The exception reason\" } ] }\n\nThis will be handled by the provided service worker. In case it is\nrequired by the app itself, the added route is named `webpush.save`.\n\n## subs\\_session2user\\_p\n\nRequired. The code to be called to look up the user currently identified\nby this session, which returns a promise of the user ID. Must reject\nif no user logged in and that matters. It will be passed parameters:\n\n- The [\"session\" in Mojolicious::Controller](https://metacpan.org/pod/Mojolicious%3A%3AController#session) object, to correctly identify\nthe user.\n\n## subs\\_create\\_p\n\nRequired. The code to be called to store users registered for push\nnotifications, which must return a promise of a true value if the\noperation succeeds, or reject with a reason. It will be passed parameters:\n\n- The ID to correctly identify the user. Please note that you ought to\nallow one person to have several devices with web-push enabled, and to\ndesign accordingly.\n- The `subscription_info` hash-ref, needed to push actual messages.\n\n## subs\\_read\\_p\n\nRequired. The code to be called to look up a user registered for push\nnotifications. It will be passed parameters:\n\n- The opaque information your app uses to identify the user.\n\nReturns a promise of the `subscription_info` hash-ref. Must reject if\nnot found.\n\n## subs\\_delete\\_p\n\nRequired. The code to be called to delete up a user registered for push\nnotifications. It will be passed parameters:\n\n- The opaque information your app uses to identify the user.\n\nReturns a promise of the deletion result. Must reject if not found.\n\n## ecc\\_private\\_key\n\nA value to be passed to [\"new\" in Crypt::PK::ECC](https://metacpan.org/pod/Crypt%3A%3APK%3A%3AECC#new): a simple scalar is a\nfilename, a scalar-ref is the actual key. If not provided,\n[\"webpush.authorization\"](#webpush-authorization) will (obviously) not be able to function.\n\n## claim\\_sub\n\nA value to be used as the `sub` claim by the [\"webpush.authorization\"](#webpush-authorization),\nwhich needs it. Must be either an HTTPS or `mailto:` URL.\n\n## claim\\_exp\\_offset\n\nA value to be added to current time, in seconds, in the `exp` claim\nfor [\"webpush.authorization\"](#webpush-authorization). Defaults to 86400 (24 hours). The maximum\nvalid value in RFC 8292 is 86400.\n\n## push\\_handler\n\nOverride the default push-event handler supplied to\n[\"add\\_event\\_listener\" in Mojolicious::Plugin::ServiceWorker](https://metacpan.org/pod/Mojolicious%3A%3APlugin%3A%3AServiceWorker#add_event_listener). The default\nwill interpret the message as a JSON object. The key `title` will be\nthe notification title, deleted from that object, then the object will be\nthe options passed to `\u003cServiceWorkerRegistration\u003e.showNotification`.\n\nSee\n[https://developers.google.com/web/fundamentals/push-notifications/handling-messages](https://developers.google.com/web/fundamentals/push-notifications/handling-messages)\nfor possibilities.\n\n# HELPERS\n\n## webpush.create\\_p\n\n    $c-\u003ewebpush-\u003ecreate_p($user_id, $subs_info)-\u003ethen(sub {\n      $c-\u003erender(json =\u003e { data =\u003e { success =\u003e \\1 } });\n    });\n\n## webpush.read\\_p\n\n    $c-\u003ewebpush-\u003eread_p($user_id)-\u003ethen(sub {\n      $c-\u003erender(text =\u003e 'Info: ' . to_json(shift));\n    });\n\n## webpush.delete\\_p\n\n    $c-\u003ewebpush-\u003edelete_p($user_id)-\u003ethen(sub {\n      $c-\u003erender(json =\u003e { data =\u003e { success =\u003e \\1 } });\n    });\n\n## webpush.authorization\n\n    my $header_value = $c-\u003ewebpush-\u003eauthorization($subs_info);\n\nWon't function without [\"claim\\_sub\"](#claim_sub) and [\"ecc\\_private\\_key\"](#ecc_private_key), or\n`$subs_info` having a valid URL to get the base of as the `aud`\nclaim. Returns a suitable `Authorization` header value to send to\na push service.  Valid for a period defined by [\"claim\\_exp\\_offset\"](#claim_exp_offset).\nbut could become so to avoid unnecessary computation.\n\n## webpush.public\\_key\n\n    my $pkey = $c-\u003ewebpush-\u003epublic_key;\n\nGives the app's public VAPID key, calculated from the private key.\n\n## webpush.verify\\_token\n\n    my $bool = $c-\u003ewebpush-\u003everify_token($authorization_header_value);\n\nCryptographically verifies a JSON Web Token (JWT), such as generated\nby [\"webpush.authorization\"](#webpush-authorization).\n\n## webpush.encrypt\n\n    use MIME::Base64 qw(decode_base64url);\n    my $ciphertext = $c-\u003ewebpush-\u003eencrypt($data_bytes,\n      map decode_base64url($_), @{$subscription_info-\u003e{keys}}{qw(p256dh auth)}\n    );\n\nReturns the data encrypted according to RFC 8188, for the relevant\nsubscriber.\n\n## webpush.send\\_p\n\n    my $result_p = $c-\u003ewebpush-\u003esend_p($jsonable_data, $user_id, $ttl, $urgency);\n\nJSON-encodes the given value, encrypts it according to the given user's\nsubscription data, adds a VAPID `Authorization` header, then sends it\nto the relevant web-push endpoint.\n\nReturns a promise of the result, which will be a hash-ref with either a\n`data` key indicating success, or an `errors` key for an array-ref of\nhash-refs with a `message` giving reasons.\n\nIf the sending gets a status code of 404 or 410, this indicates the\nsubscriber has unsubscribed, and [\"webpush.delete\\_p\"](#webpush-delete_p) will be used to\nremove the registration. This is considered success.\n\nThe `urgency` must be one of `very-low`, `low`, `normal` (the default)\nor `high`. The `ttl` defaults to 30 seconds.\n\n# TEMPLATES\n\nVarious templates are available for including in the app's templates:\n\n## webpush-askPermission.html.ep\n\nJavaScript functions, also for putting inside a `script` element:\n\n- askPermission\n- subscribeUserToPush\n- sendSubscriptionToBackEnd\n\nThese each return a promise, and should be chained together:\n\n    \u003cbutton onclick=\"\n      askPermission().then(subscribeUserToPush).then(sendSubscriptionToBackEnd)\n    \"\u003e\n      Ask permission\n    \u003c/button\u003e\n    \u003cscript\u003e\n    %= include 'serviceworker-install'\n    %= include 'webpush-askPermission'\n    \u003c/script\u003e\n\nEach application must decide when to ask such permission, bearing in\nmind that once permission is refused, it is very difficult for the user\nto change such a refusal.\n\nWhen it is granted, the JavaScript code will communicate with the\napplication, registering the needed information needed to web-push.\n\n# SEE ALSO\n\n[Mojolicious](https://metacpan.org/pod/Mojolicious), [Mojolicious::Guides](https://metacpan.org/pod/Mojolicious%3A%3AGuides), [https://mojolicious.org](https://mojolicious.org).\n\n[Mojolicious::Command::webpush](https://metacpan.org/pod/Mojolicious%3A%3ACommand%3A%3Awebpush) - command-line control of web-push.\n\nRFC 8292 - Voluntary Application Server Identification (for web push).\n\n[Crypt::RFC8188](https://metacpan.org/pod/Crypt%3A%3ARFC8188) - Encrypted Content-Encoding for HTTP (using `aes128gcm`).\n\n[https://developers.google.com/web/fundamentals/push-notifications](https://developers.google.com/web/fundamentals/push-notifications)\n\n# ACKNOWLEDGEMENTS\n\nPart of this code is ported from\n[https://github.com/web-push-libs/pywebpush](https://github.com/web-push-libs/pywebpush).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmohawk2%2Fmojolicious-plugin-webpush","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmohawk2%2Fmojolicious-plugin-webpush","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmohawk2%2Fmojolicious-plugin-webpush/lists"}