From 90a7457decdb36a035b15285b8e2f119687ee4fa Mon Sep 17 00:00:00 2001 From: zuhno Date: Mon, 1 Jan 2024 23:51:34 +0900 Subject: [PATCH 1/8] wip: dynamic update client_id --- src/App.vue | 6 ++++++ src/components/GoogleSignInButton.vue | 6 +++--- src/composables/useCodeClient.ts | 25 +++++++++++++++++------- src/composables/useOneTap.ts | 19 +++++++++++++----- src/composables/useTokenClient.ts | 23 ++++++++++++++++------ src/plugin.ts | 28 +++++++++------------------ src/utils/constants.ts | 1 + src/utils/index.ts | 1 + src/utils/logs.ts | 3 +++ src/utils/symbols.ts | 4 ++-- 10 files changed, 74 insertions(+), 42 deletions(-) create mode 100644 src/utils/constants.ts create mode 100644 src/utils/logs.ts diff --git a/src/App.vue b/src/App.vue index f8a7ca9..ecf36aa 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,6 +5,7 @@ import useCodeClient from "./composables/useCodeClient"; import { ref } from "vue"; import useTokenClient from "./composables/useTokenClient"; import useOneTap from "./composables/useOneTap"; +import { setGoogleClientId } from "./plugin"; const scope = ref(""); @@ -16,6 +17,11 @@ const onLoginError = () => { console.error("Login failed"); }; +(async () => { + await new Promise((r) => setTimeout(r, 3000)); + setGoogleClientId(import.meta.env.VITE_GOOGLE_CLIENT_ID); +})(); + const { isReady: isCodeClientReady, login: loginCodeClient } = useCodeClient({ scope, onSuccess: (resp) => console.log(resp), diff --git a/src/components/GoogleSignInButton.vue b/src/components/GoogleSignInButton.vue index 471aec5..1344617 100644 --- a/src/components/GoogleSignInButton.vue +++ b/src/components/GoogleSignInButton.vue @@ -233,16 +233,16 @@ const emits = defineEmits<{ (e: "promptMomentNotification", notification: PromptMomentNotification): void; }>(); -const clientId = inject(GoogleClientIdKey); +const clientId = inject(GoogleClientIdKey); const targetElement = ref(null); const { scriptLoaded } = useGsiScript(); watchEffect(() => { if (!scriptLoaded.value) return; + if (!clientId?.value) return; window.google?.accounts.id.initialize({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - client_id: clientId!, + client_id: clientId.value, callback: (credentialResponse: CredentialResponse) => { if (!credentialResponse.clientId || !credentialResponse.credential) { emits("error"); diff --git a/src/composables/useCodeClient.ts b/src/composables/useCodeClient.ts index c6faa2e..1466425 100644 --- a/src/composables/useCodeClient.ts +++ b/src/composables/useCodeClient.ts @@ -8,6 +8,7 @@ import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; import type { MaybeRef } from "@/utils/types"; import { buildCodeRequestRedirectUrl } from "../utils/oauth2"; +import { toPluginError } from "@/utils/logs"; /** * On success with implicit flow @@ -78,7 +79,7 @@ export interface UseCodeClientReturn { * * @memberof UseCodeClientReturn */ - login: () => void | undefined; + login: () => void; /** * Get a URL to perform code request without actually redirecting user. @@ -106,14 +107,26 @@ export default function useCodeClient( const { scope = "", onError, onSuccess, ...rest } = options; const { scriptLoaded } = useGsiScript(); - const clientId = inject(GoogleClientIdKey); + const clientId = inject(GoogleClientIdKey); const isReady = ref(false); const codeRequestRedirectUrl = ref(null); let client: CodeClient | undefined; + const login = () => { + if (!isReady.value) + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize." + ) + ); + + client?.requestCode(); + }; + watchEffect(() => { isReady.value = false; if (!scriptLoaded.value) return; + if (!clientId?.value) return; const scopeValue = unref(scope); const scopes = Array.isArray(scopeValue) @@ -122,15 +135,13 @@ export default function useCodeClient( const computedScopes = `openid email profile ${scopes}`; codeRequestRedirectUrl.value = buildCodeRequestRedirectUrl({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - client_id: clientId!, + client_id: clientId.value, scope: computedScopes, ...rest, }); client = window.google?.accounts.oauth2.initCodeClient({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - client_id: clientId!, + client_id: clientId.value, scope: computedScopes, callback: (response: CodeResponse) => { if (response.error) return onError?.(response); @@ -145,7 +156,7 @@ export default function useCodeClient( return { isReady: readonly(isReady), - login: () => client?.requestCode(), + login, codeRequestRedirectUrl: readonly(codeRequestRedirectUrl), }; } diff --git a/src/composables/useOneTap.ts b/src/composables/useOneTap.ts index b4f00a4..1a5663a 100644 --- a/src/composables/useOneTap.ts +++ b/src/composables/useOneTap.ts @@ -8,6 +8,7 @@ import type { } from "@/interfaces/accounts"; import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; +import { toPluginError } from "@/utils/logs"; export interface UseGoogleOneTapLoginOptions { /** @@ -190,18 +191,27 @@ export default function useOneTap( } = options || {}; const { scriptLoaded } = useGsiScript(); - const clientId = inject(GoogleClientIdKey); + const clientId = inject(GoogleClientIdKey); const isReady = ref(false); - const login = () => - isReady.value && + const login = () => { + if (!isReady.value) + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize." + ) + ); + window.google?.accounts.id.prompt((notification) => onPromptMomentNotification?.(notification) ); + }; watchEffect((onCleanup) => { isReady.value = false; + console.log("clientId : ", clientId?.value); if (!scriptLoaded.value) return; + if (!clientId?.value) return; const shouldAutoLogin = !unref(disableAutomaticPrompt); @@ -216,8 +226,7 @@ export default function useOneTap( const cancel_on_tap_outside = unref(cancelOnTapOutside); window.google?.accounts.id.initialize({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - client_id: clientId!, + client_id: clientId.value, callback: (credentialResponse: CredentialResponse) => { if (!credentialResponse.clientId || !credentialResponse.credential) { onError?.(); diff --git a/src/composables/useTokenClient.ts b/src/composables/useTokenClient.ts index 12024cb..682513c 100644 --- a/src/composables/useTokenClient.ts +++ b/src/composables/useTokenClient.ts @@ -8,6 +8,7 @@ import type { import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "../utils/symbols"; import type { MaybeRef } from "@/utils/types"; +import { toPluginError } from "@/utils/logs"; /** * Success response @@ -78,7 +79,7 @@ export interface UseTokenClientReturn { * * @memberof UseTokenClientReturn */ - login: (overrideConfig?: OverridableTokenClientConfig) => void | undefined; + login: (overrideConfig?: OverridableTokenClientConfig) => void; } /** @@ -97,13 +98,25 @@ export default function useTokenClient( const { scope = "", onError, onSuccess, ...rest } = options; const { scriptLoaded } = useGsiScript(); - const clientId = inject(GoogleClientIdKey); + const clientId = inject(GoogleClientIdKey); const isReady = ref(false); let client: TokenClient | undefined; + const login = (overrideConfig?: OverridableTokenClientConfig) => { + if (!isReady.value) + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize." + ) + ); + + client?.requestAccessToken(overrideConfig); + }; + watchEffect(() => { isReady.value = false; if (!scriptLoaded.value) return; + if (!clientId?.value) return; const scopeValue = unref(scope); const scopes = Array.isArray(scopeValue) @@ -111,8 +124,7 @@ export default function useTokenClient( : scopeValue; client = window.google?.accounts.oauth2.initTokenClient({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - client_id: clientId!, + client_id: clientId.value, scope: `openid email profile ${scopes}`, callback: (response: TokenResponse) => { if (response.error) return onError?.(response); @@ -127,7 +139,6 @@ export default function useTokenClient( return { isReady: readonly(isReady), - login: (overrideConfig?: OverridableTokenClientConfig) => - client?.requestAccessToken(overrideConfig), + login, }; } diff --git a/src/plugin.ts b/src/plugin.ts index 11a81e5..fc9c2f8 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,4 @@ -import type { App, Plugin } from "vue"; +import { type App, type Plugin, ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; import GoogleSignInButton from "./components/GoogleSignInButton.vue"; import useGsiScript from "./composables/useGsiScript"; @@ -25,8 +25,6 @@ import type { AuthCodeFlowSuccessResponse, } from "./composables/useTokenClient"; -export const PLUGIN_NAME = "GoogleSignInPlugin"; - export interface GoogleSignInPluginOptions { /** * This field is your application's client ID, which is found and created in the Google Developers Console @@ -38,24 +36,16 @@ export interface GoogleSignInPluginOptions { clientId: string; } -const toPluginError = (err: string) => `[${PLUGIN_NAME}]: ${err}`; - -const plugin: Plugin = { - install(app: App, options: GoogleSignInPluginOptions) { - if (!options) { - throw new Error( - toPluginError(`initialize plugin by passing an options object`) - ); - } +const googleClientIdRef = ref(); - if ( - !options.clientId || - (options.clientId && options.clientId.trim().length === 0) - ) { - throw new Error(toPluginError("clientId is required to initialize")); - } +export const setGoogleClientId = (id: string) => { + googleClientIdRef.value = id; +}; - app.provide(GoogleClientIdKey, options.clientId); +const plugin: Plugin = { + install(app: App, options?: GoogleSignInPluginOptions) { + googleClientIdRef.value = options?.clientId; + app.provide(GoogleClientIdKey, googleClientIdRef); app.component("GoogleSignInButton", GoogleSignInButton); }, }; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..b816d3d --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1 @@ +export const PLUGIN_NAME = "GoogleSignInPlugin"; diff --git a/src/utils/index.ts b/src/utils/index.ts index 0f33c2a..41e624b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from "./account"; export * from "./oauth2"; export * from "./types"; +export * from "./constants"; diff --git a/src/utils/logs.ts b/src/utils/logs.ts new file mode 100644 index 0000000..24bebfa --- /dev/null +++ b/src/utils/logs.ts @@ -0,0 +1,3 @@ +import { PLUGIN_NAME } from "./constants"; + +export const toPluginError = (err: string) => `[${PLUGIN_NAME}]: ${err}`; diff --git a/src/utils/symbols.ts b/src/utils/symbols.ts index c8847a7..2a3174c 100644 --- a/src/utils/symbols.ts +++ b/src/utils/symbols.ts @@ -1,3 +1,3 @@ -import type { InjectionKey } from "vue"; +import type { InjectionKey, Ref } from "vue"; -export const GoogleClientIdKey = Symbol() as InjectionKey; +export const GoogleClientIdKey = Symbol() as InjectionKey>; From 2eec0777d8bde311cfb04ed609ca1bcba4b1469e Mon Sep 17 00:00:00 2001 From: zuhno Date: Wed, 3 Jan 2024 00:31:02 +0900 Subject: [PATCH 2/8] feat: dynamic clientId --- src/App.vue | 13 +++++++------ src/components/GoogleSignInButton.vue | 19 +++++++++++++++---- src/composables/useCodeClient.ts | 17 +++++++++++------ src/composables/useOneTap.ts | 18 +++++++++++------- src/composables/useTokenClient.ts | 17 +++++++++++------ .../constants.ts => constants/index.ts} | 0 src/main.ts | 4 ++++ src/methods/index.ts | 5 +++++ src/plugin.ts | 11 ++++------- src/states/index.ts | 3 +++ src/utils/index.ts | 1 - src/utils/logs.ts | 2 +- 12 files changed, 72 insertions(+), 38 deletions(-) rename src/{utils/constants.ts => constants/index.ts} (100%) create mode 100644 src/methods/index.ts create mode 100644 src/states/index.ts diff --git a/src/App.vue b/src/App.vue index ecf36aa..b9ed1cd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,10 +5,16 @@ import useCodeClient from "./composables/useCodeClient"; import { ref } from "vue"; import useTokenClient from "./composables/useTokenClient"; import useOneTap from "./composables/useOneTap"; -import { setGoogleClientId } from "./plugin"; +import { setGoogleClientId } from "@/methods"; const scope = ref(""); +// Dynamic setting GoogleClientId +(async () => { + await new Promise((r) => setTimeout(r, 3000)); + setGoogleClientId(import.meta.env.VITE_GOOGLE_CLIENT_ID); +})(); + const onLoginSuccess = (resp: CredentialResponse) => { console.log("Login successful", resp); }; @@ -17,11 +23,6 @@ const onLoginError = () => { console.error("Login failed"); }; -(async () => { - await new Promise((r) => setTimeout(r, 3000)); - setGoogleClientId(import.meta.env.VITE_GOOGLE_CLIENT_ID); -})(); - const { isReady: isCodeClientReady, login: loginCodeClient } = useCodeClient({ scope, onSuccess: (resp) => console.log(resp), diff --git a/src/components/GoogleSignInButton.vue b/src/components/GoogleSignInButton.vue index 1344617..eb83dee 100644 --- a/src/components/GoogleSignInButton.vue +++ b/src/components/GoogleSignInButton.vue @@ -198,7 +198,7 @@ interface GoogleSignInButtonProps { locale?: string; } -const buttonContainerHeight = { large: 40, medium: 32, small: 20 }; +const buttonContainerHeight = { large: "40px", medium: "32px", small: "20px" }; const props = defineProps(); const emits = defineEmits<{ @@ -235,14 +235,16 @@ const emits = defineEmits<{ const clientId = inject(GoogleClientIdKey); const targetElement = ref(null); +const isReady = ref(false); const { scriptLoaded } = useGsiScript(); watchEffect(() => { if (!scriptLoaded.value) return; - if (!clientId?.value) return; + if (clientId?.value) isReady.value = true; window.google?.accounts.id.initialize({ - client_id: clientId.value, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + client_id: clientId!.value, callback: (credentialResponse: CredentialResponse) => { if (!credentialResponse.clientId || !credentialResponse.credential) { emits("error"); @@ -294,8 +296,17 @@ onUnmounted(() => { }); const height = computed(() => buttonContainerHeight[props.size || "large"]); +const pointerEvents = computed(() => (isReady.value ? "initial" : "none")); diff --git a/src/composables/useCodeClient.ts b/src/composables/useCodeClient.ts index 1466425..58879e9 100644 --- a/src/composables/useCodeClient.ts +++ b/src/composables/useCodeClient.ts @@ -113,12 +113,17 @@ export default function useCodeClient( let client: CodeClient | undefined; const login = () => { - if (!isReady.value) - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize." - ) - ); + if (!isReady.value) { + if (!clientId?.value) { + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize." + ) + ); + } + + return; + } client?.requestCode(); }; diff --git a/src/composables/useOneTap.ts b/src/composables/useOneTap.ts index 1a5663a..ab54aca 100644 --- a/src/composables/useOneTap.ts +++ b/src/composables/useOneTap.ts @@ -195,12 +195,17 @@ export default function useOneTap( const isReady = ref(false); const login = () => { - if (!isReady.value) - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize." - ) - ); + if (!isReady.value) { + if (!clientId?.value) { + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize." + ) + ); + } + + return; + } window.google?.accounts.id.prompt((notification) => onPromptMomentNotification?.(notification) @@ -209,7 +214,6 @@ export default function useOneTap( watchEffect((onCleanup) => { isReady.value = false; - console.log("clientId : ", clientId?.value); if (!scriptLoaded.value) return; if (!clientId?.value) return; diff --git a/src/composables/useTokenClient.ts b/src/composables/useTokenClient.ts index 682513c..9b4f635 100644 --- a/src/composables/useTokenClient.ts +++ b/src/composables/useTokenClient.ts @@ -103,12 +103,17 @@ export default function useTokenClient( let client: TokenClient | undefined; const login = (overrideConfig?: OverridableTokenClientConfig) => { - if (!isReady.value) - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize." - ) - ); + if (!isReady.value) { + if (!clientId?.value) { + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize." + ) + ); + } + + return; + } client?.requestAccessToken(overrideConfig); }; diff --git a/src/utils/constants.ts b/src/constants/index.ts similarity index 100% rename from src/utils/constants.ts rename to src/constants/index.ts diff --git a/src/main.ts b/src/main.ts index 74934df..747f9ce 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,8 +4,12 @@ import GoogleOauth2Plugin from "./plugin"; const app = createApp(App); +// for static app.use(GoogleOauth2Plugin, { clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID, }); +// for dynamic +// app.use(GoogleOauth2Plugin); + app.mount("#app"); diff --git a/src/methods/index.ts b/src/methods/index.ts new file mode 100644 index 0000000..2fad125 --- /dev/null +++ b/src/methods/index.ts @@ -0,0 +1,5 @@ +import { googleClientIdRef } from "@/states"; + +export const setGoogleClientId = (id: string) => { + googleClientIdRef.value = id; +}; diff --git a/src/plugin.ts b/src/plugin.ts index fc9c2f8..de45a96 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,4 @@ -import { type App, type Plugin, ref } from "vue"; +import type { App, Plugin } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; import GoogleSignInButton from "./components/GoogleSignInButton.vue"; import useGsiScript from "./composables/useGsiScript"; @@ -25,6 +25,8 @@ import type { AuthCodeFlowSuccessResponse, } from "./composables/useTokenClient"; +import { googleClientIdRef } from "./states"; + export interface GoogleSignInPluginOptions { /** * This field is your application's client ID, which is found and created in the Google Developers Console @@ -36,12 +38,6 @@ export interface GoogleSignInPluginOptions { clientId: string; } -const googleClientIdRef = ref(); - -export const setGoogleClientId = (id: string) => { - googleClientIdRef.value = id; -}; - const plugin: Plugin = { install(app: App, options?: GoogleSignInPluginOptions) { googleClientIdRef.value = options?.clientId; @@ -72,6 +68,7 @@ export type { AuthCodeFlowErrorResponse, AuthCodeFlowSuccessResponse, }; +export * from "./methods"; export * from "./@types/globals"; export default plugin; diff --git a/src/states/index.ts b/src/states/index.ts new file mode 100644 index 0000000..309d9b5 --- /dev/null +++ b/src/states/index.ts @@ -0,0 +1,3 @@ +import { ref } from "vue"; + +export const googleClientIdRef = ref(); diff --git a/src/utils/index.ts b/src/utils/index.ts index 41e624b..0f33c2a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,3 @@ export * from "./account"; export * from "./oauth2"; export * from "./types"; -export * from "./constants"; diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 24bebfa..4cff8a7 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -1,3 +1,3 @@ -import { PLUGIN_NAME } from "./constants"; +import { PLUGIN_NAME } from "@/constants"; export const toPluginError = (err: string) => `[${PLUGIN_NAME}]: ${err}`; From 84ff98c58b3bf653f7e6b96cd2f7917be6a8bbd0 Mon Sep 17 00:00:00 2001 From: zuhno Date: Wed, 3 Jan 2024 00:31:58 +0900 Subject: [PATCH 3/8] docs: for setGoogleClientId method --- docs/.vitepress/config.ts | 4 ++++ docs/guide/index.md | 12 ++++++++++++ docs/methods/index.md | 31 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 docs/methods/index.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c35d8b6..aefb968 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -58,6 +58,10 @@ const SideBar: DefaultTheme.Sidebar = [ }, ], }, + { + text: "Methods", + items: [{ text: "setGoogleClientId", link: "/methods/#setgoogleclientid" }], + }, { text: "Helpers", items: [ diff --git a/docs/guide/index.md b/docs/guide/index.md index 16009ab..09143ee 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -47,15 +47,23 @@ pnpm add vue3-google-signin Setting up the library is very simple. In your application entry point(`main.js` or `main.ts`) put the following code. +::: tip +:bulb: If you wish to dynamically add the `clientId`, you can use the `setGoogleClientId` method in your application. +::: + ```js // rest of the code import GoogleSignInPlugin from "vue3-google-signin" +// for static clientId app.use(GoogleSignInPlugin, { clientId: 'CLIENT ID OBTAINED FROM GOOGLE API CONSOLE', }); +// for dynamic clientId +app.use(GoogleSignInPlugin); + // other config app.mount("#app"); @@ -91,6 +99,10 @@ pnpm add nuxt-vue3-google-signin Now you can add following entry to the `nuxt.config.ts`(or `nuxt.config.js`) +::: warning +The feature to dynamically add the `clientId` has not yet been implemented in this library. +::: + ```ts import { defineNuxtConfig } from 'nuxt/config' diff --git a/docs/methods/index.md b/docs/methods/index.md new file mode 100644 index 0000000..b5f1114 --- /dev/null +++ b/docs/methods/index.md @@ -0,0 +1,31 @@ +--- +title: Methods +--- + +# Methods + +## setGoogleClientId() + +- **Type** + +```ts +function setGoogleClientId(id: string): void; +``` + +- **Details** + +This `setGoogleClientId` is used with the **vue3-google-signin** plugin to initialize the `clientId` dynamically, rather than at the time of installing the app. +_For example, it can be used when retrieving the clientId from a backend API._ + +:::tip +:bulb: While the `clientId` value is being loaded, the `isReady` value returned by the [**useCodeClient**](/composables/use-code-client), [**useTokenClient**](/composables/use-token-client), and [**useOneTap**](/composables/use-one-tap) hooks will be **false**. Additionally, the [**GoogleSignInButton**](/components/google-signin-button) component will have its `pointer-events` set to '**none**', and will become clickable after the loading process is complete. +::: + +- **Example** + +```ts +// some component +import { setGoogleClientId } from "vue3-google-signin"; + +setGoogleClientId("CLIENT ID OBTAINED FROM GOOGLE API CONSOLE"); +``` From 35fa5cf8482baa7340feecf386e96c9bfc9441c5 Mon Sep 17 00:00:00 2001 From: zuhno Date: Sat, 9 Mar 2024 08:56:35 +0900 Subject: [PATCH 4/8] docs: add dynamic client id information to README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e5f62aa..53fbe8d 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,14 @@ put the following code. import GoogleSignInPlugin from "vue3-google-signin" +// for static clientId app.use(GoogleSignInPlugin, { clientId: 'CLIENT ID OBTAINED FROM GOOGLE API CONSOLE', }); +// for dynamic clientId +app.use(GoogleSignInPlugin); + // other config app.mount("#app"); @@ -75,6 +79,8 @@ pnpm add nuxt-vue3-google-signin Now you can add following entry to the `nuxt.config.ts`(or `nuxt.config.js`) +_The feature to dynamically add the `clientId` has not yet been implemented in this library._ + ```ts import { defineNuxtConfig } from 'nuxt/config' From 32d2e293f5ef8e2217059a3bef5e3acd71109258 Mon Sep 17 00:00:00 2001 From: zuhno Date: Tue, 20 Aug 2024 10:40:08 +0900 Subject: [PATCH 5/8] style: apply ESLint rules to fix formatting issues --- src/composables/useCodeClient.ts | 4 ++-- src/composables/useOneTap.ts | 4 ++-- src/composables/useTokenClient.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/composables/useCodeClient.ts b/src/composables/useCodeClient.ts index 0899382..40c231d 100644 --- a/src/composables/useCodeClient.ts +++ b/src/composables/useCodeClient.ts @@ -117,8 +117,8 @@ export default function useCodeClient( if (!clientId?.value) { throw new Error( toPluginError( - "Set clientId in options or use setClientId to initialize." - ) + "Set clientId in options or use setClientId to initialize.", + ), ); } diff --git a/src/composables/useOneTap.ts b/src/composables/useOneTap.ts index b4cc70b..defdcc5 100644 --- a/src/composables/useOneTap.ts +++ b/src/composables/useOneTap.ts @@ -199,8 +199,8 @@ export default function useOneTap( if (!clientId?.value) { throw new Error( toPluginError( - "Set clientId in options or use setClientId to initialize." - ) + "Set clientId in options or use setClientId to initialize.", + ), ); } diff --git a/src/composables/useTokenClient.ts b/src/composables/useTokenClient.ts index 422cc49..e3c23e8 100644 --- a/src/composables/useTokenClient.ts +++ b/src/composables/useTokenClient.ts @@ -107,8 +107,8 @@ export default function useTokenClient( if (!clientId?.value) { throw new Error( toPluginError( - "Set clientId in options or use setClientId to initialize." - ) + "Set clientId in options or use setClientId to initialize.", + ), ); } From 71ba0cfe38bab903904e339722d71e2650e31465 Mon Sep 17 00:00:00 2001 From: zuhno Date: Fri, 1 Nov 2024 00:18:58 +0900 Subject: [PATCH 6/8] refactor: modularization of the validation part for login and initial Setup --- package-lock.json | 4 ++-- src/composables/useCodeClient.ts | 24 ++++++++---------------- src/composables/useOneTap.ts | 22 +++++++--------------- src/composables/useTokenClient.ts | 24 ++++++++---------------- src/utils/validations.ts | 22 ++++++++++++++++++++++ 5 files changed, 47 insertions(+), 49 deletions(-) create mode 100644 src/utils/validations.ts diff --git a/package-lock.json b/package-lock.json index e7cb622..88c514a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vue3-google-signin", - "version": "1.3.4", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vue3-google-signin", - "version": "1.3.4", + "version": "2.0.0", "license": "MIT", "dependencies": { "vite-plugin-dts": "^3.7.3" diff --git a/src/composables/useCodeClient.ts b/src/composables/useCodeClient.ts index 40c231d..04694d3 100644 --- a/src/composables/useCodeClient.ts +++ b/src/composables/useCodeClient.ts @@ -8,7 +8,10 @@ import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; import type { MaybeRef } from "@/utils/types"; import { buildCodeRequestRedirectUrl } from "../utils/oauth2"; -import { toPluginError } from "@/utils/logs"; +import { + validateInitializeSetup, + validateLoginSetup, +} from "@/utils/validations"; /** * On success with implicit flow @@ -113,25 +116,14 @@ export default function useCodeClient( let client: CodeClient | undefined; const login = () => { - if (!isReady.value) { - if (!clientId?.value) { - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize.", - ), - ); - } - - return; - } + if (!validateLoginSetup(isReady.value, clientId?.value)) return; client?.requestCode(); }; watchEffect(() => { isReady.value = false; - if (!scriptLoaded.value) return; - if (!clientId?.value) return; + if (!validateInitializeSetup(scriptLoaded.value, clientId?.value)) return; const scopeValue = unref(scope); const scopes = Array.isArray(scopeValue) @@ -140,13 +132,13 @@ export default function useCodeClient( const computedScopes = `openid email profile ${scopes}`; codeRequestRedirectUrl.value = buildCodeRequestRedirectUrl({ - client_id: clientId.value, + client_id: clientId!.value, scope: computedScopes, ...rest, }); client = window.google?.accounts.oauth2.initCodeClient({ - client_id: clientId.value, + client_id: clientId!.value, scope: computedScopes, callback: (response: CodeResponse) => { if (response.error) return onError?.(response); diff --git a/src/composables/useOneTap.ts b/src/composables/useOneTap.ts index defdcc5..c48cf23 100644 --- a/src/composables/useOneTap.ts +++ b/src/composables/useOneTap.ts @@ -8,7 +8,10 @@ import type { } from "@/interfaces/accounts"; import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; -import { toPluginError } from "@/utils/logs"; +import { + validateInitializeSetup, + validateLoginSetup, +} from "@/utils/validations"; export interface UseGoogleOneTapLoginOptions { /** @@ -195,17 +198,7 @@ export default function useOneTap( const isReady = ref(false); const login = () => { - if (!isReady.value) { - if (!clientId?.value) { - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize.", - ), - ); - } - - return; - } + if (!validateLoginSetup(isReady.value, clientId?.value)) return; window.google?.accounts.id.prompt((notification) => onPromptMomentNotification?.(notification), @@ -214,8 +207,7 @@ export default function useOneTap( watchEffect((onCleanup) => { isReady.value = false; - if (!scriptLoaded.value) return; - if (!clientId?.value) return; + if (!validateInitializeSetup(scriptLoaded.value, clientId?.value)) return; const shouldAutoLogin = !unref(disableAutomaticPrompt); @@ -230,7 +222,7 @@ export default function useOneTap( const cancel_on_tap_outside = unref(cancelOnTapOutside); window.google?.accounts.id.initialize({ - client_id: clientId.value, + client_id: clientId!.value, callback: (credentialResponse: CredentialResponse) => { if (!credentialResponse.credential) { onError?.(); diff --git a/src/composables/useTokenClient.ts b/src/composables/useTokenClient.ts index e3c23e8..70d705e 100644 --- a/src/composables/useTokenClient.ts +++ b/src/composables/useTokenClient.ts @@ -6,9 +6,12 @@ import type { OverridableTokenClientConfig, } from "@/interfaces/oauth2"; import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; -import { GoogleClientIdKey } from "../utils/symbols"; import type { MaybeRef } from "@/utils/types"; -import { toPluginError } from "@/utils/logs"; +import { GoogleClientIdKey } from "../utils/symbols"; +import { + validateInitializeSetup, + validateLoginSetup, +} from "@/utils/validations"; /** * Success response @@ -103,25 +106,14 @@ export default function useTokenClient( let client: TokenClient | undefined; const login = (overrideConfig?: OverridableTokenClientConfig) => { - if (!isReady.value) { - if (!clientId?.value) { - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize.", - ), - ); - } - - return; - } + if (!validateLoginSetup(isReady.value, clientId?.value)) return; client?.requestAccessToken(overrideConfig); }; watchEffect(() => { isReady.value = false; - if (!scriptLoaded.value) return; - if (!clientId?.value) return; + if (!validateInitializeSetup(scriptLoaded.value, clientId?.value)) return; const scopeValue = unref(scope); const scopes = Array.isArray(scopeValue) @@ -129,7 +121,7 @@ export default function useTokenClient( : scopeValue; client = window.google?.accounts.oauth2.initTokenClient({ - client_id: clientId.value, + client_id: clientId!.value, scope: `openid email profile ${scopes}`, callback: (response: TokenResponse) => { if (response.error) return onError?.(response); diff --git a/src/utils/validations.ts b/src/utils/validations.ts new file mode 100644 index 0000000..d64356d --- /dev/null +++ b/src/utils/validations.ts @@ -0,0 +1,22 @@ +import { toPluginError } from "./logs"; + +export const validateLoginSetup = (isReady: boolean, clientId?: string) => { + if (!isReady) { + if (!clientId) { + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize.", + ), + ); + } + + return false; + } + + return true; +}; + +export const validateInitializeSetup = ( + isScriptLoad: boolean, + clientId?: string, +) => isScriptLoad && !!clientId; From 7d5893c584a1170e83432cae3e99eeb0d2df9609 Mon Sep 17 00:00:00 2001 From: zuhno Date: Fri, 1 Nov 2024 01:30:12 +0900 Subject: [PATCH 7/8] refactor: rename 'validateLoginSetup' to 'isClientIdValid' refactor: applied early return to 'isClientIdValid' --- src/composables/useCodeClient.ts | 7 ++----- src/composables/useOneTap.ts | 7 ++----- src/composables/useTokenClient.ts | 7 ++----- src/utils/validations.ts | 21 ++++++++------------- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/composables/useCodeClient.ts b/src/composables/useCodeClient.ts index 04694d3..6ef2c7c 100644 --- a/src/composables/useCodeClient.ts +++ b/src/composables/useCodeClient.ts @@ -8,10 +8,7 @@ import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; import type { MaybeRef } from "@/utils/types"; import { buildCodeRequestRedirectUrl } from "../utils/oauth2"; -import { - validateInitializeSetup, - validateLoginSetup, -} from "@/utils/validations"; +import { isClientIdValid, validateInitializeSetup } from "@/utils/validations"; /** * On success with implicit flow @@ -116,7 +113,7 @@ export default function useCodeClient( let client: CodeClient | undefined; const login = () => { - if (!validateLoginSetup(isReady.value, clientId?.value)) return; + if (!isClientIdValid(isReady.value, clientId?.value)) return; client?.requestCode(); }; diff --git a/src/composables/useOneTap.ts b/src/composables/useOneTap.ts index c48cf23..80def39 100644 --- a/src/composables/useOneTap.ts +++ b/src/composables/useOneTap.ts @@ -8,10 +8,7 @@ import type { } from "@/interfaces/accounts"; import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import { GoogleClientIdKey } from "@/utils/symbols"; -import { - validateInitializeSetup, - validateLoginSetup, -} from "@/utils/validations"; +import { isClientIdValid, validateInitializeSetup } from "@/utils/validations"; export interface UseGoogleOneTapLoginOptions { /** @@ -198,7 +195,7 @@ export default function useOneTap( const isReady = ref(false); const login = () => { - if (!validateLoginSetup(isReady.value, clientId?.value)) return; + if (!isClientIdValid(isReady.value, clientId?.value)) return; window.google?.accounts.id.prompt((notification) => onPromptMomentNotification?.(notification), diff --git a/src/composables/useTokenClient.ts b/src/composables/useTokenClient.ts index 70d705e..ef36153 100644 --- a/src/composables/useTokenClient.ts +++ b/src/composables/useTokenClient.ts @@ -8,10 +8,7 @@ import type { import { inject, unref, watchEffect, ref, readonly, type Ref } from "vue"; import type { MaybeRef } from "@/utils/types"; import { GoogleClientIdKey } from "../utils/symbols"; -import { - validateInitializeSetup, - validateLoginSetup, -} from "@/utils/validations"; +import { isClientIdValid, validateInitializeSetup } from "@/utils/validations"; /** * Success response @@ -106,7 +103,7 @@ export default function useTokenClient( let client: TokenClient | undefined; const login = (overrideConfig?: OverridableTokenClientConfig) => { - if (!validateLoginSetup(isReady.value, clientId?.value)) return; + if (!isClientIdValid(isReady.value, clientId?.value)) return; client?.requestAccessToken(overrideConfig); }; diff --git a/src/utils/validations.ts b/src/utils/validations.ts index d64356d..8ca4582 100644 --- a/src/utils/validations.ts +++ b/src/utils/validations.ts @@ -1,19 +1,14 @@ import { toPluginError } from "./logs"; -export const validateLoginSetup = (isReady: boolean, clientId?: string) => { - if (!isReady) { - if (!clientId) { - throw new Error( - toPluginError( - "Set clientId in options or use setClientId to initialize.", - ), - ); - } +export const isClientIdValid = (isReady: boolean, clientId?: string) => { + if (!clientId) + throw new Error( + toPluginError( + "Set clientId in options or use setClientId to initialize.", + ), + ); - return false; - } - - return true; + return isReady; }; export const validateInitializeSetup = ( From 88caa53b9f0d2569c9ff2ef58c7b7db7d6cc7be5 Mon Sep 17 00:00:00 2001 From: zuhno Date: Thu, 21 Nov 2024 16:19:24 +0900 Subject: [PATCH 8/8] refactor: dir structure style: align with code style guidelines --- src/App.vue | 2 +- src/{constants/index.ts => constant.ts} | 0 src/methods/index.ts | 5 ----- src/methods/setGoogleClientId.ts | 13 +++++++++++++ src/plugin.ts | 5 +++-- src/{states/index.ts => store.ts} | 0 src/utils/logs.ts | 6 ++++-- src/utils/validations.ts | 10 ++++++---- 8 files changed, 27 insertions(+), 14 deletions(-) rename src/{constants/index.ts => constant.ts} (100%) delete mode 100644 src/methods/index.ts create mode 100644 src/methods/setGoogleClientId.ts rename src/{states/index.ts => store.ts} (100%) diff --git a/src/App.vue b/src/App.vue index a4025c5..277f22d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,7 +5,7 @@ import useCodeClient from "./composables/useCodeClient"; import { ref } from "vue"; import useTokenClient from "./composables/useTokenClient"; import useOneTap from "./composables/useOneTap"; -import { setGoogleClientId } from "@/methods"; +import setGoogleClientId from "./methods/setGoogleClientId"; const scope = ref(""); diff --git a/src/constants/index.ts b/src/constant.ts similarity index 100% rename from src/constants/index.ts rename to src/constant.ts diff --git a/src/methods/index.ts b/src/methods/index.ts deleted file mode 100644 index 2fad125..0000000 --- a/src/methods/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { googleClientIdRef } from "@/states"; - -export const setGoogleClientId = (id: string) => { - googleClientIdRef.value = id; -}; diff --git a/src/methods/setGoogleClientId.ts b/src/methods/setGoogleClientId.ts new file mode 100644 index 0000000..2f5e366 --- /dev/null +++ b/src/methods/setGoogleClientId.ts @@ -0,0 +1,13 @@ +import { googleClientIdRef } from "@/store"; + +/** + * Dynamically set the Google OAuth client ID for the application. + * This method is primarily used with the `vue3-google-signin` plugin to configure the + * `clientId` after retrieving it dynamically. + * + * @export + * @param {string} id - The Google OAuth client ID to initialize. + */ +export default function setGoogleClientId(id: string) { + googleClientIdRef.value = id; +} diff --git a/src/plugin.ts b/src/plugin.ts index de45a96..473ef4e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -5,6 +5,7 @@ import useGsiScript from "./composables/useGsiScript"; import useCodeClient from "./composables/useCodeClient"; import useOneTap from "./composables/useOneTap"; import useTokenClient from "./composables/useTokenClient"; +import setGoogleClientId from "./methods/setGoogleClientId"; import type { ImplicitFlowOptions, @@ -25,7 +26,7 @@ import type { AuthCodeFlowSuccessResponse, } from "./composables/useTokenClient"; -import { googleClientIdRef } from "./states"; +import { googleClientIdRef } from "./store"; export interface GoogleSignInPluginOptions { /** @@ -52,6 +53,7 @@ export { useGsiScript, useTokenClient, useOneTap, + setGoogleClientId, }; export * from "./interfaces"; @@ -68,7 +70,6 @@ export type { AuthCodeFlowErrorResponse, AuthCodeFlowSuccessResponse, }; -export * from "./methods"; export * from "./@types/globals"; export default plugin; diff --git a/src/states/index.ts b/src/store.ts similarity index 100% rename from src/states/index.ts rename to src/store.ts diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 4cff8a7..ea9dd30 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -1,3 +1,5 @@ -import { PLUGIN_NAME } from "@/constants"; +import { PLUGIN_NAME } from "@/constant"; -export const toPluginError = (err: string) => `[${PLUGIN_NAME}]: ${err}`; +export function toPluginError(err: string) { + return `[${PLUGIN_NAME}]: ${err}`; +} diff --git a/src/utils/validations.ts b/src/utils/validations.ts index 8ca4582..52664d3 100644 --- a/src/utils/validations.ts +++ b/src/utils/validations.ts @@ -1,6 +1,6 @@ import { toPluginError } from "./logs"; -export const isClientIdValid = (isReady: boolean, clientId?: string) => { +export function isClientIdValid(isReady: boolean, clientId?: string) { if (!clientId) throw new Error( toPluginError( @@ -9,9 +9,11 @@ export const isClientIdValid = (isReady: boolean, clientId?: string) => { ); return isReady; -}; +} -export const validateInitializeSetup = ( +export function validateInitializeSetup( isScriptLoad: boolean, clientId?: string, -) => isScriptLoad && !!clientId; +) { + return isScriptLoad && !!clientId; +}