npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@astralweb/nova-social-login

v2.0.3

Published

Social Login module for Nova e-commerce platform (Internal Use Only)

Readme

Social Login 模組使用指南

⚠️ 內部使用套件 - 此套件僅供 Astral Web 內部使用,未經授權不得用於其他用途。

概覽

Social Login 是一個 Nuxt 3 模組,提供完整的第三方社群登入功能,支援 Google、Facebook、LINE 等平台的 OAuth 認證。模組採用 Nuxt Module 架構設計,支援自動註冊組件、composables,並提供靈活的樣式配置、服務端回調處理,以及社群帳號綁定/解綁功能。

安裝與設定

1. 安裝套件

在 apps/web 和 apps/server 安裝

yarn add @astralweb/nova-social-login

2. Middleware 擴充

// apps/server/extendApiMethods/index.ts

// ... 略
export {
  socialLoginV2,
  socialLoginV2Binding,
  socialLoginV2Unbinding,
  customerSocialAccounts,
} from '@astralweb/nova-social-login/api';

3. GraphQL 擴充

// apps/server/extendApiMethods/customer.ts

export const generateCustomerToken = async (
  context: Context,
  variables: TObject,
): Promise<FetchResult<GenerateCustomerTokenMutation>> => {
  const { res, config, client } = context;
  try {
    const result = await client.mutate({
      mutation: gql`
        mutation generateCustomerToken($type: Int!, $email: String, $password: String, $uid: String) {
          generateCustomerToken(type: $type, email: $email, password: $password, uid: $uid) {
            token
          }
        }
      `,
      variables,
      context: {
        headers: getHeaders(context),
      },
    });
    // ... 略
    return result;
  } catch (error) {
    // ... 略
  }
};

4. 註冊模組

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@astralweb/nova-social-login',
  ],
})

5. storeConfig customQuery 新增欄位

// apps/web/api/magento/storeConfig/customQuery.ts
import { socialLoginFields } from '@astralweb/nova-social-login/api';

export const storeConfigCustomQuery: Record<string, any> = {
  storeConfig: 'store-config-custom-query',
  metadata: {
    fields: `
      product_reviews_enabled
      allow_guests_to_write_product_reviews
      is_guest_checkout_enabled
      cart_expires_in_days
      default_description
      default_title
      default_country
      ${socialLoginFields}
    `,
  },
};

6. Pinia storeConfig 設定

// apps/web/stores/storeConfig.ts
export const useStoreConfigStore = defineStore('storeConfig', {
  getters: {
    // ... 略
    socialLoginEnabled(state) {
      const socialLoginPlatforms = Object.keys(state.storeConfig?.social_login_v2 ?? {});
      return socialLoginPlatforms.length > 0;
    },
    socialLoginConfig(state) {
      return state.storeConfig?.social_login_v2 ?? null;
    },
  },
});

7. Account Modal 新增功能

// apps/web/plugins/account-modal.ts
export default defineNuxtPlugin(() => {
  // ... 略
  const closeAccountModal = () => {
    close();
    activeTab.value = 'login';
    if (isSocialLoginRegister.value) {
      initialRegisterValues.value = {};
    }
  };

  const initialRegisterValues = ref<Partial<ExtendCustomerCreateInput>>({});
  const openSocialLoginRegisterModal = (initialValues: Partial<ExtendCustomerCreateInput>) => {
    activeTab.value = 'socialLoginRegister';
    initialRegisterValues.value = initialValues;
    nextTick(() => {
      openAccountModal();
    });
  };
  const isSocialLoginRegister = computed(() => activeTab.value === 'socialLoginRegister' && initialRegisterValues.value?.type);

  return {
    provide: {
      accountModal: {
        // ... 略
        openSocialLoginRegisterModal,
        initialRegisterValues,
        isSocialLoginRegister,
      },
    },
  };
});
// apps/web/index.d.ts
import type { Ref } from 'vue';
import type { ExtendCustomerCreateInput } from '@astralweb/nova-magento-types';

