Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dynamic clientId Initialization #61

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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'

Expand Down
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ const SideBar: DefaultTheme.Sidebar = [
},
],
},
{
text: "Methods",
items: [{ text: "setGoogleClientId", link: "/methods/#setgoogleclientid" }],
},
{
text: "Helpers",
items: [
Expand Down
12 changes: 12 additions & 0 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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'

Expand Down
31 changes: 31 additions & 0 deletions docs/methods/index.md
Original file line number Diff line number Diff line change
@@ -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");
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ import useCodeClient from "./composables/useCodeClient";
import { ref } from "vue";
import useTokenClient from "./composables/useTokenClient";
import useOneTap from "./composables/useOneTap";
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);
};
Expand Down
19 changes: 15 additions & 4 deletions src/components/GoogleSignInButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<GoogleSignInButtonProps>();
const emits = defineEmits<{
Expand Down Expand Up @@ -233,16 +233,18 @@ const emits = defineEmits<{
(e: "promptMomentNotification", notification: PromptMomentNotification): void;
}>();

const clientId = inject<string>(GoogleClientIdKey);
const clientId = inject(GoogleClientIdKey);
const targetElement = ref<HTMLElement | null>(null);
const isReady = ref(false);
const { scriptLoaded } = useGsiScript();

watchEffect(() => {
if (!scriptLoaded.value) return;
if (clientId?.value) isReady.value = true;

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.credential) {
emits("error");
Expand Down Expand Up @@ -294,8 +296,17 @@ onUnmounted(() => {
});

const height = computed(() => buttonContainerHeight[props.size || "large"]);
const pointerEvents = computed(() => (isReady.value ? "initial" : "none"));
</script>

<template>
<div ref="targetElement" :style="{ display: 'inline-flex', height }"></div>
<div
ref="targetElement"
:style="{
display: 'inline-flex',
verticalAlign: 'top',
height,
pointerEvents,
}"
></div>
</template>
21 changes: 13 additions & 8 deletions src/composables/useCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { isClientIdValid, validateInitializeSetup } from "@/utils/validations";

/**
* On success with implicit flow
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -106,14 +107,20 @@ export default function useCodeClient(
const { scope = "", onError, onSuccess, ...rest } = options;

const { scriptLoaded } = useGsiScript();
const clientId = inject<string>(GoogleClientIdKey);
const clientId = inject(GoogleClientIdKey);
const isReady = ref(false);
const codeRequestRedirectUrl = ref<string | null>(null);
let client: CodeClient | undefined;

const login = () => {
if (!isClientIdValid(isReady.value, clientId?.value)) return;

client?.requestCode();
};

watchEffect(() => {
isReady.value = false;
if (!scriptLoaded.value) return;
if (!validateInitializeSetup(scriptLoaded.value, clientId?.value)) return;

const scopeValue = unref(scope);
const scopes = Array.isArray(scopeValue)
Expand All @@ -122,15 +129,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);
Expand All @@ -145,7 +150,7 @@ export default function useCodeClient(

return {
isReady: readonly(isReady),
login: () => client?.requestCode(),
login,
codeRequestRedirectUrl: readonly(codeRequestRedirectUrl),
};
}
14 changes: 8 additions & 6 deletions src/composables/useOneTap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { isClientIdValid, validateInitializeSetup } from "@/utils/validations";

export interface UseGoogleOneTapLoginOptions {
/**
Expand Down Expand Up @@ -190,18 +191,20 @@ export default function useOneTap(
} = options || {};

const { scriptLoaded } = useGsiScript();
const clientId = inject<string>(GoogleClientIdKey);
const clientId = inject(GoogleClientIdKey);
const isReady = ref(false);

const login = () =>
isReady.value &&
const login = () => {
if (!isClientIdValid(isReady.value, clientId?.value)) return;

window.google?.accounts.id.prompt((notification) =>
onPromptMomentNotification?.(notification),
);
};

watchEffect((onCleanup) => {
isReady.value = false;
if (!scriptLoaded.value) return;
if (!validateInitializeSetup(scriptLoaded.value, clientId?.value)) return;

const shouldAutoLogin = !unref(disableAutomaticPrompt);

Expand All @@ -216,8 +219,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.credential) {
onError?.();
Expand Down
21 changes: 13 additions & 8 deletions src/composables/useTokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ 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 { GoogleClientIdKey } from "../utils/symbols";
import { isClientIdValid, validateInitializeSetup } from "@/utils/validations";

/**
* Success response
Expand Down Expand Up @@ -78,7 +79,7 @@ export interface UseTokenClientReturn {
*
* @memberof UseTokenClientReturn
*/
login: (overrideConfig?: OverridableTokenClientConfig) => void | undefined;
login: (overrideConfig?: OverridableTokenClientConfig) => void;
}

/**
Expand All @@ -97,22 +98,27 @@ export default function useTokenClient(
const { scope = "", onError, onSuccess, ...rest } = options;

const { scriptLoaded } = useGsiScript();
const clientId = inject<string>(GoogleClientIdKey);
const clientId = inject(GoogleClientIdKey);
const isReady = ref(false);
let client: TokenClient | undefined;

const login = (overrideConfig?: OverridableTokenClientConfig) => {
if (!isClientIdValid(isReady.value, clientId?.value)) return;

client?.requestAccessToken(overrideConfig);
};

watchEffect(() => {
isReady.value = false;
if (!scriptLoaded.value) return;
if (!validateInitializeSetup(scriptLoaded.value, clientId?.value)) return;

const scopeValue = unref(scope);
const scopes = Array.isArray(scopeValue)
? scopeValue.join(" ")
: 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);
Expand All @@ -127,7 +133,6 @@ export default function useTokenClient(

return {
isReady: readonly(isReady),
login: (overrideConfig?: OverridableTokenClientConfig) =>
client?.requestAccessToken(overrideConfig),
login,
};
}
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PLUGIN_NAME = "GoogleSignInPlugin";
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
5 changes: 5 additions & 0 deletions src/methods/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { googleClientIdRef } from "@/states";

export const setGoogleClientId = (id: string) => {
googleClientIdRef.value = id;
};
Loading