Skip to content

Commit e852443

Browse files
authored
feat: Calendly (#750)
1 parent d2114c5 commit e852443

28 files changed

Lines changed: 1075 additions & 5 deletions

FIRST_PARTY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Four presets in `proxy-configs.ts` cover all proxy-enabled scripts:
113113
| `PRIVACY_NONE` | all false | (not currently assigned to any script) |
114114
| `PRIVACY_FULL` | all true | Meta, TikTok, X, Snap, Reddit, LinkedIn |
115115
| `PRIVACY_HEATMAP` | ip, language, hardware | GA, Clarity, Hotjar |
116-
| `PRIVACY_IP_ONLY` | ip only | PostHog, Plausible, Umami, Rybbit, Databuddy, Ahrefs, Fathom, CF Web Analytics, Vercel, Matomo, Carbon Ads, Lemon Squeezy, Intercom, Gravatar, YouTube, Vimeo |
116+
| `PRIVACY_IP_ONLY` | ip only | PostHog, Plausible, Umami, Rybbit, Databuddy, Ahrefs, Fathom, CF Web Analytics, Vercel, Matomo, Carbon Ads, Lemon Squeezy, Intercom, Gravatar, YouTube, Vimeo, Calendly |
117117

118118
Note: GTM, Segment, Crisp, Mixpanel, and Bing UET are bundle-only (no proxy capability), so no privacy transforms are applied.
119119

@@ -146,6 +146,7 @@ Note: GTM, Segment, Crisp, Mixpanel, and Bing UET are bundle-only (no proxy capa
146146
| `vimeoPlayer` | vimeoPlayer | `PRIVACY_IP_ONLY` | Path A |
147147
| `intercom` | intercom | `PRIVACY_IP_ONLY` | Path A |
148148
| `gravatar` | gravatar | `PRIVACY_IP_ONLY` | Path A |
149+
| `calendly` | calendly | `PRIVACY_IP_ONLY` | Path A |
149150
| `googleTagManager` | googleTagManager | n/a | Bundle only |
150151
| `segment` | segment | n/a | Bundle only |
151152
| `crisp` | crisp | n/a | Bundle only |

docs/content/docs/1.guides/2.first-party.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Every proxied script defaults to a privacy tier based on what level of anonymisa
6363
|------|-------------------|---------|
6464
| **Full** | IP, user agent, language, screen, timezone, hardware fingerprints | Meta Pixel, TikTok Pixel, X Pixel, Snapchat Pixel, Reddit Pixel, LinkedIn Insight Tag |
6565
| **Heatmap-safe** | IP, language, hardware fingerprints (preserves screen and user agent for session replay) | Google Analytics, Microsoft Clarity, Hotjar |
66-
| **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Ahrefs Web Analytics, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense |
66+
| **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Ahrefs Web Analytics, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense, Calendly |
6767

6868
Sensitive headers (`cookie`, `authorization`) are **always** stripped regardless of tier.
6969

docs/content/scripts/calendly.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: Calendly
3+
description: Embed Calendly bookings in your Nuxt app with inline, popup, and badge widgets.
4+
links:
5+
- label: useScriptCalendly
6+
icon: i-simple-icons-github
7+
to: https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/registry/calendly.ts
8+
size: xs
9+
- label: "<ScriptCalendlyInlineWidget>"
10+
icon: i-simple-icons-github
11+
to: https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/components/ScriptCalendlyInlineWidget.vue
12+
size: xs
13+
---
14+
15+
[Calendly](https://calendly.com) is a scheduling tool that lets visitors book time on your calendar without back-and-forth emails. The Calendly embed widget renders the booking flow inline, in a popup, or behind a floating badge button.
16+
17+
Nuxt Scripts provides a registry script composable [`useScriptCalendly()`{lang="ts"}](/scripts/calendly) and a headless [`<ScriptCalendlyInlineWidget>`{lang="html"}](/scripts/calendly){lang="html"} component to integrate it in your Nuxt app.
18+
19+
::script-stats
20+
::
21+
22+
::script-docs
23+
::
24+
25+
The composable comes with the following defaults:
26+
- **Trigger: Client** Script will load when Nuxt is hydrating.
27+
- **Stylesheet: Inline** The widget stylesheet (and its close-icon SVG) is inlined on first use, so no IP leak to `assets.calendly.com` on render.
28+
29+
You can access the `Calendly` global as a proxy directly or await `onLoaded` to use it. Recommended to use the proxy for void calls; `onLoaded` is convenient when you need a stable DOM reference.
30+
31+
::code-group
32+
33+
```ts [Proxy]
34+
const { proxy } = useScriptCalendly()
35+
function openBooking() {
36+
proxy.Calendly.initPopupWidget({
37+
url: 'https://calendly.com/your-name/30min',
38+
})
39+
}
40+
```
41+
42+
```ts [onLoaded]
43+
const { onLoaded } = useScriptCalendly()
44+
onLoaded(({ Calendly }) => {
45+
Calendly.initInlineWidget({
46+
url: 'https://calendly.com/your-name/30min',
47+
parentElement: document.getElementById('calendly-inline')!,
48+
})
49+
})
50+
```
51+
52+
::
53+
54+
## [`<ScriptCalendlyInlineWidget>`{lang="html"}](/scripts/calendly){lang="html"}
55+
56+
The [`<ScriptCalendlyInlineWidget>`{lang="html"}](/scripts/calendly){lang="html"} component wraps [`useScriptCalendly()`{lang="ts"}](/scripts/calendly){lang="ts"} for the most common embed shape: an inline booking flow mounted into a host element you control.
57+
58+
It's optimized for performance by using [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers), only loading the Calendly widget script once the host element comes into view. By default the trigger is `'visible'`.
59+
60+
```vue
61+
<script setup lang="ts">
62+
const ready = ref(false)
63+
</script>
64+
65+
<template>
66+
<ScriptCalendlyInlineWidget
67+
url="https://calendly.com/your-name/30min"
68+
@ready="ready = true"
69+
/>
70+
</template>
71+
```
72+
73+
### Above-the-fold loading
74+
75+
If the widget is above the fold and you want it to start loading on hydration rather than on visibility, set `above-the-fold` (adds a preconnect to `calendly.com`) and override the trigger.
76+
77+
```vue
78+
<ScriptCalendlyInlineWidget
79+
url="https://calendly.com/your-name/30min"
80+
above-the-fold
81+
trigger="onNuxtReady"
82+
/>
83+
```
84+
85+
### Prefill, UTM, and page settings
86+
87+
```vue
88+
<ScriptCalendlyInlineWidget
89+
url="https://calendly.com/your-name/30min"
90+
:prefill="{ name: 'Ada Lovelace', email: 'ada@example.com' }"
91+
:utm="{ utmSource: 'website', utmMedium: 'cta', utmCampaign: 'launch' }"
92+
:page-settings="{ hideEventTypeDetails: true, hideGdprBanner: true }"
93+
/>
94+
```
95+
96+
### Slots
97+
98+
The component exposes `loading`, `awaitingLoad`, and `error` slots for placeholder UX while the script trigger waits or the script load fails. The default `loading` slot renders an accessible spinner.
99+
100+
## Popup and badge widgets
101+
102+
Popup and badge modes have no host element, so they're driven from the composable directly:
103+
104+
::code-group
105+
106+
```ts [Popup]
107+
const { proxy } = useScriptCalendly()
108+
function open() {
109+
proxy.Calendly.initPopupWidget({
110+
url: 'https://calendly.com/your-name/30min',
111+
})
112+
}
113+
```
114+
115+
```ts [Badge]
116+
const { onLoaded } = useScriptCalendly()
117+
onLoaded(({ Calendly }) => {
118+
Calendly.initBadgeWidget({
119+
url: 'https://calendly.com/your-name/30min',
120+
text: 'Schedule time with me',
121+
color: '#0069ff',
122+
textColor: '#ffffff',
123+
})
124+
})
125+
```
126+
127+
::
128+
129+
## Prefilling invitee details and UTM parameters
130+
131+
All four widget initialisers (`initInlineWidget`, `initPopupWidget`, `initBadgeWidget`, `initPopupWidgetWithText`) accept `prefill` and `utm` options to pre-populate the booking form and tag the booking with marketing attribution.
132+
133+
```vue
134+
<script setup lang="ts">
135+
const { proxy } = useScriptCalendly()
136+
137+
function bookFromCampaign(user: { name: string, email: string }) {
138+
proxy.Calendly.initPopupWidget({
139+
url: 'https://calendly.com/your-name/30min',
140+
prefill: {
141+
name: user.name,
142+
email: user.email,
143+
},
144+
utm: {
145+
utmSource: 'website',
146+
utmMedium: 'cta',
147+
utmCampaign: 'launch',
148+
},
149+
})
150+
}
151+
</script>
152+
```
153+
154+
::script-types
155+
::
156+
157+
## Example
158+
159+
Loading Calendly through `app.vue` when Nuxt is ready, with the inline widget rendered on a booking page.
160+
161+
```vue [app.vue]
162+
<script setup lang="ts">
163+
useScriptCalendly({
164+
scriptOptions: {
165+
trigger: 'onNuxtReady',
166+
},
167+
})
168+
</script>
169+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"dev": "nuxt dev playground",
2020
"dev:ssl": "nuxt dev playground --https",
2121
"dev:prepare": "pnpm -r dev:prepare && nuxt prepare && nuxt prepare playground && pnpm prepare:fixtures",
22-
"prepare:fixtures": "nuxt prepare test/fixtures/basic && nuxt prepare test/fixtures/cdn && nuxt prepare test/fixtures/extend-registry && nuxt prepare test/fixtures/partytown && nuxt prepare test/fixtures/first-party && nuxt prepare test/fixtures/linkedin-insight && nuxt prepare test/fixtures/linkedin-insight-cdn && nuxt prepare test/fixtures/ahrefs-analytics && nuxt prepare test/fixtures/ahrefs-analytics-cdn && nuxt prepare test/fixtures/usercentrics",
22+
"prepare:fixtures": "nuxt prepare test/fixtures/basic && nuxt prepare test/fixtures/cdn && nuxt prepare test/fixtures/extend-registry && nuxt prepare test/fixtures/partytown && nuxt prepare test/fixtures/first-party && nuxt prepare test/fixtures/linkedin-insight && nuxt prepare test/fixtures/linkedin-insight-cdn && nuxt prepare test/fixtures/calendly && nuxt prepare test/fixtures/calendly-cdn && nuxt prepare test/fixtures/ahrefs-analytics && nuxt prepare test/fixtures/ahrefs-analytics-cdn && nuxt prepare test/fixtures/usercentrics",
2323
"typecheck": "nuxt typecheck",
2424
"release": "pnpm build && bumpp -r --output=CHANGELOG.md",
2525
"lint": "eslint .",

packages/script/src/registry-logos.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const LOGOS = {
5454
dark: `<svg height="30" width="35" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter id="a" height="138.7%" width="131.4%" x="-15.7%" y="-15.1%"><feMorphology in="SourceAlpha" operator="dilate" radius="1" result="shadowSpreadOuter1"/><feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1"/><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/></filter><path id="b" d="M14.23 20.46l-9.65 1.1L3 5.12 30.07 2l1.58 16.46-9.37 1.07-3.5 5.72-4.55-4.8z"/></defs><g fill="none" fill-rule="evenodd"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#fff" stroke="#fff" stroke-width="2" xlink:href="#b"/></g></svg>`,
5555
},
5656
npm: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#C12127" d="M0 256V0h256v256z"/><path fill="#FFF" d="M48 48h160v160h-32V80h-48v128H48z"/></svg>`,
57+
calendly: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#006BFF" d="M128 0C57.308 0 0 57.308 0 128s57.308 128 128 128s128-57.308 128-128S198.692 0 128 0m65.832 165.957l-15.226 13.157c-13.768 11.892-31.358 18.435-49.55 18.435h-2.36c-26.36 0-50.51-14.518-62.99-37.85l-7.392-13.823a71.32 71.32 0 0 1 0-67.752l7.391-13.822c12.48-23.333 36.63-37.851 62.99-37.851h2.361c18.193 0 35.782 6.543 49.55 18.435l15.226 13.157a8.93 8.93 0 0 1 .904 12.598a8.93 8.93 0 0 1-12.598.904l-15.225-13.157c-10.527-9.094-23.97-14.094-37.857-14.094h-2.361c-19.823 0-37.985 10.918-47.367 28.466l-7.39 13.822a53.49 53.49 0 0 0 0 50.83l7.39 13.822c9.382 17.548 27.544 28.466 47.367 28.466h2.361c13.886 0 27.33-5 37.857-14.094l15.225-13.157a8.93 8.93 0 0 1 12.598.904a8.93 8.93 0 0 1-.904 12.598"/></svg>`,
5758
googleRecaptcha: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 262"><path fill="#4285F4" d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"/><path fill="#34A853" d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"/><path fill="#FBBC05" d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"/><path fill="#EB4335" d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"/></svg>`,
5859
googleSignIn: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 262"><path fill="#4285F4" d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"/><path fill="#34A853" d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"/><path fill="#FBBC05" d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"/><path fill="#EB4335" d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"/></svg>`,
5960
googleTagManager: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#8AB4F8" d="m150.262 245.516l-44.437-43.331l95.433-97.454l46.007 45.091z"/><path fill="#4285F4" d="M150.45 53.938L106.176 8.731L9.36 104.629c-12.48 12.48-12.48 32.713 0 45.207l95.36 95.986l45.09-42.182l-72.654-76.407z"/><path fill="#8AB4F8" d="m246.625 105.37l-96-96c-12.494-12.494-32.756-12.494-45.25 0c-12.495 12.495-12.495 32.757 0 45.252l96 96c12.494 12.494 32.756 12.494 45.25 0c12.495-12.495 12.495-32.757 0-45.251"/><circle cx="127.265" cy="224.731" r="31.273" fill="#246FDB"/></svg>`,

packages/script/src/registry-types.json

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,48 @@
116116
"code": "interface ScriptBlueskyEmbedSlots {\n default?: (props: object) => any\n loading?: () => any\n error?: (props: object) => any\n}"
117117
}
118118
],
119+
"calendly": [
120+
{
121+
"name": "CalendlyOptions",
122+
"kind": "const",
123+
"code": "export const CalendlyOptions = object({\n /**\n * The Calendly event URL to embed.\n * Required for inline, popup, and badge widgets when called via the composable.\n * @example 'https://calendly.com/your-name/30min'\n * @see https://help.calendly.com/hc/en-us/articles/223147027\n */\n url: optional(string()),\n /**\n * Pre-fill invitee fields on the booking form.\n * @see https://help.calendly.com/hc/en-us/articles/360020052833\n */\n prefill: optional(object({\n name: optional(string()),\n email: optional(string()),\n firstName: optional(string()),\n lastName: optional(string()),\n /** Custom answers keyed by `a1`, `a2`, ... matching custom question order. */\n customAnswers: optional(record(string(), string())),\n })),\n /**\n * UTM parameters appended to the booking URL for marketing attribution.\n * @see https://help.calendly.com/hc/en-us/articles/360020052833\n */\n utm: optional(object({\n utmCampaign: optional(string()),\n utmSource: optional(string()),\n utmMedium: optional(string()),\n utmContent: optional(string()),\n utmTerm: optional(string()),\n })),\n /**\n * Theme and layout overrides applied to the booking page.\n * @see https://help.calendly.com/hc/en-us/articles/360020052833\n */\n pageSettings: optional(object({\n backgroundColor: optional(string()),\n hideEventTypeDetails: optional(boolean()),\n hideLandingPageDetails: optional(boolean()),\n primaryColor: optional(string()),\n textColor: optional(string()),\n })),\n /**\n * CSS selector for the element that hosts the inline widget.\n * Required when the widget is initialised inline; the element should have a\n * minimum height of around 700px so the booking iframe is fully visible.\n */\n parentElement: optional(string()),\n})"
124+
},
125+
{
126+
"name": "CalendlyPrefill",
127+
"kind": "interface",
128+
"code": "interface CalendlyPrefill {\n name?: string\n email?: string\n firstName?: string\n lastName?: string\n customAnswers?: Record<string, string>\n}"
129+
},
130+
{
131+
"name": "CalendlyUtm",
132+
"kind": "interface",
133+
"code": "interface CalendlyUtm {\n utmCampaign?: string\n utmSource?: string\n utmMedium?: string\n utmContent?: string\n utmTerm?: string\n}"
134+
},
135+
{
136+
"name": "CalendlyPageSettings",
137+
"kind": "interface",
138+
"code": "interface CalendlyPageSettings {\n backgroundColor?: string\n hideEventTypeDetails?: boolean\n hideLandingPageDetails?: boolean\n primaryColor?: string\n textColor?: string\n}"
139+
},
140+
{
141+
"name": "CalendlyInlineWidgetOptions",
142+
"kind": "interface",
143+
"code": "export interface CalendlyInlineWidgetOptions {\n url: string\n parentElement: HTMLElement\n prefill?: CalendlyPrefill\n utm?: CalendlyUtm\n pageSettings?: CalendlyPageSettings\n}"
144+
},
145+
{
146+
"name": "CalendlyPopupWidgetOptions",
147+
"kind": "interface",
148+
"code": "export interface CalendlyPopupWidgetOptions {\n url: string\n rootElement?: HTMLElement\n text?: string\n color?: string\n textColor?: string\n branding?: boolean\n prefill?: CalendlyPrefill\n utm?: CalendlyUtm\n pageSettings?: CalendlyPageSettings\n}"
149+
},
150+
{
151+
"name": "CalendlyBadgeWidgetOptions",
152+
"kind": "interface",
153+
"code": "export interface CalendlyBadgeWidgetOptions {\n url: string\n text?: string\n color?: string\n textColor?: string\n branding?: boolean\n prefill?: CalendlyPrefill\n utm?: CalendlyUtm\n pageSettings?: CalendlyPageSettings\n}"
154+
},
155+
{
156+
"name": "CalendlyApi",
157+
"kind": "interface",
158+
"code": "export interface CalendlyApi {\n Calendly: {\n initInlineWidget: (options: CalendlyInlineWidgetOptions) => void\n initPopupWidget: (options: CalendlyPopupWidgetOptions) => void\n initBadgeWidget: (options: CalendlyBadgeWidgetOptions) => void\n showPopupWidget: (url: string) => void\n closePopupWidget: () => void\n initPopupWidgetWithText: (options: CalendlyPopupWidgetOptions) => void\n q?: unknown[]\n }\n}"
159+
}
160+
],
119161
"clarity": [
120162
{
121163
"name": "ClarityOptions",
@@ -2138,6 +2180,38 @@
21382180
"description": "Override the language displayed by the CMP UI (BCP-47 code, e.g. `'en'`, `'de'`)."
21392181
}
21402182
],
2183+
"CalendlyOptions": [
2184+
{
2185+
"name": "url",
2186+
"type": "string",
2187+
"required": false,
2188+
"description": "The Calendly event URL to embed. Required for inline, popup, and badge widgets when called via the composable."
2189+
},
2190+
{
2191+
"name": "prefill",
2192+
"type": "object",
2193+
"required": false,
2194+
"description": "Pre-fill invitee fields on the booking form."
2195+
},
2196+
{
2197+
"name": "utm",
2198+
"type": "object",
2199+
"required": false,
2200+
"description": "UTM parameters appended to the booking URL for marketing attribution."
2201+
},
2202+
{
2203+
"name": "pageSettings",
2204+
"type": "object",
2205+
"required": false,
2206+
"description": "Theme and layout overrides applied to the booking page."
2207+
},
2208+
{
2209+
"name": "parentElement",
2210+
"type": "string",
2211+
"required": false,
2212+
"description": "CSS selector for the element that hosts the inline widget. Required when the widget is initialised inline; the element should have a minimum height of around 700px so the booking iframe is fully visible."
2213+
}
2214+
],
21412215
"SegmentOptions": [
21422216
{
21432217
"name": "writeKey",

0 commit comments

Comments
 (0)