@vimine/sdk 0.2.0
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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/elements.d.ts +37 -0
- package/dist/elements.js +331 -0
- package/dist/elements.js.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.js +50 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated.d.ts +557 -0
- package/dist/generated.js +57 -0
- package/dist/generated.js.map +1 -0
- package/dist/index.d.ts +205 -0
- package/dist/index.js +401 -0
- package/dist/index.js.map +1 -0
- package/dist/pkce.d.ts +14 -0
- package/dist/pkce.js +34 -0
- package/dist/pkce.js.map +1 -0
- package/dist/public.d.ts +43 -0
- package/dist/public.js +59 -0
- package/dist/public.js.map +1 -0
- package/dist/react.d.ts +29 -0
- package/dist/react.js +55 -0
- package/dist/react.js.map +1 -0
- package/dist/server.d.ts +86 -0
- package/dist/server.js +146 -0
- package/dist/server.js.map +1 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +64 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +782 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
package/dist/public.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// The public surface of the API — no credentials, no sign-in: verify a card, read an association's public
|
|
2
|
+
// profile (branding, membership types, in-force statuto), validate a codice fiscale, fetch the card-token
|
|
3
|
+
// public keys. Usable from any page, from a scanner, from a server.
|
|
4
|
+
import { VimineError } from './errors.js';
|
|
5
|
+
export const DEFAULT_API_URL = 'https://api.vimine.network';
|
|
6
|
+
/** Query string of an events listing (shared by the public, browser and server clients). */
|
|
7
|
+
export const eventQuery = (params = {}) => {
|
|
8
|
+
const q = new URLSearchParams();
|
|
9
|
+
if (params.filter)
|
|
10
|
+
q.set('filter', params.filter);
|
|
11
|
+
if (params.from)
|
|
12
|
+
q.set('from', params.from);
|
|
13
|
+
if (params.to)
|
|
14
|
+
q.set('to', params.to);
|
|
15
|
+
if (params.limit)
|
|
16
|
+
q.set('limit', String(params.limit));
|
|
17
|
+
if (params.locale)
|
|
18
|
+
q.set('locale', params.locale);
|
|
19
|
+
const qs = q.toString();
|
|
20
|
+
return qs ? `?${qs}` : '';
|
|
21
|
+
};
|
|
22
|
+
/** `?locale=` of a single read. */
|
|
23
|
+
export const localeQuery = (params = {}) => (params.locale ? `?locale=${encodeURIComponent(params.locale)}` : '');
|
|
24
|
+
export const jsonOrThrow = async (res) => {
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw await VimineError.fromResponse(res);
|
|
27
|
+
if (res.status === 204)
|
|
28
|
+
return undefined;
|
|
29
|
+
return (await res.json());
|
|
30
|
+
};
|
|
31
|
+
export const withNetworkError = async (p) => {
|
|
32
|
+
try {
|
|
33
|
+
return await p;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err instanceof VimineError)
|
|
37
|
+
throw err;
|
|
38
|
+
throw new VimineError('network', 'The request could not reach the Vimine API', { cause: err });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
export const publicApi = (options = {}) => {
|
|
42
|
+
const apiUrl = (options.apiUrl || DEFAULT_API_URL).replace(/\/+$/, '');
|
|
43
|
+
const f = options.fetch || globalThis.fetch.bind(globalThis);
|
|
44
|
+
const get = (path) => withNetworkError(f(`${apiUrl}${path}`, { headers: { Accept: 'application/json' } }).then((r) => jsonOrThrow(r)));
|
|
45
|
+
const post = (path, body) => withNetworkError(f(`${apiUrl}${path}`, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then((r) => jsonOrThrow(r)));
|
|
46
|
+
return {
|
|
47
|
+
verifyCard: (cardToken) => get(`/v1/verify/${encodeURIComponent(cardToken)}`),
|
|
48
|
+
association: (slug, params = {}) => get(`/v1/aps/${encodeURIComponent(slug)}${localeQuery(params)}`),
|
|
49
|
+
validateFiscalCode: (input) => post('/v1/validate/fiscal-code', input),
|
|
50
|
+
cardKeys: () => get('/v1/card-keys'),
|
|
51
|
+
confirmEnrollment: (token, identification) => post('/v1/enroll/confirm', identification ? { token, identification } : { token }),
|
|
52
|
+
events: {
|
|
53
|
+
list: (slug, params = {}) => get(`/v1/aps/${encodeURIComponent(slug)}/events${eventQuery(params)}`),
|
|
54
|
+
get: (slug, event, params = {}) => get(`/v1/aps/${encodeURIComponent(slug)}/events/${encodeURIComponent(event)}${localeQuery(params)}`)
|
|
55
|
+
},
|
|
56
|
+
confirmRegistration: (token) => post('/v1/registrations/confirm', { token })
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=public.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.js","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"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","sourcesContent":["// The public surface of the API — no credentials, no sign-in: verify a card, read an association's public\n// profile (branding, membership types, in-force statuto), validate a codice fiscale, fetch the card-token\n// public keys. Usable from any page, from a scanner, from a server.\nimport { VimineError } from './errors.js';\nimport type { AssociationPublicConfig, CardKeys, EnrollConfirmResult, EnrollIdentification, Event, EventList, EventListParams, FiscalCodeCheck, FiscalCodeResult, LocaleParam, Registration, Verification } from './types.js';\n\nexport const DEFAULT_API_URL = 'https://api.vimine.network';\n\nexport interface PublicOptions {\n apiUrl?: string;\n fetch?: typeof fetch;\n}\n\nexport interface PublicApi {\n /** Is this card active right now? Privacy-preserving: status and validity end only. */\n verifyCard(cardToken: string): Promise<Verification>;\n /**\n * An association's public profile by its enrolment slug. `locale`: read its copy — the membership types, the payment\n * instructions, the privacy notice — in that language where the association wrote it; `locale` / `locales` of the\n * answer say what was served and what it offers.\n */\n association(slug: string, params?: LocaleParam): Promise<AssociationPublicConfig>;\n /**\n * Structure + check digit; with the personal data also their consistency with the code — and what the code itself\n * encodes (date and country of birth). For a person born abroad send `country`: their code encodes only the state.\n */\n validateFiscalCode(input: FiscalCodeCheck): Promise<FiscalCodeResult>;\n /** Public keys (JWKS) that verify card tokens offline. Cache and re-fetch daily. */\n cardKeys(): Promise<CardKeys>;\n /**\n * Consume the token of a \"confirm your membership request\" link (sent when a system enrols an existing account).\n * An account that is not identified yet answers 409 `identification_required` (`errors[]` = what is missing): ask\n * the person and call again with `identification` — it travels in the body, never in the link.\n */\n confirmEnrollment(token: string, identification?: EnrollIdentification): Promise<EnrollConfirmResult>;\n /** An association's PUBLIC events (members-only ones need a member token: `Vimine.events`). */\n events: {\n list(slug: string, params?: EventListParams): Promise<EventList>;\n get(slug: string, event: string, params?: LocaleParam): Promise<Event>;\n };\n /** Consume the token of a guest's \"confirm your registration\" link: the registration, with the QR `token` when confirmed. */\n confirmRegistration(token: string): Promise<Registration>;\n}\n\n/** Query string of an events listing (shared by the public, browser and server clients). */\nexport const eventQuery = (params: EventListParams = {}): string => {\n const q = new URLSearchParams();\n if (params.filter) q.set('filter', params.filter);\n if (params.from) q.set('from', params.from);\n if (params.to) q.set('to', params.to);\n if (params.limit) q.set('limit', String(params.limit));\n if (params.locale) q.set('locale', params.locale);\n const qs = q.toString();\n return qs ? `?${qs}` : '';\n};\n\n/** `?locale=` of a single read. */\nexport const localeQuery = (params: LocaleParam = {}): string => (params.locale ? `?locale=${encodeURIComponent(params.locale)}` : '');\n\nexport const jsonOrThrow = async <T>(res: Response): Promise<T> => {\n if (!res.ok) throw await VimineError.fromResponse(res);\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n};\n\nexport const withNetworkError = async <T>(p: Promise<T>): Promise<T> => {\n try {\n return await p;\n } catch (err) {\n if (err instanceof VimineError) throw err;\n throw new VimineError('network', 'The request could not reach the Vimine API', { cause: err });\n }\n};\n\nexport const publicApi = (options: PublicOptions = {}): PublicApi => {\n const apiUrl = (options.apiUrl || DEFAULT_API_URL).replace(/\\/+$/, '');\n const f = options.fetch || globalThis.fetch.bind(globalThis);\n const get = <T>(path: string) => withNetworkError(f(`${apiUrl}${path}`, { headers: { Accept: 'application/json' } }).then((r) => jsonOrThrow<T>(r)));\n const post = <T>(path: string, body: unknown) =>\n withNetworkError(f(`${apiUrl}${path}`, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then((r) => jsonOrThrow<T>(r)));\n return {\n verifyCard: (cardToken) => get<Verification>(`/v1/verify/${encodeURIComponent(cardToken)}`),\n association: (slug, params = {}) => get<AssociationPublicConfig>(`/v1/aps/${encodeURIComponent(slug)}${localeQuery(params)}`),\n validateFiscalCode: (input) => post<FiscalCodeResult>('/v1/validate/fiscal-code', input),\n cardKeys: () => get<CardKeys>('/v1/card-keys'),\n confirmEnrollment: (token, identification) => post<EnrollConfirmResult>('/v1/enroll/confirm', identification ? { token, identification } : { token }),\n events: {\n list: (slug, params = {}) => get<EventList>(`/v1/aps/${encodeURIComponent(slug)}/events${eventQuery(params)}`),\n get: (slug, event, params = {}) => get<Event>(`/v1/aps/${encodeURIComponent(slug)}/events/${encodeURIComponent(event)}${localeQuery(params)}`)\n },\n confirmRegistration: (token) => post<Registration>('/v1/registrations/confirm', { token })\n };\n};\n"]}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import type { Vimine, RedirectResult, Session, SignInOptions, SignOutOptions } from './index.js';
|
|
3
|
+
import type { VimineError } from './errors.js';
|
|
4
|
+
export interface VimineContextValue {
|
|
5
|
+
client: Vimine;
|
|
6
|
+
session: Session | null;
|
|
7
|
+
isSignedIn: boolean;
|
|
8
|
+
/** True while a sign-in redirect is being exchanged for a session. */
|
|
9
|
+
loading: boolean;
|
|
10
|
+
signIn: (opts?: SignInOptions) => Promise<void>;
|
|
11
|
+
signOut: (opts?: SignOutOptions) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface VimineProviderProps {
|
|
14
|
+
client: Vimine;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/** Exchange the code when the page loads with sign-in parameters (default true). */
|
|
17
|
+
handleRedirect?: boolean;
|
|
18
|
+
onSignedIn?: (result: RedirectResult) => void;
|
|
19
|
+
onRedirectError?: (error: VimineError) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function VimineProvider({ client, children, handleRedirect, onSignedIn, onRedirectError }: VimineProviderProps): import("react").JSX.Element;
|
|
22
|
+
export declare function useVimine(): VimineContextValue;
|
|
23
|
+
export interface SignInButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> {
|
|
24
|
+
scopes?: SignInOptions['scopes'];
|
|
25
|
+
prompt?: SignInOptions['prompt'];
|
|
26
|
+
returnTo?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A plain button that starts the sign-in from the click (the hosted page must be reached from a user gesture). */
|
|
29
|
+
export declare function SignInButton({ scopes, prompt, returnTo, children, disabled, ...rest }: SignInButtonProps): import("react").JSX.Element;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vimine/sdk/react — a provider that owns one Vimine client, handles the sign-in redirect on mount, and
|
|
3
|
+
// exposes the session to the tree; a hook; a sign-in button. React ≥ 18 (optional peer dependency).
|
|
4
|
+
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
5
|
+
const VimineContext = createContext(null);
|
|
6
|
+
export function VimineProvider({ client, children, handleRedirect = true, onSignedIn, onRedirectError }) {
|
|
7
|
+
const [session, setSession] = useState(() => client.getSession());
|
|
8
|
+
const [loading, setLoading] = useState(() => handleRedirect && client.hasRedirectParams());
|
|
9
|
+
useEffect(() => client.onChange(setSession), [client]);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!handleRedirect || !client.hasRedirectParams())
|
|
12
|
+
return;
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
client
|
|
15
|
+
.handleRedirect()
|
|
16
|
+
.then((result) => {
|
|
17
|
+
if (!cancelled && result && onSignedIn)
|
|
18
|
+
onSignedIn(result);
|
|
19
|
+
})
|
|
20
|
+
.catch((err) => {
|
|
21
|
+
if (!cancelled && onRedirectError)
|
|
22
|
+
onRedirectError(err);
|
|
23
|
+
})
|
|
24
|
+
.finally(() => {
|
|
25
|
+
if (!cancelled)
|
|
26
|
+
setLoading(false);
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
cancelled = true;
|
|
30
|
+
};
|
|
31
|
+
// The redirect is handled once per page load; callbacks are read at that time.
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
}, [client, handleRedirect]);
|
|
34
|
+
const value = useMemo(() => ({
|
|
35
|
+
client,
|
|
36
|
+
session,
|
|
37
|
+
isSignedIn: !!session,
|
|
38
|
+
loading,
|
|
39
|
+
signIn: (opts) => client.signIn(opts),
|
|
40
|
+
signOut: (opts) => client.signOut(opts)
|
|
41
|
+
}), [client, session, loading]);
|
|
42
|
+
return _jsx(VimineContext.Provider, { value: value, children: children });
|
|
43
|
+
}
|
|
44
|
+
export function useVimine() {
|
|
45
|
+
const value = useContext(VimineContext);
|
|
46
|
+
if (!value)
|
|
47
|
+
throw new Error('useVimine must be used within <VimineProvider>');
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/** A plain button that starts the sign-in from the click (the hosted page must be reached from a user gesture). */
|
|
51
|
+
export function SignInButton({ scopes, prompt, returnTo, children, disabled, ...rest }) {
|
|
52
|
+
const { signIn, loading } = useVimine();
|
|
53
|
+
return (_jsx("button", { type: "button", ...rest, disabled: disabled || loading, onClick: () => void signIn({ scopes, prompt, returnTo }), children: children ?? 'Accedi con Vimine' }));
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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","sourcesContent":["// @vimine/sdk/react — a provider that owns one Vimine client, handles the sign-in redirect on mount, and\n// exposes the session to the tree; a hook; a sign-in button. React ≥ 18 (optional peer dependency).\nimport { createContext, useContext, useEffect, useMemo, useState, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport type { Vimine, RedirectResult, Session, SignInOptions, SignOutOptions } from './index.js';\nimport type { VimineError } from './errors.js';\n\nexport interface VimineContextValue {\n client: Vimine;\n session: Session | null;\n isSignedIn: boolean;\n /** True while a sign-in redirect is being exchanged for a session. */\n loading: boolean;\n signIn: (opts?: SignInOptions) => Promise<void>;\n signOut: (opts?: SignOutOptions) => Promise<void>;\n}\n\nconst VimineContext = createContext<VimineContextValue | null>(null);\n\nexport interface VimineProviderProps {\n client: Vimine;\n children?: ReactNode;\n /** Exchange the code when the page loads with sign-in parameters (default true). */\n handleRedirect?: boolean;\n onSignedIn?: (result: RedirectResult) => void;\n onRedirectError?: (error: VimineError) => void;\n}\n\nexport function VimineProvider({ client, children, handleRedirect = true, onSignedIn, onRedirectError }: VimineProviderProps) {\n const [session, setSession] = useState<Session | null>(() => client.getSession());\n const [loading, setLoading] = useState<boolean>(() => handleRedirect && client.hasRedirectParams());\n\n useEffect(() => client.onChange(setSession), [client]);\n\n useEffect(() => {\n if (!handleRedirect || !client.hasRedirectParams()) return;\n let cancelled = false;\n client\n .handleRedirect()\n .then((result) => {\n if (!cancelled && result && onSignedIn) onSignedIn(result);\n })\n .catch((err: VimineError) => {\n if (!cancelled && onRedirectError) onRedirectError(err);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n // The redirect is handled once per page load; callbacks are read at that time.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, handleRedirect]);\n\n const value = useMemo<VimineContextValue>(\n () => ({\n client,\n session,\n isSignedIn: !!session,\n loading,\n signIn: (opts) => client.signIn(opts),\n signOut: (opts) => client.signOut(opts)\n }),\n [client, session, loading]\n );\n\n return <VimineContext.Provider value={value}>{children}</VimineContext.Provider>;\n}\n\nexport function useVimine(): VimineContextValue {\n const value = useContext(VimineContext);\n if (!value) throw new Error('useVimine must be used within <VimineProvider>');\n return value;\n}\n\nexport interface SignInButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> {\n scopes?: SignInOptions['scopes'];\n prompt?: SignInOptions['prompt'];\n returnTo?: string;\n}\n\n/** A plain button that starts the sign-in from the click (the hosted page must be reached from a user gesture). */\nexport function SignInButton({ scopes, prompt, returnTo, children, disabled, ...rest }: SignInButtonProps) {\n const { signIn, loading } = useVimine();\n return (\n <button type=\"button\" {...rest} disabled={disabled || loading} onClick={() => void signIn({ scopes, prompt, returnTo })}>\n {children ?? 'Accedi con Vimine'}\n </button>\n );\n}\n"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type PublicApi } from './public.js';
|
|
2
|
+
import type { Accepted, CheckinResult, EnrollInput, Event, EventList, EventListParams, EventRegistrationList, EventRegistrationListParams, GuestRegistrationRequest, LocaleParam, MembershipList, MembershipListParams, MembershipStatusInfo, SandboxMembers, SandboxReset, Scope, SignupInput } from './types.js';
|
|
3
|
+
export { VimineError } from './errors.js';
|
|
4
|
+
export type { FieldError, ProblemDetails } from './errors.js';
|
|
5
|
+
export type * from './types.js';
|
|
6
|
+
export interface VimineServerOptions {
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
/** Subset of the client's registered scopes to ask for (default: all of them). */
|
|
10
|
+
scopes?: Scope[];
|
|
11
|
+
apiUrl?: string;
|
|
12
|
+
fetch?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
export declare class VimineServer {
|
|
15
|
+
readonly options: Required<Pick<VimineServerOptions, 'clientId' | 'clientSecret' | 'apiUrl'>> & VimineServerOptions;
|
|
16
|
+
readonly public: PublicApi;
|
|
17
|
+
private readonly fetchImpl;
|
|
18
|
+
private cached;
|
|
19
|
+
private pending;
|
|
20
|
+
constructor(options: VimineServerOptions);
|
|
21
|
+
/** A client access token (`client_credentials`), cached until shortly before it expires; single-flight. */
|
|
22
|
+
token(): Promise<string>;
|
|
23
|
+
/** An authenticated call; a 401 (revoked or rotated credentials) is retried once with a fresh token. */
|
|
24
|
+
request<T>(path: string, init?: RequestInit & {
|
|
25
|
+
idempotencyKey?: string;
|
|
26
|
+
}): Promise<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a Vimine account for a person (`identity:signup`). They receive the mail that confirms the address
|
|
29
|
+
* and lets them set a password. Always `{ status: 'accepted' }` — whether the address already had an account is
|
|
30
|
+
* never disclosed to you (its owner is told by mail). Retries are safe: an `Idempotency-Key` is generated
|
|
31
|
+
* unless you pass one.
|
|
32
|
+
*/
|
|
33
|
+
signup(input: SignupInput, opts?: {
|
|
34
|
+
idempotencyKey?: string;
|
|
35
|
+
}): Promise<Accepted>;
|
|
36
|
+
/**
|
|
37
|
+
* Submit a membership request for a person (`enroll:write`): a new address gets an account and a PENDING request
|
|
38
|
+
* the association then activates; an existing account's owner receives a confirmation link instead (nothing is
|
|
39
|
+
* written until they use it). Always `{ status: 'accepted' }`. `accept_statuto` must name the in-force statuto.
|
|
40
|
+
* The person is identified by a fiscal code OR by `dob` + `birthplace` + `birth_country` — never neither (422
|
|
41
|
+
* `required` on each missing particular); a code that disagrees with the particulars is a 422
|
|
42
|
+
* `codicefiscale_mismatch` entry, which says whether the person may confirm it (`codicefiscale_confirmed`).
|
|
43
|
+
*/
|
|
44
|
+
enroll(input: EnrollInput, opts?: {
|
|
45
|
+
idempotencyKey?: string;
|
|
46
|
+
}): Promise<Accepted>;
|
|
47
|
+
/** Status of one membership of the association — no personal data (`memberships:read`). */
|
|
48
|
+
readonly memberships: {
|
|
49
|
+
status: (id: string) => Promise<MembershipStatusInfo>;
|
|
50
|
+
/** Tenant-wide list, ids and status only (`memberships:read`; plans with tenant-wide lists — 403 `plan_limit` otherwise). */
|
|
51
|
+
list: (params?: MembershipListParams) => Promise<MembershipList>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Door check-ins (`checkins:write`): the scanned token in, the verdict out — no personal data. A membership CARD
|
|
55
|
+
* token answers `kind: 'card'`; an event REGISTRATION token (`k:'reg'`) answers `kind: 'registration'` with the
|
|
56
|
+
* registration's verdict and, when the type requires a card, the membership's.
|
|
57
|
+
*/
|
|
58
|
+
readonly checkins: {
|
|
59
|
+
create: (token: string) => Promise<CheckinResult>;
|
|
60
|
+
};
|
|
61
|
+
/** The association's published events, members-only included (any client token). */
|
|
62
|
+
readonly events: {
|
|
63
|
+
list: (params?: EventListParams) => Promise<EventList>;
|
|
64
|
+
get: (slug: string, params?: LocaleParam) => Promise<Event>;
|
|
65
|
+
};
|
|
66
|
+
/** Event registrations from the association's side. */
|
|
67
|
+
readonly registrations: {
|
|
68
|
+
/** Ids, status, type and check-in of an event's registrations — no personal data (`registrations:read`; plans with tenant-wide lists). */
|
|
69
|
+
listForEvent: (slug: string, params?: EventRegistrationListParams) => Promise<EventRegistrationList>;
|
|
70
|
+
/**
|
|
71
|
+
* Register a GUEST — a person without a Vimine account — for a PUBLIC event (`registrations:write`). The
|
|
72
|
+
* place is held until they confirm their address through the link they receive (24 h); a free type is then
|
|
73
|
+
* confirmed, a paid one waits for the contribution. Always `{ status: 'accepted' }` (whether the address is
|
|
74
|
+
* known is never disclosed); `422 not_eligible` when the type needs a membership or is sold out. Retries are
|
|
75
|
+
* safe: an `Idempotency-Key` is generated unless you pass one.
|
|
76
|
+
*/
|
|
77
|
+
guest: (slug: string, input: GuestRegistrationRequest, opts?: {
|
|
78
|
+
idempotencyKey?: string;
|
|
79
|
+
}) => Promise<Accepted>;
|
|
80
|
+
};
|
|
81
|
+
/** The sandbox (test clients only): its synthetic members, and a rebuild that rotates their password. */
|
|
82
|
+
readonly sandbox: {
|
|
83
|
+
members: () => Promise<SandboxMembers>;
|
|
84
|
+
reset: () => Promise<SandboxReset>;
|
|
85
|
+
};
|
|
86
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// @vimine/sdk/server — for an association's OWN systems: a confidential client (`vmc_…` + `vms_…`)
|
|
2
|
+
// authenticating with `client_credentials`, reading membership statuses, driving its sandbox. Node ≥ 18 (fetch,
|
|
3
|
+
// btoa); never ship the secret to a browser.
|
|
4
|
+
import { VimineError } from './errors.js';
|
|
5
|
+
import { createState } from './pkce.js';
|
|
6
|
+
import { DEFAULT_API_URL, eventQuery, jsonOrThrow, localeQuery, publicApi, withNetworkError } from './public.js';
|
|
7
|
+
export { VimineError } from './errors.js';
|
|
8
|
+
const REFRESH_LEAD_MS = 30_000;
|
|
9
|
+
export class VimineServer {
|
|
10
|
+
options;
|
|
11
|
+
public;
|
|
12
|
+
fetchImpl;
|
|
13
|
+
cached = null;
|
|
14
|
+
pending = null;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
if (!options?.clientId || !options.clientSecret)
|
|
17
|
+
throw new VimineError('invalid_options', 'clientId and clientSecret are required');
|
|
18
|
+
this.options = { ...options, apiUrl: (options.apiUrl || DEFAULT_API_URL).replace(/\/+$/, '') };
|
|
19
|
+
this.fetchImpl = options.fetch || globalThis.fetch.bind(globalThis);
|
|
20
|
+
this.public = publicApi({ apiUrl: this.options.apiUrl, fetch: this.fetchImpl });
|
|
21
|
+
}
|
|
22
|
+
/** A client access token (`client_credentials`), cached until shortly before it expires; single-flight. */
|
|
23
|
+
token() {
|
|
24
|
+
if (this.cached && this.cached.expiresAt - Date.now() > REFRESH_LEAD_MS)
|
|
25
|
+
return Promise.resolve(this.cached.token);
|
|
26
|
+
if (this.pending)
|
|
27
|
+
return this.pending;
|
|
28
|
+
this.pending = (async () => {
|
|
29
|
+
try {
|
|
30
|
+
const form = new URLSearchParams({ grant_type: 'client_credentials' });
|
|
31
|
+
if (this.options.scopes?.length)
|
|
32
|
+
form.set('scope', this.options.scopes.join(' '));
|
|
33
|
+
const res = await withNetworkError(this.fetchImpl(`${this.options.apiUrl}/v1/oauth/token`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${btoa(`${this.options.clientId}:${this.options.clientSecret}`)}` },
|
|
36
|
+
body: form.toString()
|
|
37
|
+
}));
|
|
38
|
+
const tokens = await jsonOrThrow(res);
|
|
39
|
+
this.cached = { token: tokens.access_token, expiresAt: Date.now() + Math.max(0, tokens.expires_in) * 1000 };
|
|
40
|
+
return tokens.access_token;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
this.pending = null;
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
return this.pending;
|
|
47
|
+
}
|
|
48
|
+
/** An authenticated call; a 401 (revoked or rotated credentials) is retried once with a fresh token. */
|
|
49
|
+
async request(path, init = {}) {
|
|
50
|
+
const attempt = async (token) => {
|
|
51
|
+
const headers = { Accept: 'application/json', Authorization: `Bearer ${token}` };
|
|
52
|
+
if (init.body !== undefined)
|
|
53
|
+
headers['Content-Type'] = 'application/json';
|
|
54
|
+
if (init.idempotencyKey)
|
|
55
|
+
headers['Idempotency-Key'] = init.idempotencyKey;
|
|
56
|
+
const { idempotencyKey: _ignored, ...rest } = init;
|
|
57
|
+
return this.fetchImpl(`${this.options.apiUrl}${path}`, { ...rest, headers: { ...headers, ...(rest.headers || {}) } });
|
|
58
|
+
};
|
|
59
|
+
let res = await withNetworkError(attempt(await this.token()));
|
|
60
|
+
if (res.status === 401) {
|
|
61
|
+
this.cached = null;
|
|
62
|
+
res = await withNetworkError(attempt(await this.token()));
|
|
63
|
+
}
|
|
64
|
+
return jsonOrThrow(res);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a Vimine account for a person (`identity:signup`). They receive the mail that confirms the address
|
|
68
|
+
* and lets them set a password. Always `{ status: 'accepted' }` — whether the address already had an account is
|
|
69
|
+
* never disclosed to you (its owner is told by mail). Retries are safe: an `Idempotency-Key` is generated
|
|
70
|
+
* unless you pass one.
|
|
71
|
+
*/
|
|
72
|
+
signup(input, opts = {}) {
|
|
73
|
+
return this.request('/v1/signup', { method: 'POST', body: JSON.stringify(input), idempotencyKey: opts.idempotencyKey || createState() });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Submit a membership request for a person (`enroll:write`): a new address gets an account and a PENDING request
|
|
77
|
+
* the association then activates; an existing account's owner receives a confirmation link instead (nothing is
|
|
78
|
+
* written until they use it). Always `{ status: 'accepted' }`. `accept_statuto` must name the in-force statuto.
|
|
79
|
+
* The person is identified by a fiscal code OR by `dob` + `birthplace` + `birth_country` — never neither (422
|
|
80
|
+
* `required` on each missing particular); a code that disagrees with the particulars is a 422
|
|
81
|
+
* `codicefiscale_mismatch` entry, which says whether the person may confirm it (`codicefiscale_confirmed`).
|
|
82
|
+
*/
|
|
83
|
+
enroll(input, opts = {}) {
|
|
84
|
+
return this.request('/v1/enroll', { method: 'POST', body: JSON.stringify(input), idempotencyKey: opts.idempotencyKey || createState() });
|
|
85
|
+
}
|
|
86
|
+
/** Status of one membership of the association — no personal data (`memberships:read`). */
|
|
87
|
+
memberships = {
|
|
88
|
+
status: (id) => this.request(`/v1/memberships/${encodeURIComponent(id)}/status`),
|
|
89
|
+
/** Tenant-wide list, ids and status only (`memberships:read`; plans with tenant-wide lists — 403 `plan_limit` otherwise). */
|
|
90
|
+
list: (params = {}) => {
|
|
91
|
+
const q = new URLSearchParams();
|
|
92
|
+
if (params.status)
|
|
93
|
+
q.set('status', params.status);
|
|
94
|
+
if (params.updated_since)
|
|
95
|
+
q.set('updated_since', params.updated_since);
|
|
96
|
+
if (params.limit)
|
|
97
|
+
q.set('limit', String(params.limit));
|
|
98
|
+
if (params.cursor)
|
|
99
|
+
q.set('cursor', params.cursor);
|
|
100
|
+
const qs = q.toString();
|
|
101
|
+
return this.request(`/v1/memberships${qs ? `?${qs}` : ''}`);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Door check-ins (`checkins:write`): the scanned token in, the verdict out — no personal data. A membership CARD
|
|
106
|
+
* token answers `kind: 'card'`; an event REGISTRATION token (`k:'reg'`) answers `kind: 'registration'` with the
|
|
107
|
+
* registration's verdict and, when the type requires a card, the membership's.
|
|
108
|
+
*/
|
|
109
|
+
checkins = {
|
|
110
|
+
create: (token) => this.request('/v1/checkins', { method: 'POST', body: JSON.stringify({ token }) })
|
|
111
|
+
};
|
|
112
|
+
/** The association's published events, members-only included (any client token). */
|
|
113
|
+
events = {
|
|
114
|
+
list: (params = {}) => this.request(`/v1/events${eventQuery(params)}`),
|
|
115
|
+
get: (slug, params = {}) => this.request(`/v1/events/${encodeURIComponent(slug)}${localeQuery(params)}`)
|
|
116
|
+
};
|
|
117
|
+
/** Event registrations from the association's side. */
|
|
118
|
+
registrations = {
|
|
119
|
+
/** Ids, status, type and check-in of an event's registrations — no personal data (`registrations:read`; plans with tenant-wide lists). */
|
|
120
|
+
listForEvent: (slug, params = {}) => {
|
|
121
|
+
const q = new URLSearchParams();
|
|
122
|
+
if (params.status)
|
|
123
|
+
q.set('status', params.status);
|
|
124
|
+
if (params.limit)
|
|
125
|
+
q.set('limit', String(params.limit));
|
|
126
|
+
if (params.cursor)
|
|
127
|
+
q.set('cursor', params.cursor);
|
|
128
|
+
const qs = q.toString();
|
|
129
|
+
return this.request(`/v1/events/${encodeURIComponent(slug)}/registrations${qs ? `?${qs}` : ''}`);
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* Register a GUEST — a person without a Vimine account — for a PUBLIC event (`registrations:write`). The
|
|
133
|
+
* place is held until they confirm their address through the link they receive (24 h); a free type is then
|
|
134
|
+
* confirmed, a paid one waits for the contribution. Always `{ status: 'accepted' }` (whether the address is
|
|
135
|
+
* known is never disclosed); `422 not_eligible` when the type needs a membership or is sold out. Retries are
|
|
136
|
+
* safe: an `Idempotency-Key` is generated unless you pass one.
|
|
137
|
+
*/
|
|
138
|
+
guest: (slug, input, opts = {}) => this.request(`/v1/events/${encodeURIComponent(slug)}/registrations/guest`, { method: 'POST', body: JSON.stringify(input), idempotencyKey: opts.idempotencyKey || createState() })
|
|
139
|
+
};
|
|
140
|
+
/** The sandbox (test clients only): its synthetic members, and a rebuild that rotates their password. */
|
|
141
|
+
sandbox = {
|
|
142
|
+
members: () => this.request('/v1/sandbox'),
|
|
143
|
+
reset: () => this.request('/v1/sandbox/reset', { method: 'POST' })
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["// @vimine/sdk/server — for an association's OWN systems: a confidential client (`vmc_…` + `vms_…`)\n// authenticating with `client_credentials`, reading membership statuses, driving its sandbox. Node ≥ 18 (fetch,\n// btoa); never ship the secret to a browser.\nimport { VimineError } from './errors.js';\nimport { createState } from './pkce.js';\nimport { DEFAULT_API_URL, eventQuery, jsonOrThrow, localeQuery, publicApi, withNetworkError, type PublicApi } from './public.js';\nimport type { Accepted, CheckinResult, EnrollInput, Event, EventList, EventListParams, EventRegistrationList, EventRegistrationListParams, GuestRegistrationRequest, LocaleParam, MembershipList, MembershipListParams, MembershipStatusInfo, SandboxMembers, SandboxReset, Scope, SignupInput, TokenResponse } from './types.js';\n\nexport { VimineError } from './errors.js';\nexport type { FieldError, ProblemDetails } from './errors.js';\nexport type * from './types.js';\n\nexport interface VimineServerOptions {\n clientId: string;\n clientSecret: string;\n /** Subset of the client's registered scopes to ask for (default: all of them). */\n scopes?: Scope[];\n apiUrl?: string;\n fetch?: typeof fetch;\n}\n\nconst REFRESH_LEAD_MS = 30_000;\n\nexport class VimineServer {\n readonly options: Required<Pick<VimineServerOptions, 'clientId' | 'clientSecret' | 'apiUrl'>> & VimineServerOptions;\n readonly public: PublicApi;\n private readonly fetchImpl: typeof fetch;\n private cached: { token: string; expiresAt: number } | null = null;\n private pending: Promise<string> | null = null;\n\n constructor(options: VimineServerOptions) {\n if (!options?.clientId || !options.clientSecret) throw new VimineError('invalid_options', 'clientId and clientSecret are required');\n this.options = { ...options, apiUrl: (options.apiUrl || DEFAULT_API_URL).replace(/\\/+$/, '') };\n this.fetchImpl = options.fetch || globalThis.fetch.bind(globalThis);\n this.public = publicApi({ apiUrl: this.options.apiUrl, fetch: this.fetchImpl });\n }\n\n /** A client access token (`client_credentials`), cached until shortly before it expires; single-flight. */\n token(): Promise<string> {\n if (this.cached && this.cached.expiresAt - Date.now() > REFRESH_LEAD_MS) return Promise.resolve(this.cached.token);\n if (this.pending) return this.pending;\n this.pending = (async () => {\n try {\n const form = new URLSearchParams({ grant_type: 'client_credentials' });\n if (this.options.scopes?.length) form.set('scope', this.options.scopes.join(' '));\n const res = await withNetworkError(\n this.fetchImpl(`${this.options.apiUrl}/v1/oauth/token`, {\n method: 'POST',\n headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${btoa(`${this.options.clientId}:${this.options.clientSecret}`)}` },\n body: form.toString()\n })\n );\n const tokens = await jsonOrThrow<TokenResponse>(res);\n this.cached = { token: tokens.access_token, expiresAt: Date.now() + Math.max(0, tokens.expires_in) * 1000 };\n return tokens.access_token;\n } finally {\n this.pending = null;\n }\n })();\n return this.pending;\n }\n\n /** An authenticated call; a 401 (revoked or rotated credentials) is retried once with a fresh token. */\n async request<T>(path: string, init: RequestInit & { idempotencyKey?: string } = {}): Promise<T> {\n const attempt = async (token: string) => {\n const headers: Record<string, string> = { Accept: 'application/json', Authorization: `Bearer ${token}` };\n if (init.body !== undefined) headers['Content-Type'] = 'application/json';\n if (init.idempotencyKey) headers['Idempotency-Key'] = init.idempotencyKey;\n const { idempotencyKey: _ignored, ...rest } = init;\n return this.fetchImpl(`${this.options.apiUrl}${path}`, { ...rest, headers: { ...headers, ...((rest.headers as Record<string, string>) || {}) } });\n };\n let res = await withNetworkError(attempt(await this.token()));\n if (res.status === 401) {\n this.cached = null;\n res = await withNetworkError(attempt(await this.token()));\n }\n return jsonOrThrow<T>(res);\n }\n\n /**\n * Create a Vimine account for a person (`identity:signup`). They receive the mail that confirms the address\n * and lets them set a password. Always `{ status: 'accepted' }` — whether the address already had an account is\n * never disclosed to you (its owner is told by mail). Retries are safe: an `Idempotency-Key` is generated\n * unless you pass one.\n */\n signup(input: SignupInput, opts: { idempotencyKey?: string } = {}): Promise<Accepted> {\n return this.request<Accepted>('/v1/signup', { method: 'POST', body: JSON.stringify(input), idempotencyKey: opts.idempotencyKey || createState() });\n }\n\n /**\n * Submit a membership request for a person (`enroll:write`): a new address gets an account and a PENDING request\n * the association then activates; an existing account's owner receives a confirmation link instead (nothing is\n * written until they use it). Always `{ status: 'accepted' }`. `accept_statuto` must name the in-force statuto.\n * The person is identified by a fiscal code OR by `dob` + `birthplace` + `birth_country` — never neither (422\n * `required` on each missing particular); a code that disagrees with the particulars is a 422\n * `codicefiscale_mismatch` entry, which says whether the person may confirm it (`codicefiscale_confirmed`).\n */\n enroll(input: EnrollInput, opts: { idempotencyKey?: string } = {}): Promise<Accepted> {\n return this.request<Accepted>('/v1/enroll', { method: 'POST', body: JSON.stringify(input), idempotencyKey: opts.idempotencyKey || createState() });\n }\n\n /** Status of one membership of the association — no personal data (`memberships:read`). */\n readonly memberships = {\n status: (id: string): Promise<MembershipStatusInfo> => this.request<MembershipStatusInfo>(`/v1/memberships/${encodeURIComponent(id)}/status`),\n /** Tenant-wide list, ids and status only (`memberships:read`; plans with tenant-wide lists — 403 `plan_limit` otherwise). */\n list: (params: MembershipListParams = {}): Promise<MembershipList> => {\n const q = new URLSearchParams();\n if (params.status) q.set('status', params.status);\n if (params.updated_since) q.set('updated_since', params.updated_since);\n if (params.limit) q.set('limit', String(params.limit));\n if (params.cursor) q.set('cursor', params.cursor);\n const qs = q.toString();\n return this.request<MembershipList>(`/v1/memberships${qs ? `?${qs}` : ''}`);\n }\n };\n\n /**\n * Door check-ins (`checkins:write`): the scanned token in, the verdict out — no personal data. A membership CARD\n * token answers `kind: 'card'`; an event REGISTRATION token (`k:'reg'`) answers `kind: 'registration'` with the\n * registration's verdict and, when the type requires a card, the membership's.\n */\n readonly checkins = {\n create: (token: string): Promise<CheckinResult> => this.request<CheckinResult>('/v1/checkins', { method: 'POST', body: JSON.stringify({ token }) })\n };\n\n /** The association's published events, members-only included (any client token). */\n readonly events = {\n list: (params: EventListParams = {}): Promise<EventList> => this.request<EventList>(`/v1/events${eventQuery(params)}`),\n get: (slug: string, params: LocaleParam = {}): Promise<Event> => this.request<Event>(`/v1/events/${encodeURIComponent(slug)}${localeQuery(params)}`)\n };\n\n /** Event registrations from the association's side. */\n readonly registrations = {\n /** Ids, status, type and check-in of an event's registrations — no personal data (`registrations:read`; plans with tenant-wide lists). */\n listForEvent: (slug: string, params: EventRegistrationListParams = {}): Promise<EventRegistrationList> => {\n const q = new URLSearchParams();\n if (params.status) q.set('status', params.status);\n if (params.limit) q.set('limit', String(params.limit));\n if (params.cursor) q.set('cursor', params.cursor);\n const qs = q.toString();\n return this.request<EventRegistrationList>(`/v1/events/${encodeURIComponent(slug)}/registrations${qs ? `?${qs}` : ''}`);\n },\n /**\n * Register a GUEST — a person without a Vimine account — for a PUBLIC event (`registrations:write`). The\n * place is held until they confirm their address through the link they receive (24 h); a free type is then\n * confirmed, a paid one waits for the contribution. Always `{ status: 'accepted' }` (whether the address is\n * known is never disclosed); `422 not_eligible` when the type needs a membership or is sold out. Retries are\n * safe: an `Idempotency-Key` is generated unless you pass one.\n */\n guest: (slug: string, input: GuestRegistrationRequest, opts: { idempotencyKey?: string } = {}): Promise<Accepted> =>\n this.request<Accepted>(`/v1/events/${encodeURIComponent(slug)}/registrations/guest`, { method: 'POST', body: JSON.stringify(input), idempotencyKey: opts.idempotencyKey || createState() })\n };\n\n /** The sandbox (test clients only): its synthetic members, and a rebuild that rotates their password. */\n readonly sandbox = {\n members: (): Promise<SandboxMembers> => this.request<SandboxMembers>('/v1/sandbox'),\n reset: (): Promise<SandboxReset> => this.request<SandboxReset>('/v1/sandbox/reset', { method: 'POST' })\n };\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface TokenStorage {
|
|
2
|
+
get(key: string): string | null;
|
|
3
|
+
set(key: string, value: string): void;
|
|
4
|
+
remove(key: string): void;
|
|
5
|
+
}
|
|
6
|
+
export type StorageKind = 'session' | 'local' | 'memory';
|
|
7
|
+
export declare class MemoryStorage implements TokenStorage {
|
|
8
|
+
private map;
|
|
9
|
+
get(key: string): string | null;
|
|
10
|
+
set(key: string, value: string): void;
|
|
11
|
+
remove(key: string): void;
|
|
12
|
+
}
|
|
13
|
+
/** Resolve a storage option to an adapter; falls back to memory where web storage is unavailable. */
|
|
14
|
+
export declare const resolveStorage: (kind: StorageKind | TokenStorage | undefined) => TokenStorage;
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Where the SDK keeps the pending sign-in and the session. Default `session` (per tab, gone when the tab
|
|
2
|
+
// closes — the safest default for a public client); `local` survives tabs and restarts and syncs sign-out
|
|
3
|
+
// across tabs; `memory` never touches the browser (SSR, tests, kiosks that never persist).
|
|
4
|
+
export class MemoryStorage {
|
|
5
|
+
map = new Map();
|
|
6
|
+
get(key) {
|
|
7
|
+
return this.map.has(key) ? this.map.get(key) : null;
|
|
8
|
+
}
|
|
9
|
+
set(key, value) {
|
|
10
|
+
this.map.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
remove(key) {
|
|
13
|
+
this.map.delete(key);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class WebStorage {
|
|
17
|
+
store;
|
|
18
|
+
constructor(store) {
|
|
19
|
+
this.store = store;
|
|
20
|
+
}
|
|
21
|
+
get(key) {
|
|
22
|
+
try {
|
|
23
|
+
return this.store.getItem(key);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
set(key, value) {
|
|
30
|
+
try {
|
|
31
|
+
this.store.setItem(key, value);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* quota / privacy mode: the session simply does not persist */
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
remove(key) {
|
|
38
|
+
try {
|
|
39
|
+
this.store.removeItem(key);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* ignore */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Resolve a storage option to an adapter; falls back to memory where web storage is unavailable. */
|
|
47
|
+
export const resolveStorage = (kind) => {
|
|
48
|
+
if (kind && typeof kind === 'object')
|
|
49
|
+
return kind;
|
|
50
|
+
const want = kind || 'session';
|
|
51
|
+
if (want === 'memory')
|
|
52
|
+
return new MemoryStorage();
|
|
53
|
+
try {
|
|
54
|
+
const g = globalThis;
|
|
55
|
+
const store = want === 'local' ? g.localStorage : g.sessionStorage;
|
|
56
|
+
if (store)
|
|
57
|
+
return new WebStorage(store);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* no window / blocked storage */
|
|
61
|
+
}
|
|
62
|
+
return new MemoryStorage();
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,yGAAyG;AACzG,0GAA0G;AAC1G,2FAA2F;AAU3F,MAAM,OAAO,aAAa;IACd,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,GAAG,CAAC,GAAW;QACX,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,CAAC;IACD,GAAG,CAAC,GAAW,EAAE,KAAa;QAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,CAAC,GAAW;QACd,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;CACJ;AAED,MAAM,UAAU;IACiB;IAA7B,YAA6B,KAAc;QAAd,UAAK,GAAL,KAAK,CAAS;IAAG,CAAC;IAC/C,GAAG,CAAC,GAAW;QACX,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IACD,GAAG,CAAC,GAAW,EAAE,KAAa;QAC1B,IAAI,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACL,+DAA+D;QACnE,CAAC;IACL,CAAC;IACD,MAAM,CAAC,GAAW;QACd,IAAI,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACL,YAAY;QAChB,CAAC;IACL,CAAC;CACJ;AAED,qGAAqG;AACrG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAA4C,EAAgB,EAAE;IACzF,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClD,MAAM,IAAI,GAAG,IAAI,IAAI,SAAS,CAAC;IAC/B,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,aAAa,EAAE,CAAC;IAClD,IAAI,CAAC;QACD,MAAM,CAAC,GAAG,UAA6E,CAAC;QACxF,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;QACnE,IAAI,KAAK;YAAE,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACL,iCAAiC;IACrC,CAAC;IACD,OAAO,IAAI,aAAa,EAAE,CAAC;AAC/B,CAAC,CAAC","sourcesContent":["// Where the SDK keeps the pending sign-in and the session. Default `session` (per tab, gone when the tab\n// closes — the safest default for a public client); `local` survives tabs and restarts and syncs sign-out\n// across tabs; `memory` never touches the browser (SSR, tests, kiosks that never persist).\n\nexport interface TokenStorage {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nexport type StorageKind = 'session' | 'local' | 'memory';\n\nexport class MemoryStorage implements TokenStorage {\n private map = new Map<string, string>();\n get(key: string): string | null {\n return this.map.has(key) ? (this.map.get(key) as string) : null;\n }\n set(key: string, value: string): void {\n this.map.set(key, value);\n }\n remove(key: string): void {\n this.map.delete(key);\n }\n}\n\nclass WebStorage implements TokenStorage {\n constructor(private readonly store: Storage) {}\n get(key: string): string | null {\n try {\n return this.store.getItem(key);\n } catch {\n return null;\n }\n }\n set(key: string, value: string): void {\n try {\n this.store.setItem(key, value);\n } catch {\n /* quota / privacy mode: the session simply does not persist */\n }\n }\n remove(key: string): void {\n try {\n this.store.removeItem(key);\n } catch {\n /* ignore */\n }\n }\n}\n\n/** Resolve a storage option to an adapter; falls back to memory where web storage is unavailable. */\nexport const resolveStorage = (kind: StorageKind | TokenStorage | undefined): TokenStorage => {\n if (kind && typeof kind === 'object') return kind;\n const want = kind || 'session';\n if (want === 'memory') return new MemoryStorage();\n try {\n const g = globalThis as unknown as { sessionStorage?: Storage; localStorage?: Storage };\n const store = want === 'local' ? g.localStorage : g.sessionStorage;\n if (store) return new WebStorage(store);\n } catch {\n /* no window / blocked storage */\n }\n return new MemoryStorage();\n};\n"]}
|