declare module '#app' {
  interface NuxtApp {
    $accountModal: {
      // ... 略
      openSocialLoginRegisterModal: (initialValues: Partial<ExtendCustomerCreateInput>) => void;
      initialRegisterValues: Ref<Partial<ExtendCustomerCreateInput>>;
      isSocialLoginRegister: Ref<boolean>;
    };
  }
}

8. Composable 新增 useIntegrationSocialLogin

// apps/web/composables/useIntegrationSocialLogin/useIntegrationSocialLogin.ts

import { useSocialLoginVerification } from '@astralweb/nova-social-login';

export const useIntegrationSocialLogin = () => {
  const { $accountModal } = useNuxtApp();
  const { openAccountModal, openSocialLoginRegisterModal } = $accountModal;
  const { customerLoginMutation } = useCustomer();
  const toast = useToast();
  const { t } = useI18n();
  const { isLoggedIn } = useCustomerStore();
  const { processLoginResult } = useSocialLoginVerification();
  const oauthProvider = useCookie('oauth-provider');
  const oauthCode = useCookie('oauth-code');

  onMounted(async () => {
    // 如果已登入,則改走 social login binding
    if (isLoggedIn) return;

    if (!oauthProvider.value || !oauthCode.value) {
      return;
    }
    await processLoginResult({
      handleExistCustomer: customerLoginMutation.mutate,
      openLoginModal: () => {
        openAccountModal();
        // 避免被 modal 遮住
        nextTick(() => {
          toast.add({
            severity: 'info',
            summary: t('socialLogin.alreadyHaveAccount'),
            detail: t('socialLogin.useEmailLogin'),
            life: 5000,
          });
        });
      },
      handleNewCustomer: openSocialLoginRegisterModal,
      handleError: (error: string) => {
        openAccountModal();
        nextTick(() => {
          toast.add({
            severity: 'error',
            summary: t('socialLogin.failed'),
            detail: error,
            life: 5000,
          });
        });
      },
    });
  });
};
// apps/web/composables/useIntegrationSocialLogin/index.ts

export * from './useIntegrationSocialLogin';

9. 使用 useIntegrationSocialLogin

<!-- apps/web/app.vue -->

<script setup lang="ts">
// ... 略
onMounted(() => {
  // Need this class for cypress testing
  bodyClass.value = 'hydrated';
  useIntegrationSocialLogin();
});
</script>
<!-- apps/web/error.vue -->

<script setup lang="ts">
// ... 略
onMounted(() => {
  useIntegrationSocialLogin();
});
</script>

組件基本使用

社群登入

在登入頁面引入社群登入按鈕

<!-- AccountFormsLogin -->
<template>
  <div class="inline-flex w-fit flex-col gap-y-3 xl:mt-4 xl:gap-y-5">
    <div class="flex items-center justify-center">
      <div class="grow border-t border-primary-200"></div>
      <span class="typography-text-base mx-4 text-primary-700">
        {{ t('account.socialLogin') }}
      </span>
      <div class="grow border-t border-primary-200"></div>
    </div>

    <NovaSocialLoginButtonGroup 
      v-if="storeConfigStore.socialLoginConfig" 
      :social-login-config="storeConfigStore.socialLoginConfig" 
    />
  </div>
</template>

<script setup lang="ts">
const storeConfigStore = useStoreConfigStore();
</script>

社群註冊

使用原本的註冊表單,並且新增標題

<!-- components/Account/Modal/AccountModal.vue -->

<template>
  <!-- ... 略 -->
  <template #header>
    <div v-if="isSocialLoginRegister" class="w-full text-center text-secondary-700">
      {{ t('socialLogin.register.heading') }}
    </div>
    <PrimeTabs
      v-else-if="showTabs"
      :value="activeTab"
      class="w-full"
      scrollable
    >
      <PrimeTabList
        class="mt-4"
        :pt="{
          root: ({ instance }: any) => {
            setReferenceTabListContent(instance.$refs.content);
          },
        }"
      >
        <PrimeTab
          v-for="(tab, index) in tabs"
          :key="tab.label"
          :ref="(el: any) => setReferenceTab(el, index)"
          :value="tab.value"
          class="typography-text-sm w-[142px] xl:w-[120px]"
          @click="updateActiveTab(tab.value, index)"
        >
          {{ tab.label }}
        </PrimeTab>
      </PrimeTabList>
    </PrimeTabs>
    <div v-else class="w-full text-center text-secondary-700">
      {{ t('auth.forgotPassword.heading') }}
    </div>
  </template>
  <!-- ... 略 -->
