├── .npmrc ├── src ├── app.css ├── routes │ ├── __layout.svelte │ ├── index.svelte │ ├── [room].svelte │ └── api │ │ └── get-access-token.ts ├── store.ts ├── app.html ├── services │ ├── user.ts │ └── chat.ts ├── app.d.ts └── components │ ├── LoggedUserHeader.svelte │ ├── LogoutIcon.svelte │ ├── ConversationInput.svelte │ ├── Message.svelte │ ├── Conversation.svelte │ ├── UserHeader.svelte │ ├── Logo.svelte │ └── RoomSelect.svelte ├── static ├── favicon.png ├── robots.txt ├── svelte-welcome.png └── svelte-welcome.webp ├── .prettierrc ├── postcss.config.cjs ├── .gitignore ├── tailwind.config.cjs ├── tests └── test.ts ├── .eslintignore ├── .prettierignore ├── playwright.config.ts ├── tsconfig.json ├── svelte.config.js ├── .eslintrc.cjs ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/chat-hackathon/main/static/favicon.png -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /static/svelte-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/chat-hackathon/main/static/svelte-welcome.png -------------------------------------------------------------------------------- /static/svelte-welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/chat-hackathon/main/static/svelte-welcome.webp -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | .vercel 10 | .output 11 | package-lock.json 12 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | theme: { 5 | extend: {} 6 | }, 7 | plugins: [] 8 | }; 9 | -------------------------------------------------------------------------------- /tests/test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('about page has expected h1', async ({ page }) => { 4 | await page.goto('/about'); 5 | expect(await page.textContent('h1')).toBe('About this app'); 6 | }); 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /src/routes/__layout.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 3000 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | type User = { 4 | avatar: string, 5 | userName: string, 6 | name: string, 7 | email: string, 8 | token: string 9 | } 10 | 11 | export const activeConversation = writable(null) 12 | export const user = writable(null) 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/services/user.ts: -------------------------------------------------------------------------------- 1 | export const getAccessToken = async ({ token }: { token: string}) => { 2 | const res = await fetch('/api/get-access-token', { 3 | headers: { 4 | jwt: token 5 | } 6 | }) 7 | 8 | if (!res.ok) throw new Error('Error getting access token') 9 | 10 | const { accessToken } = await res.json() 11 | return accessToken 12 | } -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | // and what to do when importing types 6 | declare namespace App { 7 | interface Locals { 8 | userid: string; 9 | } 10 | 11 | // interface Platform {} 12 | 13 | // interface Session {} 14 | 15 | // interface Stuff {} 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | secret chat 🥷 9 | 10 | 11 | 12 | 13 | {#if $user}{/if} 14 | -------------------------------------------------------------------------------- /src/components/LoggedUserHeader.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if $user} 7 | {$user.name} 8 | {$user.name} 9 | 13 | {/if} 14 | -------------------------------------------------------------------------------- /src/routes/[room].svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {#if $activeConversation?.uniqueName} 9 |
10 |

11 | {$activeConversation.uniqueName} 12 |

