npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vennio/proposals-widget

v0.1.1

Published

Embeddable proposal-response widget for Vennio — recipients accept, counter, or decline a meeting proposal with 2 lines of HTML

Downloads

15

Readme

@vennio/proposals-widget

Embed a Vennio proposal-response widget with 2 lines of HTML. Recipients accept, counter, or decline a meeting proposal — no framework required, no API keys, no architectural choices.

This is the demand-side counterpart to @vennio/widget (supply-side bookings). Where @vennio/widget embeds a shareable-link booking page, @vennio/proposals-widget embeds the magic-link recipient view at vennio.app/p/:token.

Quick Start

<script src="https://unpkg.com/@vennio/proposals-widget"></script>
<div data-vennio-proposal data-token="MAGIC_LINK_TOKEN"></div>

That's it. Replace MAGIC_LINK_TOKEN with the token from a Vennio proposal-invitation email (or the token field returned by POST /v1/proposals).

Why this widget exists

Vennio's proposal API has a deliberate guardrail: the proposal organizer cannot respond to their own proposal. Responses come from recipients, authenticated via magic-link token — not via the organizer's API key. Calling POST /v1/proposals/:id/response with the organizer's key returns 403 organizer_cannot_respond.

This widget removes that footgun by embedding the recipient flow directly. Your app passes the token, the widget renders the page, the recipient acts. You never call the response API yourself, so you can never accidentally call it as the wrong principal.

Options

<div
  data-vennio-proposal
  data-token="abc123"
  data-height="700px"
  data-width="100%"
></div>

| Attribute | Default | Description | |-----------|---------|-------------| | data-token | (required) | The magic-link token for the proposal | | data-height | 700px | Widget height | | data-width | 100% | Widget width | | data-base-url | https://vennio.app | Custom Vennio URL (for self-hosted) |

Programmatic Usage

import { init } from '@vennio/proposals-widget'

const container = document.getElementById('proposal')

const unsubscribe = init(container, {
  token: 'abc123',
  height: '700px',
  onAccepted: ({ proposalId, slot }) => {
    console.log('Accepted', proposalId, slot)
  },
  onCountered: ({ proposalId, counterSlots }) => {
    console.log('Countered with', counterSlots)
  },
  onRejected: ({ proposalId }) => {
    console.log('Declined', proposalId)
  },
  onEvent: (event) => {
    // Fires for every terminal state, including errors:
    //   accepted, countered, rejected, expired, used, not_found,
    //   calendar_disconnected, error
    console.log('Event:', event.type, event)
  },
})

// Optional: call unsubscribe() to remove the message listener.
// Calling init() again on the same container also auto-unsubscribes
// the previous handler, so SPAs that re-init on token change won't leak.

Event payloads

  • onAccepted{ proposalId, slot: { id, start_time, end_time } | null }
  • onCountered{ proposalId, counterSlots: [{ start, end, preference? }] }
  • onRejected{ proposalId }
  • onEvent — every event above, plus the terminal/error states. Use this when you need to react to expiry, already-used tokens, calendar-disconnected recipients, or fetch errors.

Note the asymmetry on slot field names: accepted slots carry start_time/end_time (the GET-response shape), while counter slots carry start/end (the POST-request shape). This matches the underlying API.

React

If you're using React, install @vennio/react and use the <VennioProposal> component instead of mounting this widget by hand:

import { VennioProposal } from '@vennio/react'

<VennioProposal
  token={token}
  onAccepted={({ slot }) => console.log('accepted', slot)}
  onCountered={({ counterSlots }) => console.log('countered', counterSlots)}
  onRejected={() => console.log('rejected')}
/>

Security: the token in the DOM

The widget reads the magic-link token from a data-token attribute, which means the token is in the rendered HTML and inspectable in DevTools. This is by design. Magic-link tokens are:

  • Single-use. The token is consumed atomically the moment the recipient submits an accept/counter/reject. A second attempt returns 401 invalid_token.
  • Recipient-scoped. A token binds one specific recipient to one specific proposal. It cannot be used to read or respond to anything else.
  • Already-public-by-design. The same token travels through the recipient's email inbox in the proposal-invitation URL (https://vennio.app/p/:token). The widget does not increase its exposure surface.

In other words, the token is intentionally a bearer credential with a tiny blast radius. Treat it the way you would a one-time signed URL — don't log it long-term, don't share it across recipients, but don't be afraid to render it.

The widget verifies event.origin on incoming postMessage events, so a malicious page on a different origin cannot spoof acceptance/counter/reject callbacks.

Building from source

cd packages/proposals-widget
npm install
npm run build

The build emits three formats:

  • dist/widget.global.js — IIFE for <script src> tags (default unpkg/jsdelivr target)
  • dist/widget.mjs — ESM for bundlers
  • dist/widget.js — CJS

License

MIT