# Quickstart — host [#quickstart--host] > Pergunta: "eu sou o host, como mostro um QR e recebo evento?" **Pré-requisitos:** * Harness rodando — [setup-harness.md](/pt/docs/event-room/latest/markdown/setup-harness). * Uma credencial de transporte (`credential`): uma string não vazia, opaca para esta lib — a sessão logada da sua própria conta. Não existe `credential: none`. * Um `guestUrl`: a URL da sua própria tela de guest (a página/app que o guest vai abrir depois de escanear o QR ou digitar o código). Contra o harness local, qualquer URL absoluta serve como exemplo — em produção é o endereço real do seu guest. ## Passo a passo [#passo-a-passo] ```ts import { createHost, buildJoinUrl, type JoinTarget } from '@sofya-sdk/event-room'; import { generateQrImageDataUrl } from '@sofya-sdk/event-room/qr'; const { host, room } = await createHost({ url: 'ws://localhost:8080/ws', credential: 'dev-host-credential', guestUrl: 'https://guest.example.com/join', room: { accessMode: 'approval_and_code', }, }); console.log('sala criada:', room.roomId, room.accessMode); // room.joinPayload já é {roomId, accessMode, joinCode?} — o alvo que o guest // precisa. buildJoinUrl nunca acrescenta caminho a guestUrl (ver nota abaixo) // — só pendura o alvo na query string do endereço que você já escolheu. const joinUrl = buildJoinUrl('https://guest.example.com/join', room.joinPayload as unknown as JoinTarget); const qrDataUrl = await generateQrImageDataUrl(joinUrl); // qrDataUrl é "data:image/png;base64,..." — use direto num . console.log('QR pronto:', qrDataUrl.slice(0, 40) + '...'); room.onJoinRequest((request) => { console.log('pedido de entrada de', request.displayName ?? '(sem nome)'); // `request.displayName` é auto-declarado, nunca verificado (ver // seguranca.md) — decida com base na sua própria tela/fluxo, não só nele. const aprovado = true; // decisão do app if (aprovado) { room.approveJoin(request.id); } else { room.rejectJoin(request.id); } }); room.session; // RoomSessionStateMachine — ver maquina-de-estados.md // Quando terminar de usar a sala: await room.close(); // Quando terminar de usar o host inteiro (nenhuma sala será reaberta): await host.close(); ``` ## Nota: `buildJoinUrl` nunca acrescenta caminho [#nota-buildjoinurl-nunca-acrescenta-caminho] `buildJoinUrl(guestUrl, target)` sempre usa `guestUrl` exatamente como o endereço final — nunca resolve um caminho relativo contra ele, nunca acrescenta segmento. Ele só chama `searchParams.set` para pendurar `v`/`roomId`/`accessMode`/`joinCode?` na query string. Se `guestUrl` já tiver caminho e query próprios (`https://app.exemplo.com/entrar?tema=escuro`), eles são preservados — só os quatro parâmetros reservados são adicionados/sobrescritos, e nenhum outro. ## Nota: esquema customizado (deep link) também funciona [#nota-esquema-customizado-deep-link-também-funciona] `guestUrl` não precisa ser `http(s)`: um esquema de app próprio (`myapp://join`) é aceito do mesmo jeito, contanto que não esteja na lista de recusa abaixo. ## A lista de esquemas recusados é fechada, e por quê [#a-lista-de-esquemas-recusados-é-fechada-e-por-quê] `buildJoinUrl` recusa exatamente cinco esquemas: `javascript:`, `data:`, `blob:`, `vbscript:`, `file:` — cada um executa conteúdo, embute conteúdo, ou não é um destino de aplicação de verdade. A lista **não cresce por reflexo**: `about:`, `view-source:`, `chrome:`, `ms-settings:` ficam de fora deliberadamente. A razão: essa guarda defende contra **erro do integrador** (um `guestUrl` que virou `data:` por acidente), não contra um adversário — quem controla o `guestUrl` já controla o código do host. Por isso a lib também não pede (nem oferece) uma lista branca de esquemas. Essa validação acontece dentro de `buildJoinUrl`, é síncrona e falha antes de qualquer I/O — descubra o erro no instante mais cedo possível, não num reinício. ## Próximo passo [#próximo-passo] [receita-microfone.md](/pt/docs/event-room/latest/markdown/receita-microfone) — o fluxo completo com um guest de verdade do outro lado, incluindo o login que o guest precisa fazer antes de escanear.