Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/chrome-extension/lib/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ bridge.handle.CREATE_MEMO(async (payload, _sender, sendResponse) => {
} catch (error) {
sendResponse({
success: false,
error: error instanceof Error ? error.message : "메모 생성에 실패했습니다.",
error:
error instanceof Error ? error.message : "메모 생성에 실패했습니다.",
});
}
});
4 changes: 1 addition & 3 deletions apps/chrome-extension/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import fs from "node:fs";
import { CONFIG } from "@web-memo/env";
import deepmerge from "deepmerge";



const packageJson = JSON.parse(fs.readFileSync("../../package.json", "utf8"));

/**
Expand Down Expand Up @@ -72,7 +70,7 @@ const manifest = deepmerge(
side_panel: {
default_path: "side-panel/index.html",
},
}
},
);

export default manifest;
7 changes: 6 additions & 1 deletion apps/chrome-extension/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
"tags": ["app"],
"tasks": {
"build": {
"outputs": ["../../dist/**", "!../../dist/side-panel/**", "!../../dist/options/**", "!../../dist/content-ui/**"]
"outputs": [
"../../dist/**",
"!../../dist/side-panel/**",
"!../../dist/options/**",
"!../../dist/content-ui/**"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { colorLog, ManifestParser } from "@web-memo/dev-utils";
import * as fs from "node:fs";
import { colorLog, ManifestParser } from "@web-memo/dev-utils";
import * as path from "path";
import * as process from "process";
import { pathToFileURL } from "url";
Expand Down
2 changes: 1 addition & 1 deletion apps/chrome-extension/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import makeManifestPlugin from "./utils/plugins/make-manifest-plugin";
const rootDir = resolve(__dirname);
const libDir = resolve(rootDir, "lib");

const outDir = resolve(rootDir, "..", "..", "dist");
const outDir = resolve(rootDir, "..", "..", "dist");
export default defineConfig({
resolve: {
alias: {
Expand Down
8 changes: 4 additions & 4 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
"files": {
"ignoreUnknown": false,
"includes": [
"**/src/**/*",
"**/app/**/*",
"**",
"!**/ios/**/*",
"!**/android/**/*",
"e2e/tests/**/*",
"!**/dist/**",
"!**/node_modules/**",
"!**/.next/**",
"!**/.vercel/**",
"!**/.expo/**",
"!**/tailwind-output.css"
"!**/tailwind-output.css",
"!.turbo/**",
"!dist-zip/**"
]
},
"formatter": {
Expand Down
30 changes: 15 additions & 15 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"name": "e2e",
"private": true,
"version": "1.10.4",
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"install:browsers": "playwright install --with-deps"
},
"postinstall": "playwright install --with-deps",
"devDependencies": {
"@playwright/test": "^1.47.0"
},
"dependencies": {
"@web-memo/shared": "workspace:*"
}
"name": "e2e",
"private": true,
"version": "1.10.4",
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"install:browsers": "playwright install --with-deps"
},
"postinstall": "playwright install --with-deps",
"devDependencies": {
"@playwright/test": "^1.47.0"
},
"dependencies": {
"@web-memo/shared": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion packages/supabase-edge-functions/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["denoland.vscode-deno"]
"recommendations": ["denoland.vscode-deno"]
}
14 changes: 6 additions & 8 deletions packages/supabase-edge-functions/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
{
"deno.enablePaths": [
"supabase/functions"
],
"deno.lint": true,
"deno.unstable": true,
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
"deno.enablePaths": ["supabase/functions"],
"deno.lint": true,
"deno.unstable": true,
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}
250 changes: 120 additions & 130 deletions packages/supabase-edge-functions/supabase/functions/kakao-auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,127 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};

Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}