</template>

<script setup lang="ts">
// ... 略
const formComponent = computed(() => {
  const forms: Record<FormComponentName, Component> = {
    login: AccountFormsLogin,
    register: AccountFormsRegister,
    forgetPassword: AccountFormsForgetPassword,
    socialLoginRegister: AccountFormsRegister,
  };
  return forms[activeTab.value as FormComponentName];
});
</script>

修改 createCustomer input 的格式

<!-- components/Account/Forms/AccountFormsRegister.vue -->

<script setup lang="ts">
// ... 略
const { $accountModal } = useNuxtApp();
const { initialRegisterValues, isSocialLoginRegister } = $accountModal;

const onSubmit = handleSubmit((values) => {
  const {
    lastname, email, password, gender, date_of_birth, phone,
  } = values;

  const baseInput = {
    lastname,
    email,
    gender,
    dob: formatDate(date_of_birth),
    is_subscribed: isSubscribed.value,
    phone,
  };

  const input: ExtendCustomerCreateInput = isSocialLoginRegister.value
    ? {
      ...baseInput,
      type: initialRegisterValues.value.type!,
      uid: initialRegisterValues.value.uid,
    }
    : {
      ...baseInput,
      type: 0,
      password,
    };

  createCustomerMutate(input);
});
</script>

社群帳號綁定組件

<!-- social binding page -->
<template>
  <NuxtLayout name="account">
    <p class="typography-text-sm mb-4 text-primary-800 xl:mb-5">
      {{ t('socialLogin.binding.description') }}
    </p>
    <NovaSocialBindingButtonGroup
      v-if="storeConfigStore.socialLoginConfig"
      :social-login-config="storeConfigStore.socialLoginConfig"
      :t="t"
      :button-size="isDesktop ? '' : 'small'"
      @on-binding-success="handleSocialBindingSuccess"
      @on-binding-error="handleSocialBindingError"
      @on-unbinding-success="handleSocialUnbindingSuccess"
      @on-unbinding-error="handleSocialUnbindingError"
    />
  </NuxtLayout>
</template>

<script setup lang="ts">
definePageMeta({
  name: 'customerSocialBinding',
  layout: false,
});

const { t } = useI18n();
const toast = useToast();
const { isDesktop } = useBreakpoints();

const storeConfigStore = useStoreConfigStore();

const { socialLoginEnabled } = useStoreConfigStore();
if (!socialLoginEnabled) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Page Not Found',
  });
}

const handleSocialBindingSuccess = () => {
  toast.add({
    severity: 'success',
    summary: t('socialLogin.binding.success'),
    life: 5000,
  });
};

const handleSocialBindingError = (error: Error) => {
  toast.add({
    severity: 'error',
    summary: t('socialLogin.binding.error'),
    detail: error.message,
    life: 5000,
  });
};

const handleSocialUnbindingSuccess = () => {
  toast.add({
    severity: 'success',
    summary: t('socialLogin.unbinding.success'),
    life: 5000,
  });
};

const handleSocialUnbindingError = (error: Error) => {
  toast.add({
    severity: 'error',
    summary: t('socialLogin.unbinding.error'),
    detail: error.message,
    life: 5000,
  });
};
</script>

自動註冊的組件

模組會自動註冊以下組件,可直接在模板中使用:

  • <NovaSocialLoginButtonGroup /> - 社群登入按鈕群組
  • <NovaSocialBindingButtonGroup /> - 社群帳號綁定介面
  • <IconFacebook /> - Facebook 圖示
  • <IconFacebookFilled /> - Facebook 填充圖示
  • <IconGoogle /> - Google 圖示
  • <IconLine /> - LINE 圖示

Composables API

useSocialLogin