13 | 14 | 15 |
16 | {/if} 17 | -------------------------------------------------------------------------------- /src/components/LogoutIcon.svelte: -------------------------------------------------------------------------------- 1 | 8 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess({ 9 | postcss: true 10 | }), 11 | 12 | kit: { 13 | adapter: adapter(), 14 | 15 | // Override http methods in the Todo forms 16 | methodOverride: { 17 | allowed: ['PATCH', 'DELETE'] 18 | } 19 | } 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/ConversationInput.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 |
17 | 23 |
24 | -------------------------------------------------------------------------------- /src/components/Message.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |

{body}

13 | {author} 14 |
15 | 16 | 27 | -------------------------------------------------------------------------------- /src/components/Conversation.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | {#each messages as message} 25 | 26 | {/each} 27 |
28 | -------------------------------------------------------------------------------- /src/services/chat.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@twilio/conversations' 2 | 3 | export const createOrJoinConversation = async ( 4 | { room, accessToken } : { room: string, accessToken: string } 5 | ) => { 6 | const client = new Client(accessToken) 7 | 8 | return new Promise(resolve => { 9 | client.on('stateChanged', async state => { 10 | if (state === 'initialized') { 11 | let conversation 12 | 13 | try { 14 | conversation = await client.createConversation({ uniqueName: room }) 15 | 16 | // await conversation?.add('ma') 17 | // await conversation?.add('mb') 18 | 19 | } catch (e) { 20 | console.error(e) 21 | 22 | try { 23 | conversation = await client.getConversationByUniqueName(room) 24 | 25 | // await conversation?.add('ma') 26 | // await conversation?.add('mb') 27 | } catch (e) { 28 | console.error(e) 29 | } 30 | } 31 | 32 | console.log(conversation) 33 | 34 | resolve(conversation) 35 | } 36 | }) 37 | }) 38 | } -------------------------------------------------------------------------------- /src/components/UserHeader.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 | {#if $user} 28 | 29 | {:else if showAnonymousInput} 30 |
31 | 37 |
38 | {:else} 39 | 42 | {/if} 43 |
44 | -------------------------------------------------------------------------------- /src/components/Logo.svelte: -------------------------------------------------------------------------------- 1 |

4 | 25 | secretchat 26 |

27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secret-chat", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "svelte-kit dev", 6 | "build": "svelte-kit build", 7 | "package": "svelte-kit package", 8 | "preview": "svelte-kit preview", 9 | "prepare": "svelte-kit sync", 10 | "test": "playwright test", 11 | "check": "svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 13 | "lint": "prettier --check --plugin-search-dir=. . && eslint .", 14 | "format": "prettier --write --plugin-search-dir=. ." 15 | }, 16 | "devDependencies": { 17 | "@playwright/test": "1.22.2", 18 | "@sveltejs/adapter-auto": "next", 19 | "@sveltejs/kit": "next", 20 | "@types/cookie": "0.5.1", 21 | "@typescript-eslint/eslint-plugin": "5.27.0", 22 | "@typescript-eslint/parser": "5.27.0", 23 | "autoprefixer": "^10.4.7", 24 | "eslint": "8.16.0", 25 | "eslint-config-prettier": "8.3.0", 26 | "eslint-plugin-svelte3": "4.0.0", 27 | "postcss": "^8.4.14", 28 | "prettier": "2.6.2", 29 | "prettier-plugin-svelte": "2.7.0", 30 | "svelte": "3.46.0", 31 | "svelte-check": "2.7.1", 32 | "svelte-preprocess": "^4.10.6", 33 | "tailwindcss": "^3.1.4", 34 | "tslib": "2.3.1", 35 | "typescript": "4.7.2" 36 | }, 37 | "type": "module", 38 | "dependencies": { 39 | "@twilio/conversations": "^2.1.0", 40 | "twilio": "^3.78.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/RoomSelect.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |
27 | 30 | 36 | 41 |
42 |
43 | -------------------------------------------------------------------------------- /src/routes/api/get-access-token.ts: -------------------------------------------------------------------------------- 1 | import type { RequestHandler } from '@sveltejs/kit' 2 | import twilio from 'twilio' 3 | 4 | const TWILIO_ACCOUNT_SID = import.meta.env.VITE_PUBLIC_TWILIO_ACCOUNT_SID 5 | const TWILIO_API_KEY = import.meta.env.VITE_PUBLIC_TWILIO_API_KEY 6 | const TWILIO_API_SECRET = import.meta.env.VITE_PUBLIC_TWILIO_API_SECRET 7 | const SERVICE_SID = import.meta.env.VITE_PUBLIC_SERVICE_SID 8 | 9 | console.log({ 10 | TWILIO_ACCOUNT_SID, 11 | TWILIO_API_KEY, 12 | TWILIO_API_SECRET, 13 | SERVICE_SID, 14 | }) 15 | 16 | export const get: RequestHandler = async ({ request }) => { 17 | const jwt = request.headers.get('jwt') 18 | if (jwt == null) return { status: 401 } 19 | 20 | // This is temporal as we're allowing anonymous users 21 | const identity = jwt.startsWith('anonymous') 22 | ? jwt.split('_')[1] 23 | : null 24 | 25 | // Lo que haríamos de verdad 26 | // const user = await supabase.auth.api.getUser(jwt) 27 | // const identity = user.data.user_metadata.user_name 28 | // console.log({identity}) 29 | 30 | if (identity == null) return { status: 401 } 31 | 32 | const { AccessToken } = twilio.jwt 33 | const { ChatGrant } = AccessToken 34 | 35 | const accessToken = new AccessToken( 36 | TWILIO_ACCOUNT_SID, 37 | TWILIO_API_KEY, 38 | TWILIO_API_SECRET, { 39 | identity 40 | }) 41 | 42 | const conversationsGrant = new ChatGrant({ 43 | serviceSid: SERVICE_SID 44 | }) 45 | 46 | accessToken.addGrant(conversationsGrant) 47 | 48 | console.log(accessToken.toJwt()) 49 | 50 | return { 51 | status: 200, 52 | body: { 53 | accessToken: accessToken.toJwt() 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | HACKATHON JULIO 2022 - TWILIO + CHAT SECRETO 🚀 5 | 6 | **🗓️ Fecha límite -> 20 de julio en Twitch.**
7 | 📹 Directo del 1 de julio con explicaciones -> https://www.twitch.tv/videos/1519558242
8 | 9 |
10 | 11 | ## - 🎁 PREMIOS para los 3 primeros: 12 | 13 | - [1] Keychron K3 Ultra-slim Wireless Mechanical Keyboard - Version 2 (Si no se puede enviar, se pagará lo que cueste el teclado con PayPal). 14 | - [2] 40€ en Redbubble. 15 | - [3] 20€ en Redbubble. 16 | 17 | ## - ⚠️ Requisitos: 18 | 19 | - **[1] Registrarse en Twilio -> https://www.twilio.com/referral/xdppiQ 🥰 ** 20 | - [2] Crea un chat secreto dónde sólo la gente que tú quieras pueda hablar **usando los servicios de Twilio Conversations.** 21 | - [3] La app/web debe estar **desplegada** para que la podamos ver. 22 | - [4] **El repositorio de código debe ser abierto** y el **README** debe indicar que participa en la hackathon. 23 | - [5] :date: Enviar el repositorio + deploy en este canal, en este hilo: https://discord.com/channels/741237973663612969/992476955792375928/992478015164518520 24 | 25 | ## - 👀 Consideraciones: 26 | - Se tendrá en cuenta: ejecución del código (estructura...), look and feel (experiencia del usuario, diseño...), no puedes pillar una plantilla CSS de una aplicación que ya exista, que sea responsive y que funcione para móvil (mobile-first). 27 | - Se puede hacer con lo que quieras: Vanilla, Angular, React, Vue, Svelte, Nextjs, Nuxtjs, Remix... 28 | - Se puede hacer equipo :partying_face::partying_face:. 29 | - 💅 Te puedes inspirar en el diseño: https://dribbble.com/ 30 | 31 | [🐙 Muchos más servicios disponibles en Twilio](https://www.twilio.com/referral/xdppiQ) 32 | --------------------------------------------------------------------------------