try {
const { kakao_access_token } = await req.json();

if (!kakao_access_token) {
return new Response(
JSON.stringify({ error: "kakao_access_token is required" }),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

// 1. 카카오 API로 access token 검증 및 사용자 정보 조회
const kakaoRes = await fetch("https://kapi.kakao.com/v2/user/me", {
headers: { Authorization: `Bearer ${kakao_access_token}` },
});

if (!kakaoRes.ok) {
return new Response(
JSON.stringify({ error: "Invalid Kakao token" }),
{
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

const kakaoUser = await kakaoRes.json();
const email = kakaoUser.kakao_account?.email;

if (!email) {
return new Response(
JSON.stringify({ error: "Email not available from Kakao account" }),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

// 2. Supabase Admin 클라이언트 생성
const supabaseAdmin = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

// 3. 기존 사용자에 대해 magiclink 토큰 생성 시도
let { data: linkData, error: linkError } =
await supabaseAdmin.auth.admin.generateLink({
type: "magiclink",
email,
});

// 4. 사용자가 없으면 생성 후 재시도
if (linkError) {
const nickname =
kakaoUser.properties?.nickname ??
kakaoUser.kakao_account?.profile?.nickname;
const avatarUrl =
kakaoUser.properties?.profile_image ??
kakaoUser.kakao_account?.profile?.profile_image_url;

const { error: createError } =
await supabaseAdmin.auth.admin.createUser({
email,
email_confirm: true,
user_metadata: {
name: nickname,
avatar_url: avatarUrl,
provider: "kakao",
kakao_id: String(kakaoUser.id),
},
});

if (createError) {
return new Response(
JSON.stringify({ error: createError.message }),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

const retryResult = await supabaseAdmin.auth.admin.generateLink({
type: "magiclink",
email,
});
linkData = retryResult.data;
linkError = retryResult.error;
}

if (linkError || !linkData) {
return new Response(
JSON.stringify({
error: linkError?.message ?? "Failed to generate auth link",
}),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

return new Response(
JSON.stringify({
email,
token: linkData.properties.hashed_token,
}),
{
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}

try {
const { kakao_access_token } = await req.json();

if (!kakao_access_token) {
return new Response(
JSON.stringify({ error: "kakao_access_token is required" }),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

// 1. 카카오 API로 access token 검증 및 사용자 정보 조회
const kakaoRes = await fetch("https://kapi.kakao.com/v2/user/me", {
headers: { Authorization: `Bearer ${kakao_access_token}` },
});

if (!kakaoRes.ok) {
return new Response(JSON.stringify({ error: "Invalid Kakao token" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}

const kakaoUser = await kakaoRes.json();
const email = kakaoUser.kakao_account?.email;

if (!email) {
return new Response(
JSON.stringify({ error: "Email not available from Kakao account" }),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

// 2. Supabase Admin 클라이언트 생성
const supabaseAdmin = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

// 3. 기존 사용자에 대해 magiclink 토큰 생성 시도
let { data: linkData, error: linkError } =
await supabaseAdmin.auth.admin.generateLink({
type: "magiclink",
email,
});

// 4. 사용자가 없으면 생성 후 재시도
if (linkError) {
const nickname =
kakaoUser.properties?.nickname ??
kakaoUser.kakao_account?.profile?.nickname;
const avatarUrl =
kakaoUser.properties?.profile_image ??
kakaoUser.kakao_account?.profile?.profile_image_url;

const { error: createError } = await supabaseAdmin.auth.admin.createUser({
email,
email_confirm: true,
user_metadata: {
name: nickname,
avatar_url: avatarUrl,
provider: "kakao",
kakao_id: String(kakaoUser.id),
},
});

if (createError) {
return new Response(JSON.stringify({ error: createError.message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}

const retryResult = await supabaseAdmin.auth.admin.generateLink({
type: "magiclink",
email,
});
linkData = retryResult.data;
linkError = retryResult.error;
}

if (linkError || !linkData) {
return new Response(
JSON.stringify({
error: linkError?.message ?? "Failed to generate auth link",
}),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
}

return new Response(
JSON.stringify({
email,
token: linkData.properties.hashed_token,
}),
{
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});
Loading
Loading