From c7504ee6d5f27d42fcb562ec737bd2ba4e5b349d Mon Sep 17 00:00:00 2001 From: abc3 Date: Thu, 10 Nov 2022 21:39:32 +0100 Subject: [PATCH 001/603] realtime protocol messages --- apps/docs/docs/guides/realtime/protocol.mdx | 107 ++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 apps/docs/docs/guides/realtime/protocol.mdx diff --git a/apps/docs/docs/guides/realtime/protocol.mdx b/apps/docs/docs/guides/realtime/protocol.mdx new file mode 100644 index 00000000000..7818171a8d1 --- /dev/null +++ b/apps/docs/docs/guides/realtime/protocol.mdx @@ -0,0 +1,107 @@ +--- +id: protocol +title: Realtime Protocol +description: Understanding Realtime Protocol +sidebar_label: Protocol +--- + + +## Connection + +In the initial message, the client sends the server a configuration of features that wants to use. + +```json +{ + "topic":$TOPIC, + "event":"phx_join", + "payload":{ + "config":{ + "broadcast":{ + "self":true + }, + "presence":{ + "key":"" + }, + "postgres_changes":[ + { + "event":$EVENT, + "schema":$SCHEMA, + "table":$TABLE, + "filter":$FILTER + } + ] + } + }, + "ref":$REF_NUMBER +} +``` + +In response, the server sends the Postgres CDC configuration with a unique id. By this id, the client should route incoming changes to the appropriate callback. + +```json +{ + "event":"phx_reply", + "payload":{ + "response":{ + "postgres_changes":[ + { + "id":13399220, + "event":"*", + "filter":$FILTER, + "schema":$SCHEMA, + "table":$TABLE + } + ] + }, + "status":"ok" + }, + "ref":$REF_NUMBER, + "topic":$TOPIC +} +``` + +## System message + +Currently, a system message uses only to inform about the status of the Postgres CDC subscription. The `payload.status` indicates if the subscription success or not. The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. + +```json +{ + "event":"system", + "payload":{ + "channel":"any", + "extension":"postgres_changes", + "message":$MESSAGE, + "status":"ok" | "error" + }, + "ref":null, + "topic":$TOPIC +} +``` + +## Heartbeat + +The heartbeat message should be sent every 60 seconds to avoid a connection timeout. + +```json +{ + "topic":"phoenix", + "event":"heartbeat", + "payload":{}, + "ref":$REF_NUMBER +} +``` + +## Access Token + +To update the access token need to send to the server the following message and specify a new token in the `payload.access_token` value. + +```json +{ + "topic":$TOPIC, + "event":"access_token", + "payload":{ + "access_token":$NEW_JWT_TOKEN + }, + "ref":$REF_NUMBER +} +``` From 962a869786d7e09c699333a302688dc15afe8334 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Sun, 27 Nov 2022 19:43:52 +0100 Subject: [PATCH 002/603] Moves the protocol to the reference docs --- apps/docs/docs/guides/realtime/protocol.mdx | 107 ----------------- .../docs/reference/javascript/protocol.mdx | 109 ++++++++++++++++++ apps/reference/nav/supabase_js_sidebars.js | 1 + 3 files changed, 110 insertions(+), 107 deletions(-) delete mode 100644 apps/docs/docs/guides/realtime/protocol.mdx create mode 100644 apps/docs/docs/reference/javascript/protocol.mdx diff --git a/apps/docs/docs/guides/realtime/protocol.mdx b/apps/docs/docs/guides/realtime/protocol.mdx deleted file mode 100644 index 7818171a8d1..00000000000 --- a/apps/docs/docs/guides/realtime/protocol.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -id: protocol -title: Realtime Protocol -description: Understanding Realtime Protocol -sidebar_label: Protocol ---- - - -## Connection - -In the initial message, the client sends the server a configuration of features that wants to use. - -```json -{ - "topic":$TOPIC, - "event":"phx_join", - "payload":{ - "config":{ - "broadcast":{ - "self":true - }, - "presence":{ - "key":"" - }, - "postgres_changes":[ - { - "event":$EVENT, - "schema":$SCHEMA, - "table":$TABLE, - "filter":$FILTER - } - ] - } - }, - "ref":$REF_NUMBER -} -``` - -In response, the server sends the Postgres CDC configuration with a unique id. By this id, the client should route incoming changes to the appropriate callback. - -```json -{ - "event":"phx_reply", - "payload":{ - "response":{ - "postgres_changes":[ - { - "id":13399220, - "event":"*", - "filter":$FILTER, - "schema":$SCHEMA, - "table":$TABLE - } - ] - }, - "status":"ok" - }, - "ref":$REF_NUMBER, - "topic":$TOPIC -} -``` - -## System message - -Currently, a system message uses only to inform about the status of the Postgres CDC subscription. The `payload.status` indicates if the subscription success or not. The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. - -```json -{ - "event":"system", - "payload":{ - "channel":"any", - "extension":"postgres_changes", - "message":$MESSAGE, - "status":"ok" | "error" - }, - "ref":null, - "topic":$TOPIC -} -``` - -## Heartbeat - -The heartbeat message should be sent every 60 seconds to avoid a connection timeout. - -```json -{ - "topic":"phoenix", - "event":"heartbeat", - "payload":{}, - "ref":$REF_NUMBER -} -``` - -## Access Token - -To update the access token need to send to the server the following message and specify a new token in the `payload.access_token` value. - -```json -{ - "topic":$TOPIC, - "event":"access_token", - "payload":{ - "access_token":$NEW_JWT_TOKEN - }, - "ref":$REF_NUMBER -} -``` diff --git a/apps/docs/docs/reference/javascript/protocol.mdx b/apps/docs/docs/reference/javascript/protocol.mdx new file mode 100644 index 00000000000..bb61b86b9eb --- /dev/null +++ b/apps/docs/docs/reference/javascript/protocol.mdx @@ -0,0 +1,109 @@ +--- +id: protocol +title: Realtime Protocol +description: Understanding Realtime Protocol +sidebar_label: Protocol +--- + +The Realtime Protocal is a set of message formats that you send and receive from the Realtime server. + +## Connection + +In the initial message, the client sends a message specifying the features that wants to use (Broadcase, Presence, Postgres Changes). + +```json +{ + "event": "phx_join", + "topic": $TOPIC, + "payload": { + "config": { + "broadcast": { + "self": true + }, + "presence": { + "key": "" + }, + "postgres_changes": [ + { + "event": $EVENT, + "schema": $SCHEMA, + "table": $TABLE, + "filter": $FILTER + } + ] + } + }, + "ref": $REF_NUMBER +} +``` + +In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. + +```json +{ + "event": "phx_reply", + "payload": { + "response": { + "postgres_changes": [ + { + "id": 13399220, + "event": "*", + "filter": $FILTER, + "schema": $SCHEMA, + "table": $TABLE + } + ] + }, + "status": "ok" + }, + "ref": $REF_NUMBER, + "topic": $TOPIC +} +``` + +## System messages + +System message are used to inform a client about the status of the Postgres subscription. The `payload.status` indicates if the subscription successful or not. +The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. + +```json +{ + "event": "system", + "payload":{ + "channel": "any", + "extension": "postgres_changes", + "message": $MESSAGE, + "status": "ok" | "error" + }, + "ref":null, + "topic": $TOPIC +} +``` + +## Heartbeat + +The heartbeat message should be sent every 60 seconds to avoid a connection timeout. + +```json +{ + "topic": "phoenix", + "event": "heartbeat", + "payload": {}, + "ref": $REF_NUMBER +} +``` + +## Access Token + +To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. + +```json +{ + "topic": $TOPIC, + "event": "access_token", + "payload":{ + "access_token": $NEW_JWT_TOKEN + }, + "ref": $REF_NUMBER +} +``` diff --git a/apps/reference/nav/supabase_js_sidebars.js b/apps/reference/nav/supabase_js_sidebars.js index fc07909a7c7..539836cfd69 100644 --- a/apps/reference/nav/supabase_js_sidebars.js +++ b/apps/reference/nav/supabase_js_sidebars.js @@ -25,6 +25,7 @@ const sidebars = { 'installing', 'generated/initializing', 'typescript-support', + 'protocol', 'release-notes', 'upgrade-guide', ], From b3a58ef72539cd6f86f41ba15c127c5318e05e0f Mon Sep 17 00:00:00 2001 From: abc3 Date: Thu, 12 Jan 2023 16:04:13 +0100 Subject: [PATCH 003/603] describe types, add cdc and broadcast events --- .../docs/reference/javascript/protocol.mdx | 103 +++++++++++++----- 1 file changed, 73 insertions(+), 30 deletions(-) diff --git a/apps/docs/docs/reference/javascript/protocol.mdx b/apps/docs/docs/reference/javascript/protocol.mdx index bb61b86b9eb..84830f7be30 100644 --- a/apps/docs/docs/reference/javascript/protocol.mdx +++ b/apps/docs/docs/reference/javascript/protocol.mdx @@ -11,53 +11,53 @@ The Realtime Protocal is a set of message formats that you send and receive from In the initial message, the client sends a message specifying the features that wants to use (Broadcase, Presence, Postgres Changes). -```json +```typescript { "event": "phx_join", - "topic": $TOPIC, + "topic": string, "payload": { "config": { "broadcast": { - "self": true + "self": boolean }, "presence": { - "key": "" + "key": string }, "postgres_changes": [ { - "event": $EVENT, - "schema": $SCHEMA, - "table": $TABLE, - "filter": $FILTER + "event": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string, + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string } ] } }, - "ref": $REF_NUMBER + "ref": string } ``` In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. -```json +```typescript { "event": "phx_reply", + "topic": string, "payload": { "response": { "postgres_changes": [ { - "id": 13399220, - "event": "*", - "filter": $FILTER, - "schema": $SCHEMA, - "table": $TABLE + "id": number, + "event": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string, + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string } ] }, - "status": "ok" + "status": "ok" | "error" }, - "ref": $REF_NUMBER, - "topic": $TOPIC + "ref": string } ``` @@ -66,17 +66,17 @@ In response, the server sends the Postgres configuration with a unique ID. With System message are used to inform a client about the status of the Postgres subscription. The `payload.status` indicates if the subscription successful or not. The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. -```json +```typescript { "event": "system", + "topic": string, "payload":{ "channel": "any", "extension": "postgres_changes", - "message": $MESSAGE, + "message": "Subscribed to PostgreSQL" | "Subscribing to PostgreSQL failed", "status": "ok" | "error" }, - "ref":null, - "topic": $TOPIC + "ref": null, } ``` @@ -84,12 +84,12 @@ The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscrib The heartbeat message should be sent every 60 seconds to avoid a connection timeout. -```json +```typescript { + "event": "heartbeat", "topic": "phoenix", - "event": "heartbeat", "payload": {}, - "ref": $REF_NUMBER + "ref": string } ``` @@ -97,13 +97,56 @@ The heartbeat message should be sent every 60 seconds to avoid a connection time To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. -```json + +```typescript { - "topic": $TOPIC, - "event": "access_token", + "event": "access_token", + "topic": string, "payload":{ - "access_token": $NEW_JWT_TOKEN + "access_token": string }, - "ref": $REF_NUMBER + "ref": string +} +``` + +## Postgres CDC message + +Realtime sends a message with the following structure + +```typescript +{ + "event": "postgres_changes", + "topic": string, + "payload": { + "data": { + "columns": Array<{name: string, type: string}>, + "commit_timestamp": string, + "errors": null | string, + "old_record": {"id": number | string}, + "record": {[key: string]: boolean | number | string | null}, + "type": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string + }, + "ids": Array + }, + "ref": null +} +``` + +## Broadcast message + +Structure of the broadcast event + +```typescript +{ + "event": "broadcast", + "topic": string, + "payload": { + "event": string, + "payload": {[key: string]: boolean | number | string | null | undefined}, + "type": "broadcast" + }, + "ref": null } ``` From 131a8425029f397b7637b09173d8b11b677d7e3a Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Tue, 7 Feb 2023 16:08:53 +0800 Subject: [PATCH 004/603] feat: add ticket referral mechanism --- .../LaunchWeek/Ticket/TicketContainer.tsx | 1 + .../www/components/LaunchWeek/Ticket/form.tsx | 40 +---------- .../LaunchWeek/Ticket/hooks/use-conf-data.ts | 1 + .../LaunchWeek/Ticket/ticket-form.tsx | 63 +++++++++++++++--- .../components/LaunchWeek/Ticket/ticket.tsx | 43 +++++++----- apps/www/package.json | 2 +- .../pages/launch-week/tickets/[username].tsx | 11 +++- apps/www/pages/launch-week/tickets/index.tsx | 6 +- package-lock.json | 66 ++++++++++++++++++- 9 files changed, 158 insertions(+), 75 deletions(-) diff --git a/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx index f658037f136..678662425f2 100644 --- a/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx +++ b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx @@ -42,6 +42,7 @@ export default function Conf({ ticketNumber={userData.ticketNumber} sharePage={sharePage} golden={userData.golden} + referrals={userData.referrals ?? 0} /> )} diff --git a/apps/www/components/LaunchWeek/Ticket/form.tsx b/apps/www/components/LaunchWeek/Ticket/form.tsx index 59dbcb902ac..2ac809410a9 100644 --- a/apps/www/components/LaunchWeek/Ticket/form.tsx +++ b/apps/www/components/LaunchWeek/Ticket/form.tsx @@ -37,45 +37,7 @@ export default function Form({ sharePage, align = 'Center' }: Props) { const router = useRouter() const isCaptchaEnabled = false - useEffect(() => { - if (session?.user) { - document.body.classList.add('ticket-generated') - const username = session.user.user_metadata.user_name - const name = session.user.user_metadata.full_name - const email = session.user.email - supabase - .from('lw6_tickets') - .upsert({ email, name, username }, { onConflict: 'email', ignoreDuplicates: false }) - .eq('email', email) - .select() - .single() - .then(async ({ error }) => { - if (error) return supabase.auth.signOut() - const { data } = await supabase - .from('lw6_tickets_golden') - .select('*') - .eq('username', username) - .single() - setUserData(data) - setFormState('default') - - // Prefetch GitHub avatar - new Image().src = `https://github.com/${username}.png` - - // Prefetch the twitter share URL to eagerly generate the page - fetch(`/launch-week/tickets/${username}`).catch((_) => {}) - // Prefetch ticket og image. - fetch( - `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent( - username ?? '' - )}` - ).catch((_) => {}) - - setPageState('ticket') - }) - } - }, [session]) - + // NOTE: this is not in use anymore as we're only allowing GitHub signups since LW7! async function register(email: string, token?: string): Promise { const { error } = await supabase!.from('lw6_tickets').insert({ email }) if (error) { diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts index 4855f4f9a56..574e23fca2e 100644 --- a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts +++ b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts @@ -9,6 +9,7 @@ export type UserData = { username?: string name?: string golden?: boolean + referrals?: number } type ConfDataContextType = { diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx index 8ded0d8cdf1..44ad832b3d8 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx @@ -1,4 +1,5 @@ -import { useState, useRef } from 'react' +import { useState, useRef, useEffect } from 'react' +import { useRouter } from 'next/router' // import { scrollTo } from '@lib/smooth-scroll'; import cn from 'classnames' // import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github' @@ -18,11 +19,53 @@ type Props = { } export default function Form({ defaultUsername = '', setTicketGenerationState }: Props) { - const [username] = useState(defaultUsername) + const [username, setUsername] = useState(defaultUsername) const [formState, setFormState] = useState('default') const [errorMsg] = useState('') - const { supabase } = useConfData() + const { supabase, session, setUserData, setPageState, userData } = useConfData() const formRef = useRef(null) + const router = useRouter() + + useEffect(() => { + if (session?.user && !userData.id) { + document.body.classList.add('ticket-generated') + const username = session.user.user_metadata.user_name + setUsername(username) + const name = session.user.user_metadata.full_name + const email = session.user.email + supabase + .from('lw7_tickets') + .insert({ email, name, username, referred_by: router.query?.referral ?? null }) + .eq('email', email) + .select() + .single() + .then(async ({ error }) => { + // If error because of duplicate email, ignore and proceed, otherwise sign out. + if (error && error?.code !== '23505') return supabase.auth.signOut() + const { data } = await supabase + .from('lw7_tickets_golden') + .select('*') + .eq('username', username) + .single() + setUserData(data) + setFormState('default') + + // Prefetch GitHub avatar + new Image().src = `https://github.com/${username}.png` + + // Prefetch the twitter share URL to eagerly generate the page + fetch(`/launch-week/tickets/${username}`).catch((_) => {}) + // Prefetch ticket og image. + fetch( + `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent( + username ?? '' + )}` + ).catch((_) => {}) + + setPageState('ticket') + }) + } + }, [session]) return formState === 'error' ? (
@@ -59,7 +102,11 @@ export default function Form({ defaultUsername = '', setTicketGenerationState }: await supabase.auth.signInWithOAuth({ provider: 'github', - options: { redirectTo: `${SITE_ORIGIN}/launch-week/tickets` }, + options: { + redirectTo: `${SITE_ORIGIN}/launch-week/tickets${ + userData.username ? '?referral=' + userData.username : '' + }`, + }, }) }} className="flex flex-col items-center xl:block" @@ -69,15 +116,15 @@ export default function Form({ defaultUsername = '', setTicketGenerationState }:
- {!username &&

Only public info will be used.

} + {!session &&

Only public info will be used.

} ) diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.tsx b/apps/www/components/LaunchWeek/Ticket/ticket.tsx index 4cc479bd591..c494a9652ed 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket.tsx @@ -20,10 +20,18 @@ type Props = { ticketNumber: UserData['ticketNumber'] name: UserData['name'] golden: UserData['golden'] + referrals: number sharePage?: boolean } -export default function Ticket({ username, name, ticketNumber, sharePage, golden }: Props) { +export default function Ticket({ + username, + name, + ticketNumber, + sharePage, + golden, + referrals, +}: Props) { const ticketRef = useRef(null) const [ticketGenerationState, setTicketGenerationState] = useState('default') @@ -70,7 +78,9 @@ export default function Ticket({ username, name, ticketNumber, sharePage, golden ) : golden ? (

Congratulations!

) : ( -

You have a ticket!

+

+ Claim your unique Ticket! +

)}

@@ -80,31 +90,28 @@ export default function Ticket({ username, name, ticketNumber, sharePage, golden

) : golden ? ( <> - You got a Golden ticket. This means you’re in, and you also won a Supabase goodie - bag. Tweet it to redeem your swag pack! + You got a Golden ticket. This means you’re in, and you also won a Supabase sticker + pack! ) : username ? ( - <>Here is your unique ticket image for bragging on socials! + <> +

Here is your unique ticket image for bragging on socials!

+

Referrals: {referrals}

+ ) : ( <> - This means you're in. Generate a unique ticket image with your GitHub profile, cause - a few of the lucky attendees will get a limited edition Supabase goodie bag. Make - sure you don't skip your chance. + Generate a unique ticket image with your GitHub profile, cause a few of the lucky + attendees will get a limited edition Supabase goodie bag. Make sure you don't skip + your chance. )}

- {!sharePage ? ( - <> - - - ) : ( -
- )} +
diff --git a/apps/www/package.json b/apps/www/package.json index 69108a1066b..5415fb31d4f 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -19,7 +19,7 @@ "@next/bundle-analyzer": "^13.0.0", "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", - "@supabase/supabase-js": "^2.0.0", + "@supabase/supabase-js": "^2.5.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx index 7bd706c81bd..45b3b027df3 100644 --- a/apps/www/pages/launch-week/tickets/[username].tsx +++ b/apps/www/pages/launch-week/tickets/[username].tsx @@ -15,6 +15,7 @@ type Props = { name: string | null ticketNumber: number | null golden: boolean + referrals: number } const supabaseAdmin = createClient( @@ -23,7 +24,7 @@ const supabaseAdmin = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2Njk3MjcwMTIsImV4cCI6MTk4NTMwMzAxMn0.SZLqryz_-stF8dgzeVXmzZWPOqdOrBwqJROlFES8v3I' ) -export default function TicketShare({ username, ticketNumber, name, golden }: Props) { +export default function TicketShare({ username, ticketNumber, name, golden, referrals }: Props) { const { isDarkMode } = useTheme() const [supabase] = useState(() => @@ -76,6 +77,7 @@ export default function TicketShare({ username, ticketNumber, name, golden }: Pr name: name || '', ticketNumber, golden, + referrals, }} sharePage /> @@ -90,16 +92,18 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { let name: string | null | undefined let ticketNumber: number | null | undefined let golden = false + let referrals = 0 if (username) { const { data: user } = await supabaseAdmin! - .from('lw6_tickets_golden') - .select('name, ticketNumber, golden') + .from('lw7_tickets_golden') + .select('name, ticketNumber, golden, referrals') .eq('username', username) .single() name = user?.name ticketNumber = user?.ticketNumber golden = user?.golden ?? false + referrals = user?.referrals ?? 0 } return { props: { @@ -108,6 +112,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { name: ticketNumber ? name || username || null : null, ticketNumber: ticketNumber || SAMPLE_TICKET_NUMBER, golden, + referrals, }, revalidate: 5, } diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx index fba61ea897e..def343e0796 100644 --- a/apps/www/pages/launch-week/tickets/index.tsx +++ b/apps/www/pages/launch-week/tickets/index.tsx @@ -26,8 +26,8 @@ export default function TicketHome() { } useEffect(() => { - supabase.auth.onAuthStateChange((event, session) => { - setSession(session) + supabase.auth.onAuthStateChange((event, supaSession) => { + setSession(supaSession) }) }, []) @@ -68,7 +68,7 @@ export default function TicketHome() { supabase={supabase} session={session} defaultUserData={defaultUserData} - defaultPageState={query.ticketNumber ? 'ticket' : 'registration'} + defaultPageState="ticket" /> diff --git a/package-lock.json b/package-lock.json index 5bb23e0dd7c..4f831603087 100644 --- a/package-lock.json +++ b/package-lock.json @@ -883,7 +883,7 @@ "@next/bundle-analyzer": "^13.0.0", "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", - "@supabase/supabase-js": "^2.0.0", + "@supabase/supabase-js": "^2.5.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", @@ -962,6 +962,36 @@ "node": ">= 8.3" } }, + "apps/www/node_modules/@supabase/gotrue-js": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.11.0.tgz", + "integrity": "sha512-yo3yExoTqpJH4YpdI5PDjZ1YLOj3wURppimfsV0ep5uxDY/lE1uOhiMCPnhy59DbFAKG7bjnhJ1L7sk+B2os3w==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "apps/www/node_modules/@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "dependencies": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "apps/www/node_modules/@supabase/supabase-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.7.1.tgz", + "integrity": "sha512-Q/e+JAluZEvy7D4ul3aAs3aOiKkGvHlZULy6wjchWQyU9YlJKZLr6VPYcwUeitcnRKZi4al5iTS55LgdJFfqIA==", + "dependencies": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.10.2", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, "apps/www/node_modules/@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", @@ -46190,7 +46220,7 @@ "@sinclair/typebox": "^0.25.1", "pg": "^8.7.1", "pg-format": "^1.0.4", - "pgsql-parser": "^13.3.0", + "pgsql-parser": "13.4.0", "postgres-array": "^3.0.1", "prettier": "^2.6.0", "prettier-plugin-sql": "^0.12.1" @@ -68989,7 +69019,7 @@ "@next/bundle-analyzer": "^13.0.0", "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", - "@supabase/supabase-js": "^2.0.0", + "@supabase/supabase-js": "^2.5.0", "@tailwindcss/typography": "^0.5.7", "@types/classnames": "^2.3.1", "@types/jest": "^25.2.2", @@ -69062,6 +69092,36 @@ "chalk": "^3.0.0" } }, + "@supabase/gotrue-js": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.11.0.tgz", + "integrity": "sha512-yo3yExoTqpJH4YpdI5PDjZ1YLOj3wURppimfsV0ep5uxDY/lE1uOhiMCPnhy59DbFAKG7bjnhJ1L7sk+B2os3w==", + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "requires": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "@supabase/supabase-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.7.1.tgz", + "integrity": "sha512-Q/e+JAluZEvy7D4ul3aAs3aOiKkGvHlZULy6wjchWQyU9YlJKZLr6VPYcwUeitcnRKZi4al5iTS55LgdJFfqIA==", + "requires": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.10.2", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, "@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", From 6aadcdb2118e9335a4a62e5df7458429d6fdac35 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Tue, 7 Feb 2023 16:25:47 +0800 Subject: [PATCH 005/603] chore: roll back supabase-js update. --- apps/www/package.json | 2 +- package-lock.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/package.json b/apps/www/package.json index 5415fb31d4f..69108a1066b 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -19,7 +19,7 @@ "@next/bundle-analyzer": "^13.0.0", "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", - "@supabase/supabase-js": "^2.5.0", + "@supabase/supabase-js": "^2.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", diff --git a/package-lock.json b/package-lock.json index 4f831603087..9f8b0c07c27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -883,7 +883,7 @@ "@next/bundle-analyzer": "^13.0.0", "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", - "@supabase/supabase-js": "^2.5.0", + "@supabase/supabase-js": "^2.0.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", @@ -69019,7 +69019,7 @@ "@next/bundle-analyzer": "^13.0.0", "@next/mdx": "^12.3.2", "@supabase/auth-ui-react": "^0.1.6", - "@supabase/supabase-js": "^2.5.0", + "@supabase/supabase-js": "^2.0.0", "@tailwindcss/typography": "^0.5.7", "@types/classnames": "^2.3.1", "@types/jest": "^25.2.2", From 6c51c52ff6e3dfa299281bb438b5e970a2e73d3e Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Tue, 7 Feb 2023 17:44:07 +0800 Subject: [PATCH 006/603] chore: roll back package-lock --- package-lock.json | 62 +---------------------------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ae802026c7..78869d57003 100644 --- a/package-lock.json +++ b/package-lock.json @@ -962,36 +962,6 @@ "node": ">= 8.3" } }, - "apps/www/node_modules/@supabase/gotrue-js": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.11.0.tgz", - "integrity": "sha512-yo3yExoTqpJH4YpdI5PDjZ1YLOj3wURppimfsV0ep5uxDY/lE1uOhiMCPnhy59DbFAKG7bjnhJ1L7sk+B2os3w==", - "dependencies": { - "cross-fetch": "^3.1.5" - } - }, - "apps/www/node_modules/@supabase/realtime-js": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", - "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", - "dependencies": { - "@types/phoenix": "^1.5.4", - "websocket": "^1.0.34" - } - }, - "apps/www/node_modules/@supabase/supabase-js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.7.1.tgz", - "integrity": "sha512-Q/e+JAluZEvy7D4ul3aAs3aOiKkGvHlZULy6wjchWQyU9YlJKZLr6VPYcwUeitcnRKZi4al5iTS55LgdJFfqIA==", - "dependencies": { - "@supabase/functions-js": "^2.0.0", - "@supabase/gotrue-js": "^2.10.2", - "@supabase/postgrest-js": "^1.1.1", - "@supabase/realtime-js": "^2.4.0", - "@supabase/storage-js": "^2.1.0", - "cross-fetch": "^3.1.5" - } - }, "apps/www/node_modules/@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", @@ -46211,7 +46181,7 @@ "@sinclair/typebox": "^0.25.1", "pg": "^8.7.1", "pg-format": "^1.0.4", - "pgsql-parser": "13.4.0", + "pgsql-parser": "^13.3.0", "postgres-array": "^3.0.1", "prettier": "^2.6.0", "prettier-plugin-sql": "^0.12.1" @@ -69076,36 +69046,6 @@ "chalk": "^3.0.0" } }, - "@supabase/gotrue-js": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.11.0.tgz", - "integrity": "sha512-yo3yExoTqpJH4YpdI5PDjZ1YLOj3wURppimfsV0ep5uxDY/lE1uOhiMCPnhy59DbFAKG7bjnhJ1L7sk+B2os3w==", - "requires": { - "cross-fetch": "^3.1.5" - } - }, - "@supabase/realtime-js": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", - "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", - "requires": { - "@types/phoenix": "^1.5.4", - "websocket": "^1.0.34" - } - }, - "@supabase/supabase-js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.7.1.tgz", - "integrity": "sha512-Q/e+JAluZEvy7D4ul3aAs3aOiKkGvHlZULy6wjchWQyU9YlJKZLr6VPYcwUeitcnRKZi4al5iTS55LgdJFfqIA==", - "requires": { - "@supabase/functions-js": "^2.0.0", - "@supabase/gotrue-js": "^2.10.2", - "@supabase/postgrest-js": "^1.1.1", - "@supabase/realtime-js": "^2.4.0", - "@supabase/storage-js": "^2.1.0", - "cross-fetch": "^3.1.5" - } - }, "@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", From 9dab245f523ff018ced279631caf61d283a0f91e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 8 Feb 2023 20:30:23 +0800 Subject: [PATCH 007/603] Revert 94d9a3a2cc43752797e676c52b41c1e9044fcc90 --- .../interfaces/Auth/Users/AddUserDropdown.tsx | 108 ++++++++++++ .../interfaces/Auth/Users/CreateUserModal.tsx | 164 ++++++++++++++++++ .../interfaces/Auth/Users/InviteUserModal.tsx | 47 ++--- .../interfaces/Auth/Users/Users.tsx | 21 +-- studio/data/auth/user-create-mutation.ts | 47 +++++ 5 files changed, 339 insertions(+), 48 deletions(-) create mode 100644 studio/components/interfaces/Auth/Users/AddUserDropdown.tsx create mode 100644 studio/components/interfaces/Auth/Users/CreateUserModal.tsx create mode 100644 studio/data/auth/user-create-mutation.ts diff --git a/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx new file mode 100644 index 00000000000..29505af9dab --- /dev/null +++ b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -0,0 +1,108 @@ +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { checkPermissions } from 'hooks' +import { IS_PLATFORM } from 'lib/constants' +import { useState } from 'react' +import semver from 'semver' +import { Button, Dropdown, IconMail, IconPlus, IconUserPlus } from 'ui' +import CreateUserModal from './CreateUserModal' +import InviteUserModal from './InviteUserModal' + +export type AddUserDropdownProps = { + projectKpsVersion?: string +} + +const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => { + const inviteEnabled = IS_PLATFORM + ? semver.gte( + // @ts-ignore + semver.coerce(projectKpsVersion ?? 'kps-v2.5.4'), + semver.coerce('kps-v2.5.3') + ) + : true + + const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') + const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + + const [inviteVisible, setInviteVisible] = useState(false) + const [createVisible, setCreateVisible] = useState(false) + + return ( + <> + + {inviteEnabled && ( + + + } + disabled={!canInviteUsers} + onClick={() => setInviteVisible(true)} + > + Send Invitation + + + {!canInviteUsers && ( + + +
+ + You need additional permissions to invite users + +
+
+ )} +
+ )} + + + + } + disabled={!canCreateUsers} + onClick={() => setCreateVisible(true)} + > + Create New User + + + + {!canCreateUsers && ( + + +
+ + You need additional permissions to create users + +
+
+ )} +
+ + } + > + +
+ + {inviteEnabled && } + + + ) +} + +export default AddUserDropdown diff --git a/studio/components/interfaces/Auth/Users/CreateUserModal.tsx b/studio/components/interfaces/Auth/Users/CreateUserModal.tsx new file mode 100644 index 00000000000..94698aca740 --- /dev/null +++ b/studio/components/interfaces/Auth/Users/CreateUserModal.tsx @@ -0,0 +1,164 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { observer } from 'mobx-react-lite' +import { useContext } from 'react' +import { Button, Checkbox, Form, IconLock, IconMail, Input, Loading, Modal } from 'ui' + +import { useUserCreateMutation } from 'data/auth/user-create-mutation' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { checkPermissions, useStore } from 'hooks' +import { PageContext } from 'pages/project/[ref]/auth/users' + +export type CreateUserModalProps = { + visible: boolean + setVisible: (visible: boolean) => void +} + +const CreateUserModal = ({ visible, setVisible }: CreateUserModalProps) => { + const { ui } = useStore() + const PageState: any = useContext(PageContext) + const projectRef = PageState.projectRef + + const { data, isLoading, isSuccess } = useProjectApiQuery({ projectRef }, { enabled: visible }) + + const handleToggle = () => setVisible(!visible) + const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + + const validate = (values: any) => { + const errors: any = {} + const emailValidateRegex = + /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/ + + if (values.email.length === 0) { + errors.email = 'Please enter a valid email' + } else if (!emailValidateRegex.test(values.email)) { + errors.email = `${values.email} is an invalid email` + } + + if (!values.password?.trim()) { + errors.password = 'Please enter a password' + } + + return errors + } + + const { mutateAsync: createUser } = useUserCreateMutation({ + async onSuccess() { + await PageState.fetchData(1) + }, + }) + + const onCreateUser = async (values: any, { setSubmitting }: any) => { + if (!isSuccess) { + ui.setNotification({ + category: 'error', + message: `Failed to create user: Error loading project config`, + }) + + return + } + + const { protocol, endpoint, serviceApiKey } = data.autoApiService + + setSubmitting(true) + + try { + await createUser({ + endpoint, + protocol, + serviceApiKey, + user: values, + }) + + ui.setNotification({ + category: 'success', + message: `Created user: ${values.email}`, + }) + + setVisible(false) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to create user: ${error.message}`, + }) + } + + setSubmitting(false) + } + + return ( + + + {({ isSubmitting }: { isSubmitting: boolean }) => ( + +
+ +
+ } + type="email" + name="email" + placeholder="user@example.com" + disabled={isSubmitting || isLoading} + /> + + } + disabled={isSubmitting || isLoading} + autoComplete="new-password" + /> + + +
+
+ + + + +
+
+ )} + +
+ ) +} + +export default observer(CreateUserModal) diff --git a/studio/components/interfaces/Auth/Users/InviteUserModal.tsx b/studio/components/interfaces/Auth/Users/InviteUserModal.tsx index 5f5898714cf..2ec628eb448 100644 --- a/studio/components/interfaces/Auth/Users/InviteUserModal.tsx +++ b/studio/components/interfaces/Auth/Users/InviteUserModal.tsx @@ -1,18 +1,21 @@ -import { useContext, useState } from 'react' -import { observer } from 'mobx-react-lite' -import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, Modal, Input, IconPlus, IconMail, Form } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { observer } from 'mobx-react-lite' +import { useContext } from 'react' +import { Button, Form, IconMail, Input, Modal } from 'ui' -import { useStore, checkPermissions } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { PageContext } from 'pages/project/[ref]/auth/users' -const InviteUserModal = () => { +export type InviteUserModalProps = { + visible: boolean + setVisible: (visible: boolean) => void +} + +const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => { const { ui } = useStore() const PageState: any = useContext(PageContext) - const [visible, setVisible] = useState(false) const handleToggle = () => setVisible(!visible) const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') @@ -31,7 +34,7 @@ const InviteUserModal = () => { return errors } - const onInviteuser = async (values: any, { setSubmitting }: any) => { + const onInviteUser = async (values: any, { setSubmitting }: any) => { setSubmitting(true) const response = await post(`${API_URL}/auth/${PageState.projectRef}/invite`, { @@ -56,28 +59,6 @@ const InviteUserModal = () => { return (
- - - - - {!canInviteUsers && ( - - -
- - You need additional permissions to invite users - -
-
- )} -
{ onCancel={handleToggle} >
{({ isSubmitting }: { isSubmitting: boolean }) => (
{ placeholder="User email" /> +
-
+
- {inviteEnabled && } + +
diff --git a/studio/data/auth/user-create-mutation.ts b/studio/data/auth/user-create-mutation.ts new file mode 100644 index 00000000000..8b41d9612a3 --- /dev/null +++ b/studio/data/auth/user-create-mutation.ts @@ -0,0 +1,47 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' + +export type UserCreateVariables = { + protocol: string + endpoint: string + serviceApiKey: string + user: { + email: string + password: string + autoConfirmUser: string + } +} + +export async function createUser({ protocol, endpoint, serviceApiKey, user }: UserCreateVariables) { + const response = await post( + `${protocol}://${endpoint}/auth/v1/admin/users`, + { + email: user.email, + password: user.password, + email_confirm: user.autoConfirmUser, + }, + { + headers: { + apikey: serviceApiKey, + Authorization: `Bearer ${serviceApiKey}`, + }, + credentials: 'omit', + } + ) + if (response.error) { + throw response.error + } + + return response +} + +type UserCreateData = Awaited> + +export const useUserCreateMutation = ( + options: Omit, 'mutationFn'> = {} +) => { + return useMutation( + (vars) => createUser(vars), + options + ) +} From 9fe0e0b20bfe6dc2493aa06dbaaa9798865a003f Mon Sep 17 00:00:00 2001 From: abc3 Date: Mon, 13 Feb 2023 18:56:07 +0100 Subject: [PATCH 008/603] remove the section --- apps/reference/nav/supabase_js_sidebars.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/reference/nav/supabase_js_sidebars.js b/apps/reference/nav/supabase_js_sidebars.js index 539836cfd69..fc07909a7c7 100644 --- a/apps/reference/nav/supabase_js_sidebars.js +++ b/apps/reference/nav/supabase_js_sidebars.js @@ -25,7 +25,6 @@ const sidebars = { 'installing', 'generated/initializing', 'typescript-support', - 'protocol', 'release-notes', 'upgrade-guide', ], From c2e86f7cefb321a101938dcb538e3444c1b3ebf7 Mon Sep 17 00:00:00 2001 From: abc3 Date: Mon, 13 Feb 2023 19:11:25 +0100 Subject: [PATCH 009/603] move the description to another chapter --- .../NavigationMenu.constants.ts | 1 + apps/docs/pages/guides/realtime/protocol.mdx | 153 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 apps/docs/pages/guides/realtime/protocol.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 08cea106b45..08fe63bb175 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -511,6 +511,7 @@ export const realtime = { items: [ { name: 'Overview', url: '/guides/realtime', items: [] }, { name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] }, + { name: 'Protocol', url: '/guides/realtime/protocol', items: [] }, { name: 'Channels', url: undefined, diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx new file mode 100644 index 00000000000..3d211079aa8 --- /dev/null +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -0,0 +1,153 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'protocol', + title: 'Realtime Protocol', + description: "Understanding Realtime Protocol", +} + +The Realtime Protocal is a set of message formats that you send and receive from the Realtime server. + +## Connection + +In the initial message, the client sends a message specifying the features that wants to use (Broadcase, Presence, Postgres Changes). + +```typescript +{ + "event": "phx_join", + "topic": string, + "payload": { + "config": { + "broadcast": { + "self": boolean + }, + "presence": { + "key": string + }, + "postgres_changes": [ + { + "event": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string, + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string + } + ] + } + }, + "ref": string +} +``` + +In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. + +```typescript +{ + "event": "phx_reply", + "topic": string, + "payload": { + "response": { + "postgres_changes": [ + { + "id": number, + "event": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string, + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string + } + ] + }, + "status": "ok" | "error" + }, + "ref": string +} +``` + +## System messages + +System message are used to inform a client about the status of the Postgres subscription. The `payload.status` indicates if the subscription successful or not. +The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. + +```typescript +{ + "event": "system", + "topic": string, + "payload":{ + "channel": "any", + "extension": "postgres_changes", + "message": "Subscribed to PostgreSQL" | "Subscribing to PostgreSQL failed", + "status": "ok" | "error" + }, + "ref": null, +} +``` + +## Heartbeat + +The heartbeat message should be sent every 60 seconds to avoid a connection timeout. + +```typescript +{ + "event": "heartbeat", + "topic": "phoenix", + "payload": {}, + "ref": string +} +``` + +## Access Token + +To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. + + +```typescript +{ + "event": "access_token", + "topic": string, + "payload":{ + "access_token": string + }, + "ref": string +} +``` + +## Postgres CDC message + +Realtime sends a message with the following structure + +```typescript +{ + "event": "postgres_changes", + "topic": string, + "payload": { + "data": { + "columns": Array<{name: string, type: string}>, + "commit_timestamp": string, + "errors": null | string, + "old_record": {"id": number | string}, + "record": {[key: string]: boolean | number | string | null}, + "type": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string + }, + "ids": Array + }, + "ref": null +} +``` + +## Broadcast message + +Structure of the broadcast event + +```typescript +{ + "event": "broadcast", + "topic": string, + "payload": { + "event": string, + "payload": {[key: string]: boolean | number | string | null | undefined}, + "type": "broadcast" + }, + "ref": null +} +``` \ No newline at end of file From 8784c96e6e3d15dc8d239803d67e26dd000d63fb Mon Sep 17 00:00:00 2001 From: abc3 Date: Mon, 13 Feb 2023 19:28:46 +0100 Subject: [PATCH 010/603] delete the old description --- .../docs/reference/javascript/protocol.mdx | 152 ------------------ 1 file changed, 152 deletions(-) delete mode 100644 apps/docs/docs/reference/javascript/protocol.mdx diff --git a/apps/docs/docs/reference/javascript/protocol.mdx b/apps/docs/docs/reference/javascript/protocol.mdx deleted file mode 100644 index 84830f7be30..00000000000 --- a/apps/docs/docs/reference/javascript/protocol.mdx +++ /dev/null @@ -1,152 +0,0 @@ ---- -id: protocol -title: Realtime Protocol -description: Understanding Realtime Protocol -sidebar_label: Protocol ---- - -The Realtime Protocal is a set of message formats that you send and receive from the Realtime server. - -## Connection - -In the initial message, the client sends a message specifying the features that wants to use (Broadcase, Presence, Postgres Changes). - -```typescript -{ - "event": "phx_join", - "topic": string, - "payload": { - "config": { - "broadcast": { - "self": boolean - }, - "presence": { - "key": string - }, - "postgres_changes": [ - { - "event": "*" | "INSERT" | "UPDATE" | "DELETE", - "schema": string, - "table": string, - "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string - } - ] - } - }, - "ref": string -} -``` - -In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. - -```typescript -{ - "event": "phx_reply", - "topic": string, - "payload": { - "response": { - "postgres_changes": [ - { - "id": number, - "event": "*" | "INSERT" | "UPDATE" | "DELETE", - "schema": string, - "table": string, - "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string - } - ] - }, - "status": "ok" | "error" - }, - "ref": string -} -``` - -## System messages - -System message are used to inform a client about the status of the Postgres subscription. The `payload.status` indicates if the subscription successful or not. -The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. - -```typescript -{ - "event": "system", - "topic": string, - "payload":{ - "channel": "any", - "extension": "postgres_changes", - "message": "Subscribed to PostgreSQL" | "Subscribing to PostgreSQL failed", - "status": "ok" | "error" - }, - "ref": null, -} -``` - -## Heartbeat - -The heartbeat message should be sent every 60 seconds to avoid a connection timeout. - -```typescript -{ - "event": "heartbeat", - "topic": "phoenix", - "payload": {}, - "ref": string -} -``` - -## Access Token - -To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. - - -```typescript -{ - "event": "access_token", - "topic": string, - "payload":{ - "access_token": string - }, - "ref": string -} -``` - -## Postgres CDC message - -Realtime sends a message with the following structure - -```typescript -{ - "event": "postgres_changes", - "topic": string, - "payload": { - "data": { - "columns": Array<{name: string, type: string}>, - "commit_timestamp": string, - "errors": null | string, - "old_record": {"id": number | string}, - "record": {[key: string]: boolean | number | string | null}, - "type": "*" | "INSERT" | "UPDATE" | "DELETE", - "schema": string, - "table": string - }, - "ids": Array - }, - "ref": null -} -``` - -## Broadcast message - -Structure of the broadcast event - -```typescript -{ - "event": "broadcast", - "topic": string, - "payload": { - "event": string, - "payload": {[key: string]: boolean | number | string | null | undefined}, - "type": "broadcast" - }, - "ref": null -} -``` From f1c2dddafb2a1c1f3b83f63abded5dc8aebaa0cb Mon Sep 17 00:00:00 2001 From: abc3 Date: Mon, 13 Feb 2023 20:27:05 +0100 Subject: [PATCH 011/603] add presence description --- apps/docs/pages/guides/realtime/protocol.mdx | 60 ++++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx index 3d211079aa8..2812ddc0165 100644 --- a/apps/docs/pages/guides/realtime/protocol.mdx +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -3,10 +3,14 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'protocol', title: 'Realtime Protocol', - description: "Understanding Realtime Protocol", + description: 'Understanding Realtime Protocol', } -The Realtime Protocal is a set of message formats that you send and receive from the Realtime server. +## Introduction + +The Realtime Protocol is a set of message formats used for communication between a Realtime client and server. These messages are used to initiate a connection, update access tokens, receive system status updates, and receive real-time updates from the Postgres database. + +The Realtime Protocol is a set of message formats that you send and receive from the Realtime server. ## Connection @@ -70,7 +74,7 @@ The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscrib ```typescript { "event": "system", - "topic": string, + "topic": string, "payload":{ "channel": "any", "extension": "postgres_changes", @@ -87,7 +91,7 @@ The heartbeat message should be sent every 60 seconds to avoid a connection time ```typescript { - "event": "heartbeat", + "event": "heartbeat", "topic": "phoenix", "payload": {}, "ref": string @@ -98,10 +102,9 @@ The heartbeat message should be sent every 60 seconds to avoid a connection time To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. - ```typescript { - "event": "access_token", + "event": "access_token", "topic": string, "payload":{ "access_token": string @@ -117,14 +120,14 @@ Realtime sends a message with the following structure ```typescript { "event": "postgres_changes", - "topic": string, + "topic": string, "payload": { - "data": { + "data": { "columns": Array<{name: string, type: string}>, "commit_timestamp": string, "errors": null | string, - "old_record": {"id": number | string}, - "record": {[key: string]: boolean | number | string | null}, + "old_record": {"id": number | string}, + "record": {[key: string]: boolean | number | string | null}, "type": "*" | "INSERT" | "UPDATE" | "DELETE", "schema": string, "table": string @@ -150,4 +153,39 @@ Structure of the broadcast event }, "ref": null } -``` \ No newline at end of file +``` + +## Presence message + +The Presence events allow clients to monitor the online status of other clients in real-time. + +### State Update + +After joining, the server sends a `presence_state` message to a client with presence information. The payload field contains keys in UUID format, where each key represents a client and its value is a JSON object containing information about that client. + +```typescript +{ + "event": "presence_state", + "topic": string, + "payload": { + [key: string]: {metas: Array<{phx_ref: string, name: string, t: float}>} + }, + "ref": null +} +``` + +### Diff Update + +After a change to the presence state, such as a client joining or leaving, the server sends a presence_diff message to update the client's view of the presence state. The payload field contains two keys, `joins` and `leaves`, which represent clients that have joined and left, respectively. The values associated with each key are UUIDs of the clients. + +```typescript +{ + "event": "presence_diff", + "topic": string, + "payload": { + "joins": {metas: Array<{phx_ref: string, name: string, t: float}>}, + "leaves": {metas: Array<{phx_ref: string, name: string, t: float}>} + }, + "ref": null +} +``` From 8eb8238fb498c42d0006ba323e525e0b6b6c5ac7 Mon Sep 17 00:00:00 2001 From: abc3 Date: Tue, 14 Feb 2023 09:46:14 +0100 Subject: [PATCH 012/603] format update --- apps/docs/pages/guides/realtime/protocol.mdx | 26 +++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx index 2812ddc0165..5232a354325 100644 --- a/apps/docs/pages/guides/realtime/protocol.mdx +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -6,17 +6,13 @@ export const meta = { description: 'Understanding Realtime Protocol', } -## Introduction - The Realtime Protocol is a set of message formats used for communication between a Realtime client and server. These messages are used to initiate a connection, update access tokens, receive system status updates, and receive real-time updates from the Postgres database. -The Realtime Protocol is a set of message formats that you send and receive from the Realtime server. - ## Connection -In the initial message, the client sends a message specifying the features that wants to use (Broadcase, Presence, Postgres Changes). +In the initial message, the client sends a message specifying the features they want to use (Broadcase, Presence, Postgres Changes). -```typescript +```ts { "event": "phx_join", "topic": string, @@ -44,7 +40,7 @@ In the initial message, the client sends a message specifying the features that In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. -```typescript +```ts { "event": "phx_reply", "topic": string, @@ -71,12 +67,12 @@ In response, the server sends the Postgres configuration with a unique ID. With System message are used to inform a client about the status of the Postgres subscription. The `payload.status` indicates if the subscription successful or not. The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. -```typescript +```ts { "event": "system", "topic": string, "payload":{ - "channel": "any", + "channel": string, "extension": "postgres_changes", "message": "Subscribed to PostgreSQL" | "Subscribing to PostgreSQL failed", "status": "ok" | "error" @@ -89,7 +85,7 @@ The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscrib The heartbeat message should be sent every 60 seconds to avoid a connection timeout. -```typescript +```ts { "event": "heartbeat", "topic": "phoenix", @@ -102,7 +98,7 @@ The heartbeat message should be sent every 60 seconds to avoid a connection time To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. -```typescript +```ts { "event": "access_token", "topic": string, @@ -117,7 +113,7 @@ To update the access token, you need to send to the server a message specifying Realtime sends a message with the following structure -```typescript +```ts { "event": "postgres_changes", "topic": string, @@ -142,7 +138,7 @@ Realtime sends a message with the following structure Structure of the broadcast event -```typescript +```ts { "event": "broadcast", "topic": string, @@ -163,7 +159,7 @@ The Presence events allow clients to monitor the online status of other clients After joining, the server sends a `presence_state` message to a client with presence information. The payload field contains keys in UUID format, where each key represents a client and its value is a JSON object containing information about that client. -```typescript +```ts { "event": "presence_state", "topic": string, @@ -178,7 +174,7 @@ After joining, the server sends a `presence_state` message to a client with pres After a change to the presence state, such as a client joining or leaving, the server sends a presence_diff message to update the client's view of the presence state. The payload field contains two keys, `joins` and `leaves`, which represent clients that have joined and left, respectively. The values associated with each key are UUIDs of the clients. -```typescript +```ts { "event": "presence_diff", "topic": string, From feb2b619fe9a2200d9b3e9635dbe068fadd7e504 Mon Sep 17 00:00:00 2001 From: abc3 Date: Tue, 14 Feb 2023 09:54:00 +0100 Subject: [PATCH 013/603] fix theme --- apps/docs/pages/guides/realtime/protocol.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx index 5232a354325..d9b34004d2b 100644 --- a/apps/docs/pages/guides/realtime/protocol.mdx +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -185,3 +185,6 @@ After a change to the presence state, such as a client joining or leaving, the s "ref": null } ``` + +export const Page = ({ children }) => +export default Page From 88173de900815369101313552c242502d85e885e Mon Sep 17 00:00:00 2001 From: abc3 Date: Tue, 14 Feb 2023 10:15:10 +0100 Subject: [PATCH 014/603] add in filter --- .../Navigation/NavigationMenu/NavigationMenu.constants.ts | 2 +- apps/docs/pages/guides/realtime/protocol.mdx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 08fe63bb175..d568a0614fb 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -511,7 +511,7 @@ export const realtime = { items: [ { name: 'Overview', url: '/guides/realtime', items: [] }, { name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] }, - { name: 'Protocol', url: '/guides/realtime/protocol', items: [] }, + { name: 'Protocol Description', url: '/guides/realtime/protocol', items: [] }, { name: 'Channels', url: undefined, diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx index d9b34004d2b..de4b74d5b57 100644 --- a/apps/docs/pages/guides/realtime/protocol.mdx +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -6,7 +6,7 @@ export const meta = { description: 'Understanding Realtime Protocol', } -The Realtime Protocol is a set of message formats used for communication between a Realtime client and server. These messages are used to initiate a connection, update access tokens, receive system status updates, and receive real-time updates from the Postgres database. +The Realtime Protocol is a set of message formats used for communication over a WebSocket connection between a Realtime client and server. These messages are used to initiate a connection, update access tokens, receive system status updates, and receive real-time updates from the Postgres database. ## Connection @@ -29,7 +29,7 @@ In the initial message, the client sends a message specifying the features they "event": "*" | "INSERT" | "UPDATE" | "DELETE", "schema": string, "table": string, - "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" + '.' + string } ] } @@ -37,6 +37,7 @@ In the initial message, the client sends a message specifying the features they "ref": string } ``` +The `in` filter has the format `COLUMN_NAME=in.(value1, valu2, value3)` In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. @@ -52,7 +53,7 @@ In response, the server sends the Postgres configuration with a unique ID. With "event": "*" | "INSERT" | "UPDATE" | "DELETE", "schema": string, "table": string, - "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" + '.' + string + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" + '.' + string } ] }, From c90a3d982766df45fca62a979dbb04288cf06553 Mon Sep 17 00:00:00 2001 From: abc3 Date: Tue, 14 Feb 2023 10:18:37 +0100 Subject: [PATCH 015/603] fix format --- apps/docs/pages/guides/realtime/protocol.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx index de4b74d5b57..ab299434105 100644 --- a/apps/docs/pages/guides/realtime/protocol.mdx +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -37,6 +37,7 @@ In the initial message, the client sends a message specifying the features they "ref": string } ``` + The `in` filter has the format `COLUMN_NAME=in.(value1, valu2, value3)` In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. From 17ffa266605012b38696c5f224d3f7782e0a3dd5 Mon Sep 17 00:00:00 2001 From: abc3 Date: Tue, 14 Feb 2023 10:29:35 +0100 Subject: [PATCH 016/603] add note about filters format --- apps/docs/pages/guides/realtime/protocol.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx index ab299434105..9cd3cab4bc4 100644 --- a/apps/docs/pages/guides/realtime/protocol.mdx +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -38,7 +38,9 @@ In the initial message, the client sends a message specifying the features they } ``` -The `in` filter has the format `COLUMN_NAME=in.(value1, valu2, value3)` + +The `in` filter has the format `COLUMN_NAME=in.(value1,value2,value3)`. However, other filters use the format `COLUMN_NAME=FILTER_NAME.value`. + In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. From cf96bf2a6d706f86cdcf0ab2f6fe618f4669a275 Mon Sep 17 00:00:00 2001 From: abc3 Date: Tue, 14 Feb 2023 22:18:40 +0100 Subject: [PATCH 017/603] update a menu title --- .../Navigation/NavigationMenu/NavigationMenu.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index d568a0614fb..08fe63bb175 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -511,7 +511,7 @@ export const realtime = { items: [ { name: 'Overview', url: '/guides/realtime', items: [] }, { name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] }, - { name: 'Protocol Description', url: '/guides/realtime/protocol', items: [] }, + { name: 'Protocol', url: '/guides/realtime/protocol', items: [] }, { name: 'Channels', url: undefined, From b1db806d03c8ca4e861b85b139a3781398da38ee Mon Sep 17 00:00:00 2001 From: Chase Granberry Date: Tue, 14 Feb 2023 14:57:00 -0700 Subject: [PATCH 018/603] More Realtime docs nav --- .../NavigationMenu/NavigationMenu.constants.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 08cea106b45..a3d77c9bd11 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -512,15 +512,24 @@ export const realtime = { { name: 'Overview', url: '/guides/realtime', items: [] }, { name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] }, { - name: 'Channels', + name: 'Discover', + url: undefined, + items: [{ name: 'Architecture', url: '/guides/realtime/architecture', items: [] }], + }, + { + name: 'Extensions', url: undefined, items: [ { name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] }, { name: 'Presence', url: '/guides/realtime/presence', items: [] }, { name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] }, - { name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] }, ], }, + { + name: 'Administer', + url: undefined, + items: [{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] }], + }, ], } From d562714f9f668e4c1eaf9cc1a8c68f765f3d0da5 Mon Sep 17 00:00:00 2001 From: Chase Granberry Date: Tue, 14 Feb 2023 14:57:32 -0700 Subject: [PATCH 019/603] More Realtime architecture description --- .../pages/guides/realtime/architecture.mdx | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 apps/docs/pages/guides/realtime/architecture.mdx diff --git a/apps/docs/pages/guides/realtime/architecture.mdx b/apps/docs/pages/guides/realtime/architecture.mdx new file mode 100644 index 00000000000..9f13be8d74e --- /dev/null +++ b/apps/docs/pages/guides/realtime/architecture.mdx @@ -0,0 +1,76 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'architecture', + title: 'Realtime Architecture', + description: 'Architecture of the Supabase Realtime service', + sidebar_label: 'Architecture', +} + +Realtime is a globally distributed Erlang cluster. Clients can connect to any node in the cluster via WebSockets and send messages to any other client connected to the cluster. + +Realtime is written in [Elixir](https://elixir-lang.org/) (a flavor of Erlang) and utilizes many tools the [Phoenix Framework](https://www.phoenixframework.org/) provides out of the box. + +## Elixir & Phoenix + +Phoenix is fast and able to handle millions of concurrent connections. + +Phoenix can handle many concurrent connections because Erlang provides lightweight processes to work with (not Linux processes). + +Client-facing WebSocket servers need to handle many concurrent connections. Elixir & Phoenix let the Supbase Realtime cluster do this easily. + +## Extensions + +Extensions are pluggable feature sets enabled by initializing a Realtime connection with certain message types. + +[Broadcast](/docs/guides/realtime/broadcast), [Presence](/docs/guides/realtime/presence) and [Posgres Changes](/docs/guides/realtime/postgres-changes) are all Realtime Extensions. Some Extensions, like Postgres Changes, require backend configuration to be saved on the cluster before use. + +For example, Realtime needs your database credentials to know how to connect to your database and you don't want to send those over a client connection. + +## Global Cluster + +Presence is an in-memory key-value store backed by a CRDT. When a user is connected to the cluster the state of that user is sent to all connected Realtime nodes. + +Broadcast lets you send a message from any connected client to a Channel. Any other client connected to that same Channel will receive that message. + +This works globally. A client connected to a Realtime node in the United States can send a message to another client connected to a node Singapore. Simply connect two clients to the same Realtime Channel and they'll all receive the same messages. + +Broadcast is useful for getting messages to users in the same location very quickly. If a group of clients are connected to a node in Singapore, the message only needs to go to that Realtime node in Singapore and back down. If users are close to a Realtime node they'll get Broadcast messages in under 50 milliseconds, basically the time it takes to ping the cluster. + +Thanks to the Realtime cluster, you (an amazing Supabase dev) don't have to think about which region your users connect to. + +If you're using Broadcast, Presence, or streaming database changes, messages will always get to your users via the shortest path possible. + +## Connecting to a Database + +Realtime allows you to listen to changes from your Postgres database. When a new client connects to Realtime and initializes the `postgres_changes` Realtime Extentsion the cluster will connect to your Postgres database and start streaming changes from a replication slot. + +Realtime knows the region your database is in, and connects to it from the closest region possible. + +Every Realtime region has at least two nodes so if one node goes offline the other node should reconnect and start streaming changes again. + +## Streaming the Write-Ahead Log + +During the connection process a Postgres replication slot is aquired. + +Realtime delivers changes by polling the replication slot and appending connection IDs to each wal record. Each connection ID is a WebSocket connection. + +Connection IDs are Erlang processes representing underlying sockets on the cluster. These IDs are globally unique and messages to processes are routed automatically by the Erlang virtual machine. + +After receiving results from the polling query, with connection IDs appended, Realtime delivers records to those connections. + +## Channels + +Channels are a `topic` you define when you initialize your Supabase Realtime client. Everyone connected to the same Channel will receive the same messages. + +Clients can send and receive messages bi-directionally over a Channel. The Realtime backend can also push messages to all clients connected to the same Channel. + +A single client can receive change records from Postgres, Broadcast messages from other clients and Presence updates all over the same Channel. + +Channels are implimented over [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html) which uses [Phoenix.PubSub](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) with the default `Phoenix.PubSub.PG2` adapter. + +The PG2 adapter utilizes Erlang [process groups](https://www.erlang.org/docs/18/man/pg2.html) to impliment the PubSub model where a publisher can send messages to many subscribers. + +export const Page = ({ children }) => + +export default Page From 035afa20dbeb702981157ef04f7c277b11bd8dc9 Mon Sep 17 00:00:00 2001 From: Chase Granberry Date: Tue, 14 Feb 2023 15:03:05 -0700 Subject: [PATCH 020/603] Spelling --- apps/docs/pages/guides/realtime/architecture.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/realtime/architecture.mdx b/apps/docs/pages/guides/realtime/architecture.mdx index 9f13be8d74e..5a8830cd9eb 100644 --- a/apps/docs/pages/guides/realtime/architecture.mdx +++ b/apps/docs/pages/guides/realtime/architecture.mdx @@ -51,7 +51,7 @@ Every Realtime region has at least two nodes so if one node goes offline the oth ## Streaming the Write-Ahead Log -During the connection process a Postgres replication slot is aquired. +During the connection process a Postgres replication slot is acquired. Realtime delivers changes by polling the replication slot and appending connection IDs to each wal record. Each connection ID is a WebSocket connection. @@ -67,9 +67,9 @@ Clients can send and receive messages bi-directionally over a Channel. The Realt A single client can receive change records from Postgres, Broadcast messages from other clients and Presence updates all over the same Channel. -Channels are implimented over [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html) which uses [Phoenix.PubSub](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) with the default `Phoenix.PubSub.PG2` adapter. +Channels are implemented over [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html) which uses [Phoenix.PubSub](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) with the default `Phoenix.PubSub.PG2` adapter. -The PG2 adapter utilizes Erlang [process groups](https://www.erlang.org/docs/18/man/pg2.html) to impliment the PubSub model where a publisher can send messages to many subscribers. +The PG2 adapter utilizes Erlang [process groups](https://www.erlang.org/docs/18/man/pg2.html) to implement the PubSub model where a publisher can send messages to many subscribers. export const Page = ({ children }) => From c0fd3974796d07f1c23d28550313e990ade413ef Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Wed, 15 Feb 2023 15:33:43 +0800 Subject: [PATCH 021/603] feat: add social tracking. --- apps/www/supabase/functions/_shared/cors.ts | 4 + .../functions/launchweek-ticket-og/README.md | 21 ++ .../launchweek-ticket-og/handler.tsx | 242 ++++++++++++++++++ .../functions/launchweek-ticket-og/index.ts | 11 + apps/www/www.code-workspace | 18 ++ 5 files changed, 296 insertions(+) create mode 100644 apps/www/supabase/functions/_shared/cors.ts create mode 100644 apps/www/supabase/functions/launchweek-ticket-og/README.md create mode 100644 apps/www/supabase/functions/launchweek-ticket-og/handler.tsx create mode 100644 apps/www/supabase/functions/launchweek-ticket-og/index.ts create mode 100644 apps/www/www.code-workspace diff --git a/apps/www/supabase/functions/_shared/cors.ts b/apps/www/supabase/functions/_shared/cors.ts new file mode 100644 index 00000000000..2ac4d89b14a --- /dev/null +++ b/apps/www/supabase/functions/_shared/cors.ts @@ -0,0 +1,4 @@ +export const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +} diff --git a/apps/www/supabase/functions/launchweek-ticket-og/README.md b/apps/www/supabase/functions/launchweek-ticket-og/README.md new file mode 100644 index 00000000000..260f2a6457d --- /dev/null +++ b/apps/www/supabase/functions/launchweek-ticket-og/README.md @@ -0,0 +1,21 @@ +# Open Graph (OG) Image Generation with Supabase Storage CDN Caching + +Generate Open Graph images with Deno and Supabase Edge Functions and cache the generated image with Supabase Storage CDN. + +- Docs: https://deno.land/x/og_edge@0.0.2 +- Examples: https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples +- Demo: https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=thorwebdev + +## Run locally + +```bash +supabase functions serve launchweek-ticket-og --no-verify-jwt +``` + +Navigate to http://localhost:54321/functions/v1/launchweek-ticket-og?username=thorwebdev + +## Deploy + +```bash +supabase functions deploy launchweek-ticket-og --no-verify-jwt +``` diff --git a/apps/www/supabase/functions/launchweek-ticket-og/handler.tsx b/apps/www/supabase/functions/launchweek-ticket-og/handler.tsx new file mode 100644 index 00000000000..c35b8b8cd3b --- /dev/null +++ b/apps/www/supabase/functions/launchweek-ticket-og/handler.tsx @@ -0,0 +1,242 @@ +import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' +import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.7.1' +import { corsHeaders } from '../_shared/cors.ts' + +const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw6' +const BACKGROUND_IMAGE_STD = `${STORAGE_URL}/lw6_ticket_regular.png?prod=true&v=2` +const BACKGROUND_IMAGE_GOLDEN = `${STORAGE_URL}/lw6_ticket_gold.png?prod=true&v=2` +const SUPA_CHECKMARK = `${STORAGE_URL}/supaverified.png?prod=true&v=2` +const SUPA_CHECKMARK_GOLD = `${STORAGE_URL}/supaverified_gold.png?prod=true&v=2` + +// Load custom font +const FONT_URL = `${STORAGE_URL}/CircularStd-Book.otf?prod=true&v=2` +const font = fetch(new URL(FONT_URL, import.meta.url)).then((res) => res.arrayBuffer()) + +export async function handler(req: Request) { + const url = new URL(req.url) + const username = url.searchParams.get('username') ?? url.searchParams.get('amp;username') + const userAgent = req.headers.get('user-agent') + + try { + if (!username) throw new Error('missing username param') + // Track social shares + const supabaseAdminClient = createClient( + // Supabase API URL - env var exported by default when deployed. + Deno.env.get('SUPABASE_URL') ?? '', + // Supabase API SERVICE ROLE KEY - env var exported by default when deployed. + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' + ) + if (userAgent?.toLocaleLowerCase().includes('twitter')) { + const { error } = await supabaseAdminClient + .from('lw6_tickets') + .update({ sharedOnTwitter: true }) + .eq('username', username) + if (error) console.log(error.message) + } else if (userAgent?.toLocaleLowerCase().includes('linkedin')) { + const { error } = await supabaseAdminClient + .from('lw6_tickets') + .update({ sharedOnLinkedIn: true }) + .eq('username', username) + if (error) console.log(error.message) + } + + // Try to get image from Supabase Storage CDN. + const storageResponse = await fetch(`${STORAGE_URL}/tickets/${username}.png?prod=true&v=5`) + if (storageResponse.ok) return storageResponse + + // Get ticket data + const { data, error } = await supabaseAdminClient + .from('lw6_tickets_golden') + .select('name, ticketNumber, golden') + .eq('username', username) + .maybeSingle() + if (error) throw error + + if (!data?.ticketNumber) throw new Error('missing data') + const { name, ticketNumber } = data + const golden = data?.golden ?? false + + // Else, generate image ad upload to storage. + const fontData = await font + const numDigits = `${Number(ticketNumber)}`.length + const prefix = `00000000`.slice(numDigits) + + const generatedImage = new ImageResponse( + ( + <> +
+ {/* Background image */} + + {/* GitHub Avatar image */} + + {/* Name & username */} +
+
+

+ {name ?? username} +

+
+ {/* Username and supaverified checkmark */} +
+ {`@${username}`} + + + +
+
+ {/* Date */} +

+ December 12th 2022 +

+ {/* URL */} +

+ supabase.com/launch-week +

+
+ {/* Ticket No */} +
+

+ {`No ${prefix}${ticketNumber}`} +

+
+ + ), + { + width: 1200, + height: 630, + fonts: [ + { + name: 'Circular', + data: fontData, + style: 'normal', + }, + ], + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', + 'cdn-cache-control': 'max-age=31536000', + }, + } + ) + + // Upload image to storage. + const { error: storageError } = await supabaseAdminClient.storage + .from('images') + .upload(`lw6/tickets/${username}.png`, generatedImage.body!, { + contentType: 'image/png', + cacheControl: '31536000', + upsert: false, + }) + if (storageError) throw storageError + + return generatedImage + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + } +} diff --git a/apps/www/supabase/functions/launchweek-ticket-og/index.ts b/apps/www/supabase/functions/launchweek-ticket-og/index.ts new file mode 100644 index 00000000000..7da9ffc001b --- /dev/null +++ b/apps/www/supabase/functions/launchweek-ticket-og/index.ts @@ -0,0 +1,11 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +import { serve } from 'https://deno.land/std@0.140.0/http/server.ts' + +import { handler } from './handler.tsx' + +console.log(`Function "launchweek-ticket-og" up and running!`) + +serve(handler) diff --git a/apps/www/www.code-workspace b/apps/www/www.code-workspace new file mode 100644 index 00000000000..1b5f7b7d4a1 --- /dev/null +++ b/apps/www/www.code-workspace @@ -0,0 +1,18 @@ +{ + "folders": [ + { + "name": "www", + "path": "./" + }, + { + "name": "supabase-functions", + "path": "supabase/functions" + } + ], + "settings": { + "files.exclude": { + "node_modules/": true, + "supabase/functions/": true + } + } +} From 0d5a5b750195a28e0052edc282c0e35f03e0499c Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 16 Feb 2023 13:27:40 +0800 Subject: [PATCH 022/603] fix: issue with detecting session on page load. --- .../components/LaunchWeek/Ticket/ticket.tsx | 7 ++- apps/www/pages/launch-week/index.tsx | 48 ++++++++++++------- apps/www/pages/launch-week/tickets/index.tsx | 44 ++++++++++++----- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.tsx b/apps/www/components/LaunchWeek/Ticket/ticket.tsx index c494a9652ed..86f76f97a8b 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket.tsx @@ -90,8 +90,11 @@ export default function Ticket({

) : golden ? ( <> - You got a Golden ticket. This means you’re in, and you also won a Supabase sticker - pack! +

+ You got a Golden ticket. This means you’re in, and you also won a Supabase sticker + pack! +

+

Referrals: {referrals}

) : username ? ( <> diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 10cddcf4113..b6b041b51c9 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -6,7 +6,7 @@ import _days from '~/components/LaunchWeek/lw6_days.json' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' -import { createClient, Session } from '@supabase/supabase-js' +import { createClient, Session, SupabaseClient } from '@supabase/supabase-js' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer' @@ -40,10 +40,7 @@ export default function launchweek() { const description = 'Supabase Launch Week 6 | 12-18 Dec 2022' const liveDay = null - const [supabase] = useState(() => - createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) - ) - + const [supabase, setSupabase] = useState(null) const [session, setSession] = useState(null) const [creators, setCreators] = useState([]) const [activeCreator, setActiveCreator] = useState(null) @@ -57,13 +54,30 @@ export default function launchweek() { } useEffect(() => { - supabase.auth.onAuthStateChange((event, session) => { - setSession(session) - }) - - getCreators() + if (!supabase) { + setSupabase( + createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) + ) + } }, []) + useEffect(() => { + if (supabase) { + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((event, session) => { + setSession(session) + }) + + getCreators() + + return () => subscription.unsubscribe() + } + }, [supabase]) + useEffect(() => { document.body.className = isDarkMode ? 'dark bg-[#121212]' : 'light bg-[#fff]' }, [isDarkMode]) @@ -1259,12 +1273,14 @@ export default function launchweek() { {process.env.NEXT_PUBLIC_LW_STARTED && (
- + {supabase && ( + + )}
)}
diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx index def343e0796..ced16c72c87 100644 --- a/apps/www/pages/launch-week/tickets/index.tsx +++ b/apps/www/pages/launch-week/tickets/index.tsx @@ -4,16 +4,14 @@ import SectionContainer from '~/components/Layouts/SectionContainer' import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer' import { SITE_URL } from '~/lib/constants' import { useRouter } from 'next/router' -import { createClient, Session } from '@supabase/supabase-js' +import { createClient, Session, SupabaseClient } from '@supabase/supabase-js' import { useState, useEffect } from 'react' import { useTheme } from '~/components/Providers' export default function TicketHome() { const { isDarkMode } = useTheme() - const [supabase] = useState(() => - createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) - ) + const [supabase, setSupabase] = useState(null) const [session, setSession] = useState(null) const description = 'Supabase Launch Week 6 | 12-16 Dec 2022' const { query } = useRouter() @@ -26,11 +24,29 @@ export default function TicketHome() { } useEffect(() => { - supabase.auth.onAuthStateChange((event, supaSession) => { - setSession(supaSession) - }) + if (!supabase) { + setSupabase( + createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) + ) + } }, []) + useEffect(() => { + if (supabase) { + supabase.auth.getSession().then(({ data: { session } }) => setSession(session)) + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((event, session) => { + setSession(session) + }) + + return () => subscription.unsubscribe() + } + }, [supabase]) + useEffect(() => { document.body.className = isDarkMode ? 'dark bg-[#121212]' : 'light bg-[#fff]' }, [isDarkMode]) @@ -64,12 +80,14 @@ export default function TicketHome() {

Dec 12 – 16 at 8 AM PT | 11 AM ET

- + {supabase && ( + + )} From cd8cd9b975ac16d53ff4b0155eb5c3eef5c06c4a Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 21 Feb 2023 16:41:47 -0330 Subject: [PATCH 023/603] Move lw6 to subdir --- apps/www/components/Nav/index.tsx | 38 +- apps/www/pages/launch-week/6/index.tsx | 1243 ++++++++++++++++ .../6/styles/launchWeek6.module.css | 25 + .../launch-week/6/styles/utils6.module.css | 76 + apps/www/pages/launch-week/6/types6.d.ts | 43 + apps/www/pages/launch-week/index.tsx | 1253 +---------------- .../pages/launch-week/launchWeek.module.css | 26 +- apps/www/public/rss.xml | 310 ++-- 8 files changed, 1607 insertions(+), 1407 deletions(-) create mode 100644 apps/www/pages/launch-week/6/index.tsx create mode 100644 apps/www/pages/launch-week/6/styles/launchWeek6.module.css create mode 100644 apps/www/pages/launch-week/6/styles/utils6.module.css create mode 100644 apps/www/pages/launch-week/6/types6.d.ts diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index 387aefacc5d..edbfd03169f 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import Link from 'next/link' +import { useRouter } from 'next/router' import { Button, Badge, IconStar, IconChevronDown } from 'ui' import FlyOut from '~/components/UI/FlyOut' @@ -19,6 +20,8 @@ import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-w const Nav = () => { const { isDarkMode } = useTheme() + const { pathname } = useRouter() + const isLauchWeekPage = pathname.includes('launch-week') const [open, setOpen] = useState(false) const [openProduct, setOpenProduct] = useState(false) @@ -160,6 +163,7 @@ const Nav = () => { px-1 text-sm font-medium transition-colors + ${isLauchWeekPage ? '!text-white' : ''} ` + props.active } onClick={props.onClick} @@ -172,7 +176,11 @@ const Nav = () => { (props.active && ' rotate-180 transform transition-all duration-100') } > - +
@@ -182,8 +190,16 @@ const Nav = () => { <> {/* */}
-
-