核心 API composable,提供所有社群登入相關的 API 方法。

const {
  fetchSocialLoginV2,
  fetchCustomerSocialAccount,
  socialBinding,
  socialUnbinding
} = useSocialLogin();

// 驗證社群登入
const result = await fetchSocialLoginV2({
  sns_type: 1, // 1: Google, 2: LINE, 3: Facebook
  auth_code: 'xxx'
});

// 獲取用戶社群帳號列表
const accounts = await fetchCustomerSocialAccount();

// 綁定社群帳號
await socialBinding({ sns_type: 1, auth_code: 'xxx' });

// 解綁社群帳號
await socialUnbinding({ sns_type: 1 });

useSocialLoginOAuth

處理 OAuth 流程

const {
  handleOAuthConnect,
  setOAuthRedirectUri,
  generateState,
  buildOAuthUrl
} = useSocialLoginOAuth(config);

// 設定登入成功後的重定向 URL
setOAuthRedirectUri();

// 發起 OAuth 連接
await handleOAuthConnect('google');

useSocialBinding

社群帳號綁定管理

const {
  fetchCustomerSocialAccountsQuery,
  socialBindingMutation,
  socialUnbindingMutation,
  isOAuthCallback
} = useSocialBinding();

// 使用 Vue Query 獲取社群帳號
const { data: accounts } = fetchCustomerSocialAccountsQuery();

// 綁定 mutation
const { mutate: bind } = socialBindingMutation();

// 解綁 mutation  
const { mutate: unbind } = socialUnbindingMutation();

useSocialLoginVerification

處理登入驗證流程

const { processLoginResult } = useSocialLoginVerification();

// 處理 OAuth 回調結果
await processLoginResult({
  handleExistCustomer: (data) => { /* 處理已存在用戶 */ },
  openLoginModal: () => { /* 開啟登入彈窗 */ },
  handleNewCustomer: (data) => { /* 處理新用戶註冊 */ },
  handleError: (error) => { /* 錯誤處理 */ }
});

useSocialLoginButtonGroupStyle

登入按鈕樣式配置

const {
  containerClasses,
  containerStyles,
  buttonClasses,
  buttonStyles,
  iconClasses,
  iconStyles,
  textClasses,
  textStyles,
} = useSocialLoginButtonGroupStyle(styleConfig);

useSocialBindingButtonGroupStyle

綁定介面樣式配置

const {
  containerClasses,
  containerStyles,
  cardClasses,
  cardStyles,
  labelClasses,
  labelStyles,
  buttonClasses,
  buttonStyles,
  bindingButtonClasses,
  bindingButtonStyles,
  iconClasses,
  iconStyles,
  textClasses,
  textStyles,
} = useSocialBindingButtonGroupStyle(styleConfig);

樣式配置

社群帳號登入樣式配置

組件支援通過 style-config prop 自訂樣式

<template>
  <NovaSocialLoginButtonGroup 
    :social-login-config="storeConfigStore.socialLoginConfig"
    :style-config="customStyleConfig"
    :show-text="true"
  />
</template>

<script setup lang="ts">
const customStyleConfig = {
  // 容器樣式
  containerClass: 'grid grid-cols-1 md:grid-cols-3 gap-6',
  containerStyle: {
    padding: '1rem',
    backgroundColor: '#f9fafb'
  },
  
  // 按鈕樣式
  buttonClass: 'w-full py-3 px-6 rounded-xl font-semibold transition-all duration-200 hover:scale-105',
  buttonStyle: {
    boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
    border: 'none'
  },
  
  // 圖示樣式
  iconClass: 'w-6 h-6',
  iconStyle: {
    filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'
  },
  
  // 文字樣式
  textClass: 'text-lg font-medium',
  textStyle: {
    letterSpacing: '0.025em'
  }
};
</script>

社群帳號綁定樣式配置

<template>
  <NovaSocialBindingButtonGroup 
    :social-login-config="socialLoginConfig"
    :style-config="bindingStyleConfig"
  />
</template>

