pywebpush

repository·main·Indexed 18 days ago

https://github.com/web-push-libs/pywebpush

A Python library for encrypting and sending Web Push notifications to browsers. It supports VAPID authentication, RFC8188 (aes128gcm) and deprecated aesgcm encryption standards, and provides both synchronous and asynchronous (asyncio) implementations. The library includes a high-level webpush() function, a WebPusher class for granular control, and a command-line interface for testing payloads and subscriptions.

Tokens
3.1K
Snippets
12
Records
15
Agent score
63%

What's inside pywebpush

  1. Install pywebpush

    main

    You can install pywebpush from PyPI. To install it locally for development, use a virtual environment and install in editable mode:

    python -m venv venv
    venv/bin/pip install --editable .
    python -m venv venv
    venv/bin/pip install --editable .
  2. Configure extra headers for Windows (WNS)

    main

    Microsoft Windows requires specific non-standard headers for push notifications to appear correctly. As of April 2024, X-WNS-Type is required. You can provide these via a JSON file using the --head flag in the CLI or the headers parameter in the Python API.

    Example windows_headers.json:

    {"X-WNS-Type":"wns/toast", "TTL":600, "Content-Type": "text/xml"}

    CLI usage with headers:

    pywebpush --data stuff_to_send.xml \
       --info edge_user_info.json \
       --head windows_headers.json \
       --claims vapid_claims.json
    {"X-WNS-Type":"wns/toast", "TTL":600, "Content-Type": "text/xml"}
  3. Configure VAPID authentication

    main

    To use VAPID (Voluntary Application Server Identification), provide a vapid_private_key and vapid_claims to webpush() or webpush_async().

    • vapid_private_key: Can be a py_vapid.Vapid instance, a path to a PEM file, or an encoded string.
    • vapid_claims: A dictionary of claims. The sub (subject) claim is required. The library will automatically calculate the aud (audience) from the endpoint URL and set an exp (expiry) if not provided.
  4. Handle WebPushException and remote service errors

    main

    When a push fails, pywebpush raises a WebPushException. Some services (like Mozilla) return additional error details in the response body. You can access these via the ex.response attribute to retrieve code, errno, and message.

    from pywebpush import webpush, WebPushException
    
    try:
        webpush(
            subscription_info={
                "endpoint": "https://push.example.com/v1/12345",
                "keys": {
                    "p256dh": "0123abcde...",
                    "auth": "abc123..."
                }},
            data="Mary had a little lamb, with a nice mint jelly",
            vapid_private_key="path/to/vapid_private.pem",
            vapid_claims={
                    "sub": "mailto:YourNameHere@example.org",
                }
        )
    except WebPushException as ex:
        print("Error: {}", repr(ex))
        if ex.response is not None and ex.response.json():
            extra = ex.response.json()
            print("Remote service replied with a {}:{}, {}",
                  extra.code,
                  extra.errno,
                  extra.message
                  )
  5. Methods of the WebPusher class

    main

    The WebPusher class provides two primary methods:

    .send(data, headers={}, ttl=0, reg_id="", content_encoding="aes128gcm", curl=False, timeout=None)

    Sends the data to the push server.

    • data: Binary string of data to send.
    • headers: A dict of additional HTTP headers.
    • ttl: Message Time To Live in seconds.
    • reg_id: GCM registration ID (extracted from endpoint if not provided).
    • content_encoding: ECE content encoding type (defaults to "aes128gcm").
    • curl: If True, does not execute the POST but returns a curl command for debugging. It writes encrypted content to encrypted.data.
    • timeout: Request timeout (compatible with requests library).

    .encode(data, content_encoding="aes128gcm")

    Encodes the data for future use. Returns a WebPushException if data is empty (raises NoData).

  6. Use the WebPusher class for advanced control

    main

    If you need to reuse a subscription or require more granular control, instantiate a WebPusher object with the subscription_info.

    from pywebpush import WebPusher
    
    wp = WebPusher(subscription_info)
    # Use wp.send(...) or wp.encode(...)
  7. Send a push notification using the `webpush()` one-call function

    main

    The webpush() function is a convenience method for sending a single message to a recipient. It handles data encoding, VAPID authentication headers, and the POST request to the push server.

    Parameters

    • subscription_info (dict): The subscription object (typically from a browser's PushSubscription.toJSON()). It must contain endpoint and keys (auth and p256dh).
    • data (any): Serialized content to send (string, JSON, bit array, etc.).
    • vapid_private_key (str): A path to a VAPID EC2 private key PEM file, or a string containing the DER representation (base64 encoded).
    • vapid_claims (dict): VAPID claims (e.g., {"sub": "mailto:user@example.com"}). Note: This dictionary will be mutated by the function to fill in aud and exp if they are missing.
    • content_type (str, optional): Encryption form. Defaults to the RFC 8188 standard. Options are 'aes128gcm' or the deprecated 'aesgcm'.
    from pywebpush import webpush
    
    webpush(subscription_info, 
            data, 
            vapid_private_key="Private Key or File Path", 
            vapid_claims={"sub": "mailto:YourEmailAddress"})
  8. Use the pywebpush CLI for testing

    main

    A standalone CLI tool is available in the ./bin directory for testing push interfaces without writing code. It requires a data file and a subscription info JSON file.

    ./bin/pywebpush --data stuff_to_send.data --info subscription.info
    ./bin/pywebpush --data stuff_to_send.data --info subscription.info
  9. Use the `webpush_async()` one-call function

    main

    The webpush_async() function is the asynchronous version of webpush(). It is suitable for asyncio environments and allows for connection reuse by passing an optional aiohttp_session. Like the synchronous version, it raises WebPushException on non-success responses.

    from pywebpush import webpush_async
    import asyncio
    
    async def send_notification():
        response = await webpush_async(
            subscription_info={
                "endpoint": "https://push.example.com/v1/abcd",
                "keys": {
                    "p256dh": "0123abcd...",
                    "auth": "001122..."
                }
            },
            data="Mary had a little lamb, with a nice mint jelly",
            vapid_private_key="path/to/key.pem",
            vapid_claims={"sub": "YourNameHere@example.com"}
        )
    
    asyncio.run(send_notification())
  10. Handle `WebPushException` errors

    main

    When a push request fails or the subscription information is invalid, the library raises a WebPushException. This exception includes a message and, if available, the response object (from requests or aiohttp) which contains the error body from the push service.

    try:
        webpush(...)
    except WebPushException as e:
        print(f"Error: {e.message}")
        if e.response is not None:
            print(f"Response body: {e.response.text}")
  11. Use the `webpush()` one-call function

    main

    The webpush() function is a high-level, synchronous solution to encode and send data to a Web Push endpoint. It handles encryption and optional VAPID authentication in a single call. If the request fails (status code > 202), it raises a WebPushException.

    from pywebpush import webpush
    
    webpush(
        subscription_info={
            "endpoint": "https://push.example.com/v1/abcd",
            "keys": {
                "p256dh": "0123abcd...",
                "auth": "001122..."
            }
        },
        data="Mary had a little lamb, with a nice mint jelly",
        vapid_private_key="path/to/key.pem",
        vapid_claims={"sub": "YourNameHere@example.com"}
    )
  12. Use the `WebPusher` class for manual control

    main

    For more granular control, use the WebPusher class. You initialize it with the client's subscription_info and can optionally provide a requests.Session (for synchronous) or aiohttp.ClientSession (for asynchronous) to optimize requests.

    Key methods:

    • encode(data, content_encoding): Encrypts the data block.
    • send(*args, **kwargs): Encodes and sends the data synchronously.
    • send_async(*args, **kwargs): Encodes and sends the data asynchronously.
    • as_curl(...): Returns a curl command string for debugging, which writes the encoded data to a local encrypted.data file.
    # Example of manual WebPusher usage
    subscription_info = {
        "endpoint": "https://push.server.com/...",
        "keys": {"auth": "...", "p256dh": "..."}
    }
    
    pusher = WebPusher(subscription_info)
    data = "Hello World"
    pusher.send(data)