@rdlabo/ionic-angular-kit 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -1
- package/fesm2022/rdlabo-ionic-angular-kit-auth-firebase-social.mjs +218 -0
- package/fesm2022/rdlabo-ionic-angular-kit-auth-firebase-social.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit-auth-firebase.mjs +340 -0
- package/fesm2022/rdlabo-ionic-angular-kit-auth-firebase.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit-printer.mjs +146 -0
- package/fesm2022/rdlabo-ionic-angular-kit-printer.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit-review.mjs +48 -0
- package/fesm2022/rdlabo-ionic-angular-kit-review.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit-theme.mjs +163 -0
- package/fesm2022/rdlabo-ionic-angular-kit-theme.mjs.map +1 -0
- package/fesm2022/rdlabo-ionic-angular-kit.mjs +3 -321
- package/fesm2022/rdlabo-ionic-angular-kit.mjs.map +1 -1
- package/package.json +37 -1
- package/types/rdlabo-ionic-angular-kit-auth-firebase-social.d.ts +104 -0
- package/types/rdlabo-ionic-angular-kit-auth-firebase.d.ts +266 -0
- package/types/rdlabo-ionic-angular-kit-printer.d.ts +81 -0
- package/types/rdlabo-ionic-angular-kit-review.d.ts +45 -0
- package/types/rdlabo-ionic-angular-kit-theme.d.ts +116 -0
- package/types/rdlabo-ionic-angular-kit.d.ts +3 -234
package/README.md
CHANGED
|
@@ -180,12 +180,22 @@ alertConfirm(options: {
|
|
|
180
180
|
|
|
181
181
|
`watchKeyboard: true` (on `presentModal` options) expands a bottom sheet to full height when the native keyboard appears (iOS/Android only; no-op on web).
|
|
182
182
|
|
|
183
|
+
**How `presentModal` decides required vs. optional props.** Props are inferred from the component's `input()` fields, and whether each prop is **required** or **optional** is decided by a single rule: *does the input's type include `undefined`?* A default value is not "optional" — providing a default removes `undefined` from the input's type, so a defaulted input becomes a **required** prop.
|
|
184
|
+
|
|
185
|
+
| Declaration | Input type | Includes `undefined`? | Prop |
|
|
186
|
+
| ------------------------ | --------------- | --------------------- | --------------------------------- |
|
|
187
|
+
| `input.required<T>()` | `T` | No | required |
|
|
188
|
+
| `input<T>(defaultValue)` | `T` | No | **required** ← a default makes it required |
|
|
189
|
+
| `input<T>()` (no arg) | `T \| undefined`| Yes | optional |
|
|
190
|
+
|
|
191
|
+
To make a prop **optional**, drop the default and use a bare `input<T>()` (its type is `T | undefined`), then apply your fallback where you read it (e.g. `this.enabled() ?? true`). If a component has at least one required input, the `componentProps` argument itself becomes mandatory; if it has no required inputs, `componentProps` may be omitted; a component with no `input()` fields at all accepts loose, untyped props.
|
|
192
|
+
|
|
183
193
|
**Best practice — the modal launcher pattern.** Never call `modalController.create(...)` inline in a component. Instead, each modal/popover page exports a typed launcher next to itself and every call site goes through `KitOverlayController`:
|
|
184
194
|
|
|
185
195
|
```typescript
|
|
186
196
|
// detail.page.ts — component declares its return type:
|
|
187
197
|
export class DetailPage {
|
|
188
|
-
declare static modalReturn: DetailResult;
|
|
198
|
+
declare static readonly modalReturn: DetailResult;
|
|
189
199
|
readonly item = input.required<Item>();
|
|
190
200
|
}
|
|
191
201
|
|
|
@@ -540,6 +550,60 @@ await BrotherPrint.printImage({ ...settings, port: channel.port, channelInfo: ch
|
|
|
540
550
|
|
|
541
551
|
---
|
|
542
552
|
|
|
553
|
+
### Firebase auth (`@rdlabo/ionic-angular-kit/auth-firebase`)
|
|
554
|
+
|
|
555
|
+
A secondary entry point so only apps that use it pull in `@angular/fire` and `firebase`. It exists to **isolate `@angular/fire`**: the SDK is touched in exactly one place — the DI provider — so apps import `KIT_FIREBASE_AUTH` and call these functions, never `@angular/fire` directly. That keeps the eventual `@angular/fire` → modular `firebase/auth` swap provider-local.
|
|
556
|
+
|
|
557
|
+
**Design principle: the kit performs no UI.** Every function runs the Firebase operation and nothing else; loading overlays, prompts and error alerts are app side effects. The flow functions take the uniform lifecycle hooks `{ before, success, error, finally }` and, rather than throwing, resolve value flows to `null` and boolean flows to `false`, handing the raw error to the `error` hook so the app presents it from its own dictionary. For anything the functions don't express, drop down to `firebase/auth` directly.
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// app.config.ts — @angular/fire lives only here
|
|
561
|
+
provideKitFirebase({ firebaseConfig: environment.firebase }),
|
|
562
|
+
provideKitFirebaseAnalytics(),
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import { inject, Injectable } from '@angular/core';
|
|
567
|
+
import {
|
|
568
|
+
KIT_FIREBASE_AUTH, kitSignIn, kitSignOut, kitResolveAuthStatus, kitReauthWithRetry,
|
|
569
|
+
} from '@rdlabo/ionic-angular-kit/auth-firebase';
|
|
570
|
+
import { updatePassword } from 'firebase/auth'; // escape hatch for the reauth mutation
|
|
571
|
+
|
|
572
|
+
@Injectable({ providedIn: 'root' })
|
|
573
|
+
export class AuthService {
|
|
574
|
+
readonly #auth = inject(KIT_FIREBASE_AUTH);
|
|
575
|
+
|
|
576
|
+
// Simple flow: hooks carry the app's side effects; errors go to the app's own dictionary.
|
|
577
|
+
signIn(email: string, password: string) {
|
|
578
|
+
return kitSignIn(this.#auth, email, password, {
|
|
579
|
+
error: (e) => this.presentError(e),
|
|
580
|
+
success: () => this.nav.navigateRoot('/'),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Re-auth: the kit owns only the re-auth + wrong-password-retry mechanic; the app supplies
|
|
585
|
+
// the password prompt and the loading overlay, and catches the thrown (non-wrong-password) error.
|
|
586
|
+
async changePassword(currentEmail: string, newPassword: string) {
|
|
587
|
+
const ok = await kitReauthWithRetry(this.#auth, currentEmail, {
|
|
588
|
+
prompt: (retry) => this.promptPassword(retry),
|
|
589
|
+
mutate: (user) => updatePassword(user, newPassword),
|
|
590
|
+
withLoading: (run) => this.withLoading(run),
|
|
591
|
+
}).catch((e) => (this.presentError(e), false));
|
|
592
|
+
if (ok) this.overlay.alertClose({ header: 'Saved', message: '…' });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Surface:
|
|
598
|
+
|
|
599
|
+
- **DI** — `KIT_FIREBASE_AUTH` (`InjectionToken<Auth>`), `provideKitFirebase({ firebaseConfig })`, `provideKitFirebaseAnalytics()`.
|
|
600
|
+
- **Flow functions** (uniform hooks + no-throw null/false) — `kitSignIn`, `kitSignUp` (create + send verification), `kitSignOut`, `kitSendPasswordReset`, `kitSendEmailVerification`, `kitUnlinkProvider`.
|
|
601
|
+
- **Mechanics** — `kitReauthWithRetry` (app injects `prompt` / `withLoading` / `mutate`; boolean result, non-wrong-password errors thrown), `kitResolveAuthStatus` (`'user' | 'confirm' | 'required'` from the user; social counts as verified; `allowWhen` bypass), `kitAuthState`, `kitGetIdToken`.
|
|
602
|
+
- **Error dictionary** — `KIT_DEFAULT_AUTH_TEXT` (importable canonical constant; the kit does not present it — the app renders its own alert).
|
|
603
|
+
- **Social** (`@rdlabo/ionic-angular-kit/auth-firebase/social`, separate nested entry to isolate the Capacitor plugins) — `kitFacebookLogin`, `kitAppleLogin`, `kitFacebookLogout`; options carry the same `{ before, success, error, finally }` hooks (`success` receives the identity payload for a backend call).
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
543
607
|
## Consumer Vitest setup notes
|
|
544
608
|
|
|
545
609
|
When testing a consumer app that declares `@rdlabo/ionic-angular-kit` as a `file:` symlink dependency, add the following to your `vitest.config.ts`:
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { signInWithCredential, linkWithCredential, reauthenticateWithCredential, EmailAuthProvider, OAuthProvider, FacebookAuthProvider, reauthenticateWithPopup, signInWithPopup, linkWithPopup } from 'firebase/auth';
|
|
2
|
+
import { Capacitor } from '@capacitor/core';
|
|
3
|
+
import { FacebookLogin } from '@capacitor-community/facebook-login';
|
|
4
|
+
import { SignInWithApple } from '@capacitor-community/apple-sign-in';
|
|
5
|
+
|
|
6
|
+
/** Generate a random nonce for the Facebook OIDC (Limited Login) flow. */
|
|
7
|
+
const generateNonce = (length = 16) => {
|
|
8
|
+
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
9
|
+
let nonce = '';
|
|
10
|
+
for (let i = 0; i < length; i++) {
|
|
11
|
+
nonce += charset[Math.floor(Math.random() * charset.length)];
|
|
12
|
+
}
|
|
13
|
+
return nonce;
|
|
14
|
+
};
|
|
15
|
+
const classifyOAuthError = (e) => {
|
|
16
|
+
const code = e?.code;
|
|
17
|
+
if (code === 'auth/credential-already-in-use') {
|
|
18
|
+
return 'already-in-use';
|
|
19
|
+
}
|
|
20
|
+
if (code === 'auth/user-cancelled') {
|
|
21
|
+
return 'cancelled';
|
|
22
|
+
}
|
|
23
|
+
return 'other';
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The shared 3-mode credential state machine (internal).
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* `new` signs in with the credential, `link` links it, `credential` re-authenticates with the social
|
|
30
|
+
* credential and then links an email/password. Any Firebase error is classified and handed to
|
|
31
|
+
* `error` (returning `false`); on success the app's `success` hook (backend + feedback) runs with the
|
|
32
|
+
* identity payload, and it returns `true`.
|
|
33
|
+
*/
|
|
34
|
+
const applyOAuthCredential = async (auth, credential, mode, effects) => {
|
|
35
|
+
try {
|
|
36
|
+
if (mode.mode === 'new') {
|
|
37
|
+
await signInWithCredential(auth, credential);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const user = auth.currentUser;
|
|
41
|
+
if (!user) {
|
|
42
|
+
throw new Error('kit social: no signed-in user to link/re-authenticate');
|
|
43
|
+
}
|
|
44
|
+
if (mode.mode === 'link') {
|
|
45
|
+
await linkWithCredential(user, credential);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await reauthenticateWithCredential(user, credential);
|
|
49
|
+
await linkWithCredential(user, EmailAuthProvider.credential(mode.emailLogin.email, mode.emailLogin.password));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
await effects.error(classifyOAuthError(e), e);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
await effects.success();
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
/** Await one animation frame where available (iOS WebView crash workaround; no-op off-browser). */
|
|
61
|
+
const nextFrame = () => new Promise((resolve) => {
|
|
62
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
63
|
+
requestAnimationFrame(() => resolve());
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
resolve();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Facebook login / link, bundled: native plugin → credential → the shared 3-mode state machine.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* On iOS the credential is built from the OIDC token with a nonce (`OAuthProvider('facebook.com')`);
|
|
74
|
+
* elsewhere from the access token (`FacebookAuthProvider`). Returns `{ status: false }` on a
|
|
75
|
+
* cancelled/failed plugin login or a handled Firebase error (the app was already notified via the
|
|
76
|
+
* hooks).
|
|
77
|
+
*/
|
|
78
|
+
const kitFacebookLogin = async (auth, options) => {
|
|
79
|
+
await options.before?.();
|
|
80
|
+
try {
|
|
81
|
+
const nonce = generateNonce();
|
|
82
|
+
const event = await FacebookLogin.login({ permissions: options.permissions, nonce }).catch(() => undefined);
|
|
83
|
+
await nextFrame();
|
|
84
|
+
if (!event || !event.accessToken?.token) {
|
|
85
|
+
return { status: false };
|
|
86
|
+
}
|
|
87
|
+
const accessToken = event.accessToken.token;
|
|
88
|
+
const credential = Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'ios'
|
|
89
|
+
? new OAuthProvider('facebook.com').credential({ rawNonce: nonce, idToken: accessToken })
|
|
90
|
+
: FacebookAuthProvider.credential(accessToken);
|
|
91
|
+
const status = await applyOAuthCredential(auth, credential, options, {
|
|
92
|
+
success: () => options.success?.({ accessToken, mode: options.mode }),
|
|
93
|
+
error: (category, error) => options.error?.(category, error),
|
|
94
|
+
});
|
|
95
|
+
return { status };
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await options.finally?.();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Log out of the Facebook SDK (best-effort; errors are ignored).
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* Apps that offer Facebook login typically call this alongside the Firebase sign-out, so it lives
|
|
106
|
+
* here to keep the `@capacitor-community/facebook-login` import out of the app.
|
|
107
|
+
*/
|
|
108
|
+
const kitFacebookLogout = () => FacebookLogin.logout().catch(() => undefined);
|
|
109
|
+
/**
|
|
110
|
+
* Sign in with Apple / link, bundled. Native uses the plugin; the web uses the Firebase popup.
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* - **Native**: `SignInWithApple.authorize()` → `OAuthProvider('apple.com')` credential → the shared
|
|
114
|
+
* 3-mode state machine.
|
|
115
|
+
* - **Web**: `signInWithPopup` / `linkWithPopup` (with `email`/`name` scopes), or, for `credential`,
|
|
116
|
+
* `reauthenticateWithPopup` then link the email/password. The identity payload for the backend is
|
|
117
|
+
* synthesized from the popup result.
|
|
118
|
+
*
|
|
119
|
+
* Every failure path (including popup errors) is routed through `onError`.
|
|
120
|
+
*/
|
|
121
|
+
const kitAppleLogin = async (auth, options) => {
|
|
122
|
+
await options.before?.();
|
|
123
|
+
try {
|
|
124
|
+
if (Capacitor.isNativePlatform()) {
|
|
125
|
+
const authorize = await SignInWithApple.authorize().catch(() => undefined);
|
|
126
|
+
if (!authorize) {
|
|
127
|
+
return { status: false };
|
|
128
|
+
}
|
|
129
|
+
const r = authorize.response;
|
|
130
|
+
const response = {
|
|
131
|
+
user: r.user ?? null,
|
|
132
|
+
email: r.email ?? null,
|
|
133
|
+
givenName: r.givenName ?? null,
|
|
134
|
+
familyName: r.familyName ?? null,
|
|
135
|
+
identityToken: r.identityToken ?? null,
|
|
136
|
+
authorizationCode: r.authorizationCode ?? null,
|
|
137
|
+
};
|
|
138
|
+
const credential = new OAuthProvider('apple.com').credential({ idToken: response.identityToken ?? undefined });
|
|
139
|
+
const status = await applyOAuthCredential(auth, credential, options, {
|
|
140
|
+
success: () => options.success?.({ response, mode: options.mode }),
|
|
141
|
+
error: (category, error) => options.error?.(category, error),
|
|
142
|
+
});
|
|
143
|
+
return { status };
|
|
144
|
+
}
|
|
145
|
+
// Web: the popup performs the sign-in/link itself.
|
|
146
|
+
const provider = new OAuthProvider('apple.com');
|
|
147
|
+
provider.addScope('email');
|
|
148
|
+
provider.addScope('name');
|
|
149
|
+
if (options.mode === 'credential') {
|
|
150
|
+
const user = auth.currentUser;
|
|
151
|
+
try {
|
|
152
|
+
if (!user) {
|
|
153
|
+
throw new Error('kit social: no signed-in user to re-authenticate');
|
|
154
|
+
}
|
|
155
|
+
await reauthenticateWithPopup(user, provider);
|
|
156
|
+
await linkWithCredential(user, EmailAuthProvider.credential(options.emailLogin.email, options.emailLogin.password));
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
await options.error?.(classifyOAuthError(e), e);
|
|
160
|
+
return { status: false };
|
|
161
|
+
}
|
|
162
|
+
await options.success?.({ response: emptyAppleResponse(), mode: 'credential' });
|
|
163
|
+
return { status: true };
|
|
164
|
+
}
|
|
165
|
+
let result;
|
|
166
|
+
try {
|
|
167
|
+
result =
|
|
168
|
+
options.mode === 'new'
|
|
169
|
+
? await signInWithPopup(auth, provider)
|
|
170
|
+
: await linkWithPopup(requireUser(auth), provider);
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
await options.error?.(classifyOAuthError(e), e);
|
|
174
|
+
return { status: false };
|
|
175
|
+
}
|
|
176
|
+
const credential = OAuthProvider.credentialFromResult(result);
|
|
177
|
+
const response = {
|
|
178
|
+
...emptyAppleResponse(),
|
|
179
|
+
email: result.user?.email ?? null,
|
|
180
|
+
identityToken: credential?.idToken ?? null,
|
|
181
|
+
authorizationCode: credential?.accessToken ?? null,
|
|
182
|
+
};
|
|
183
|
+
await options.success?.({ response, mode: options.mode });
|
|
184
|
+
return { status: true };
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await options.finally?.();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const emptyAppleResponse = () => ({
|
|
191
|
+
user: null,
|
|
192
|
+
email: null,
|
|
193
|
+
givenName: null,
|
|
194
|
+
familyName: null,
|
|
195
|
+
identityToken: null,
|
|
196
|
+
authorizationCode: null,
|
|
197
|
+
});
|
|
198
|
+
const requireUser = (auth) => {
|
|
199
|
+
const user = auth.currentUser;
|
|
200
|
+
if (!user) {
|
|
201
|
+
throw new Error('kit social: no signed-in user to link');
|
|
202
|
+
}
|
|
203
|
+
return user;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Social login (Facebook / Apple). Its own entry point so only apps that use it pull in the native
|
|
207
|
+
// plugins `@capacitor-community/facebook-login` and `@capacitor-community/apple-sign-in`; the core
|
|
208
|
+
// `auth-firebase` entry stays free of them.
|
|
209
|
+
//
|
|
210
|
+
// Public surface is curated: two bundled flows plus their option types. The nonce util, the error
|
|
211
|
+
// classifier and the 3-mode credential state machine are internal implementation details.
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generated bundle index. Do not edit.
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
export { kitAppleLogin, kitFacebookLogin, kitFacebookLogout };
|
|
218
|
+
//# sourceMappingURL=rdlabo-ionic-angular-kit-auth-firebase-social.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rdlabo-ionic-angular-kit-auth-firebase-social.mjs","sources":["../../../projects/kit/auth-firebase/social/src/kit-social.ts","../../../projects/kit/auth-firebase/social/src/public-api.ts","../../../projects/kit/auth-firebase/social/src/rdlabo-ionic-angular-kit-auth-firebase-social.ts"],"sourcesContent":["import type { Auth, AuthCredential } from 'firebase/auth';\nimport {\n EmailAuthProvider,\n FacebookAuthProvider,\n linkWithCredential,\n linkWithPopup,\n OAuthProvider,\n reauthenticateWithCredential,\n reauthenticateWithPopup,\n signInWithCredential,\n signInWithPopup,\n} from 'firebase/auth';\nimport { Capacitor } from '@capacitor/core';\nimport { FacebookLogin } from '@capacitor-community/facebook-login';\nimport { SignInWithApple } from '@capacitor-community/apple-sign-in';\n\n/** How a social-credential failure is classified for the app's error hook. */\nexport type KitOAuthErrorCategory = 'already-in-use' | 'cancelled' | 'other';\n\n/** The mode a social login runs in. */\nexport type KitOAuthModeName = 'new' | 'link' | 'credential';\n\n/**\n * The mode discriminator. `'credential'` links an email/password to the (re-authenticated) social\n * account, so it requires the new email/password; `'new'` / `'link'` do not.\n */\nexport type KitOAuthMode =\n | { mode: 'new' }\n | { mode: 'link' }\n | { mode: 'credential'; emailLogin: { email: string; password: string } };\n\n/**\n * The apple identity payload handed to the `success` hook for the backend call. Populated from the\n * native plugin on device, or synthesized from the popup result on the web.\n */\nexport interface KitAppleResponse {\n user: string | null;\n email: string | null;\n givenName: string | null;\n familyName: string | null;\n identityToken: string | null;\n authorizationCode: string | null;\n}\n\n/**\n * The uniform lifecycle hooks for a social flow — the same `before / success / error / finally`\n * shape as {@link KitFirebaseAuthService}'s hooks, so a call site reads the same everywhere. All are\n * optional; the kit renders nothing itself.\n *\n * @typeParam Info - the identity payload handed to {@link success} (Facebook access token / Apple\n * response), so an app can notify its backend and give feedback in one place.\n *\n * @remarks\n * `before` runs before the plugin login starts, `success` after the mode's Firebase op succeeds\n * (carrying the identity payload — do the backend call and the toast here), `error` on a classified\n * failure (`'cancelled'` is passed through so the app can stay silent on a user cancel), and\n * `finally` always. The kit swallows none of these errors.\n */\ninterface KitSocialHooks<Info> {\n before?: () => void | Promise<unknown>;\n success?: (info: Info) => void | Promise<unknown>;\n error?: (category: KitOAuthErrorCategory, error: unknown) => void | Promise<unknown>;\n finally?: () => void | Promise<unknown>;\n}\n\n/** Options for {@link kitFacebookLogin}. */\nexport type KitFacebookLoginOptions = KitOAuthMode &\n KitSocialHooks<{ accessToken: string; mode: KitOAuthModeName }> & {\n /** Facebook permissions to request. */\n permissions: string[];\n };\n\n/** Options for {@link kitAppleLogin}. */\nexport type KitAppleLoginOptions = KitOAuthMode & KitSocialHooks<{ response: KitAppleResponse; mode: KitOAuthModeName }>;\n\n/** Generate a random nonce for the Facebook OIDC (Limited Login) flow. */\nconst generateNonce = (length = 16): string => {\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let nonce = '';\n for (let i = 0; i < length; i++) {\n nonce += charset[Math.floor(Math.random() * charset.length)];\n }\n return nonce;\n};\n\nconst classifyOAuthError = (e: unknown): KitOAuthErrorCategory => {\n const code = (e as { code?: string } | undefined)?.code;\n if (code === 'auth/credential-already-in-use') {\n return 'already-in-use';\n }\n if (code === 'auth/user-cancelled') {\n return 'cancelled';\n }\n return 'other';\n};\n\n/**\n * The shared 3-mode credential state machine (internal).\n *\n * @remarks\n * `new` signs in with the credential, `link` links it, `credential` re-authenticates with the social\n * credential and then links an email/password. Any Firebase error is classified and handed to\n * `error` (returning `false`); on success the app's `success` hook (backend + feedback) runs with the\n * identity payload, and it returns `true`.\n */\nconst applyOAuthCredential = async (\n auth: Auth,\n credential: AuthCredential,\n mode: KitOAuthMode,\n effects: {\n success: () => void | Promise<unknown>;\n error: (category: KitOAuthErrorCategory, error: unknown) => void | Promise<unknown>;\n },\n): Promise<boolean> => {\n try {\n if (mode.mode === 'new') {\n await signInWithCredential(auth, credential);\n } else {\n const user = auth.currentUser;\n if (!user) {\n throw new Error('kit social: no signed-in user to link/re-authenticate');\n }\n if (mode.mode === 'link') {\n await linkWithCredential(user, credential);\n } else {\n await reauthenticateWithCredential(user, credential);\n await linkWithCredential(user, EmailAuthProvider.credential(mode.emailLogin.email, mode.emailLogin.password));\n }\n }\n } catch (e) {\n await effects.error(classifyOAuthError(e), e);\n return false;\n }\n await effects.success();\n return true;\n};\n\n/** Await one animation frame where available (iOS WebView crash workaround; no-op off-browser). */\nconst nextFrame = (): Promise<void> =>\n new Promise<void>((resolve) => {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => resolve());\n } else {\n resolve();\n }\n });\n\n/**\n * Facebook login / link, bundled: native plugin → credential → the shared 3-mode state machine.\n *\n * @remarks\n * On iOS the credential is built from the OIDC token with a nonce (`OAuthProvider('facebook.com')`);\n * elsewhere from the access token (`FacebookAuthProvider`). Returns `{ status: false }` on a\n * cancelled/failed plugin login or a handled Firebase error (the app was already notified via the\n * hooks).\n */\nexport const kitFacebookLogin = async (\n auth: Auth,\n options: KitFacebookLoginOptions,\n): Promise<{ status: boolean }> => {\n await options.before?.();\n try {\n const nonce = generateNonce();\n const event = await FacebookLogin.login({ permissions: options.permissions, nonce }).catch(() => undefined);\n await nextFrame();\n if (!event || !event.accessToken?.token) {\n return { status: false };\n }\n const accessToken = event.accessToken.token;\n const credential: AuthCredential =\n Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'ios'\n ? new OAuthProvider('facebook.com').credential({ rawNonce: nonce, idToken: accessToken })!\n : FacebookAuthProvider.credential(accessToken);\n\n const status = await applyOAuthCredential(auth, credential, options, {\n success: () => options.success?.({ accessToken, mode: options.mode }),\n error: (category, error) => options.error?.(category, error),\n });\n return { status };\n } finally {\n await options.finally?.();\n }\n};\n\n/**\n * Log out of the Facebook SDK (best-effort; errors are ignored).\n *\n * @remarks\n * Apps that offer Facebook login typically call this alongside the Firebase sign-out, so it lives\n * here to keep the `@capacitor-community/facebook-login` import out of the app.\n */\nexport const kitFacebookLogout = (): Promise<void> => FacebookLogin.logout().catch(() => undefined);\n\n/**\n * Sign in with Apple / link, bundled. Native uses the plugin; the web uses the Firebase popup.\n *\n * @remarks\n * - **Native**: `SignInWithApple.authorize()` → `OAuthProvider('apple.com')` credential → the shared\n * 3-mode state machine.\n * - **Web**: `signInWithPopup` / `linkWithPopup` (with `email`/`name` scopes), or, for `credential`,\n * `reauthenticateWithPopup` then link the email/password. The identity payload for the backend is\n * synthesized from the popup result.\n *\n * Every failure path (including popup errors) is routed through `onError`.\n */\nexport const kitAppleLogin = async (auth: Auth, options: KitAppleLoginOptions): Promise<{ status: boolean }> => {\n await options.before?.();\n try {\n if (Capacitor.isNativePlatform()) {\n const authorize = await SignInWithApple.authorize().catch(() => undefined);\n if (!authorize) {\n return { status: false };\n }\n const r = authorize.response;\n const response: KitAppleResponse = {\n user: r.user ?? null,\n email: r.email ?? null,\n givenName: r.givenName ?? null,\n familyName: r.familyName ?? null,\n identityToken: r.identityToken ?? null,\n authorizationCode: r.authorizationCode ?? null,\n };\n const credential = new OAuthProvider('apple.com').credential({ idToken: response.identityToken ?? undefined })!;\n const status = await applyOAuthCredential(auth, credential, options, {\n success: () => options.success?.({ response, mode: options.mode }),\n error: (category, error) => options.error?.(category, error),\n });\n return { status };\n }\n\n // Web: the popup performs the sign-in/link itself.\n const provider = new OAuthProvider('apple.com');\n provider.addScope('email');\n provider.addScope('name');\n\n if (options.mode === 'credential') {\n const user = auth.currentUser;\n try {\n if (!user) {\n throw new Error('kit social: no signed-in user to re-authenticate');\n }\n await reauthenticateWithPopup(user, provider);\n await linkWithCredential(\n user,\n EmailAuthProvider.credential(options.emailLogin.email, options.emailLogin.password),\n );\n } catch (e) {\n await options.error?.(classifyOAuthError(e), e);\n return { status: false };\n }\n await options.success?.({ response: emptyAppleResponse(), mode: 'credential' });\n return { status: true };\n }\n\n let result;\n try {\n result =\n options.mode === 'new'\n ? await signInWithPopup(auth, provider)\n : await linkWithPopup(requireUser(auth), provider);\n } catch (e) {\n await options.error?.(classifyOAuthError(e), e);\n return { status: false };\n }\n const credential = OAuthProvider.credentialFromResult(result);\n const response: KitAppleResponse = {\n ...emptyAppleResponse(),\n email: result.user?.email ?? null,\n identityToken: credential?.idToken ?? null,\n authorizationCode: credential?.accessToken ?? null,\n };\n await options.success?.({ response, mode: options.mode });\n return { status: true };\n } finally {\n await options.finally?.();\n }\n};\n\nconst emptyAppleResponse = (): KitAppleResponse => ({\n user: null,\n email: null,\n givenName: null,\n familyName: null,\n identityToken: null,\n authorizationCode: null,\n});\n\nconst requireUser = (auth: Auth) => {\n const user = auth.currentUser;\n if (!user) {\n throw new Error('kit social: no signed-in user to link');\n }\n return user;\n};\n","// Social login (Facebook / Apple). Its own entry point so only apps that use it pull in the native\n// plugins `@capacitor-community/facebook-login` and `@capacitor-community/apple-sign-in`; the core\n// `auth-firebase` entry stays free of them.\n//\n// Public surface is curated: two bundled flows plus their option types. The nonce util, the error\n// classifier and the 3-mode credential state machine are internal implementation details.\nexport * from './kit-social';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|