<script setup lang="ts">
const bindingStyleConfig = {
  // 容器樣式
  containerClass: 'max-w-2xl mx-auto space-y-6',
  
  // 卡片樣式
  cardClass: 'bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden',
  cardStyle: {
    transition: 'all 0.3s ease',
  },
  
  // 標籤區域樣式
  labelClass: 'flex items-center gap-4 p-6',
  
  // 綁定按鈕樣式
  bindingButtonClass: 'px-6 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors',
  
  // 解綁按鈕樣式
  buttonClass: 'px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors',
  
  // 圖示樣式
  iconClass: 'w-8 h-8',
  
  // 文字樣式
  textClass: 'text-xl font-semibold text-gray-800'
};
</script>

Server Routes

模組會自動註冊以下 server route:

  • GET /social-login/callback - OAuth 回調處理
    • 此路由處理 OAuth provider 的回調,並將認證結果存儲在 cookie 中。

組件 API 參考

NovaSocialLoginButtonGroup

Props

| 屬性 | 類型 | 預設值 | 說明 | |------|------|--------|------| | socialLoginConfig | SocialLoginConfig | - | 社群登入配置(必填) | | styleConfig | SocialLoginButtonGroupStyleConfig | {} | 樣式配置物件 | | showText | boolean | false | 是否顯示按鈕文字 | | t | (key: string) => string | (key) => key | 國際化函數 |

Slots

每個社群都有獨立的插槽,可以完全替換該社群的按鈕

| 插槽名稱 | 作用域參數 | 說明 | |---------|------------|------| | google | { provider, handleOAuthLogin, clientId } | 自定義 Google 登入按鈕 | | facebook | { provider, handleOAuthLogin, clientId } | 自定義 Facebook 登入按鈕 | | line | { provider, handleOAuthLogin, clientId } | 自定義 LINE 登入按鈕 |

在預設按鈕內部可以自定義特定元素

| 插槽名稱 | 說明 | |---------|------| | icon | 自定義按鈕圖示 | | text | 自定義按鈕文字 |

NovaSocialBindingButtonGroup

Props

| 屬性 | 類型 | 預設值 | 說明 | |------|------|--------|------| | socialLoginConfig | SocialBindingConfig | - | 社群登入配置(必填) | | styleConfig | SocialBindingButtonGroupStyleConfig | {} | 樣式配置物件 | | t | (key: string) => string | (key) => key | 國際化函數 | | buttonSize | string | '' | 按鈕尺寸(支援 PrimeVue Button 的 size 屬性) |

Slots

每個社群都有獨立的插槽,可以完全替換該社群的綁定卡片

| 插槽名稱 | 作用域參數 | 說明 | |---------|------------|------| | google | { provider, clientId } | 自定義 Google 綁定卡片 | | facebook | { provider, clientId } | 自定義 Facebook 綁定卡片 | | line | { provider, clientId } | 自定義 LINE 綁定卡片 |

在預設卡片內部可以自定義特定元素

| 插槽名稱 | 說明 | |---------|------| | label | 自定義標籤區域(包含圖示和名稱) | | icon | 自定義社群圖示 | | text | 自定義社群名稱 | | button | 自定義操作按鈕 |

Events

| 事件名稱 | 參數 | 說明 | |---------|------|------| | onBindingSuccess | - | 帳號綁定成功 | | onBindingError | error: Error | 帳號綁定失敗 | | onUnbindingSuccess | - | 帳號解綁成功 | | onUnbindingError | error: Error | 帳號解綁失敗 |

類型定義

SocialLoginConfig

interface SocialLoginConfig {
  google?: {
    client_id: string;
    sort_order: number;
    uid?: string; // 已綁定時才有
  };
  facebook?: {
    client_id: string;
    sort_order: number;
    uid?: string;
  };
  line?: {
    client_id: string;
    sort_order: number;
    uid?: string;
    promote_line_oa?: boolean; // LINE 官方帳號推廣
  };
}

SocialLoginType

enum SocialLoginType {
  REGULAR = 0,
  GOOGLE = 1,
  LINE = 2,
  FACEBOOK = 3,
}

授權聲明

此套件僅供 Astral Web 內部使用,未經授權不得用於其他用途。