@shware/http 3.0.0 → 3.0.1

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.
Files changed (55) hide show
  1. package/dist/error/parse.cjs +4 -3
  2. package/dist/error/parse.cjs.map +1 -1
  3. package/dist/error/parse.d.cts.map +1 -1
  4. package/dist/error/parse.d.mts.map +1 -1
  5. package/dist/error/parse.mjs +4 -3
  6. package/dist/error/parse.mjs.map +1 -1
  7. package/dist/error/status.cjs +1 -1
  8. package/dist/error/status.cjs.map +1 -1
  9. package/dist/error/status.mjs +1 -1
  10. package/dist/error/status.mjs.map +1 -1
  11. package/dist/google-one-tap/index.cjs +20 -5
  12. package/dist/google-one-tap/index.cjs.map +1 -1
  13. package/dist/google-one-tap/index.d.cts +2 -1
  14. package/dist/google-one-tap/index.d.cts.map +1 -1
  15. package/dist/google-one-tap/index.d.mts +2 -1
  16. package/dist/google-one-tap/index.d.mts.map +1 -1
  17. package/dist/google-one-tap/index.mjs +20 -5
  18. package/dist/google-one-tap/index.mjs.map +1 -1
  19. package/dist/hono/csrf.cjs +2 -2
  20. package/dist/hono/csrf.cjs.map +1 -1
  21. package/dist/hono/csrf.mjs +2 -2
  22. package/dist/hono/csrf.mjs.map +1 -1
  23. package/dist/hono/handler.cjs +2 -2
  24. package/dist/hono/handler.cjs.map +1 -1
  25. package/dist/hono/handler.mjs +2 -2
  26. package/dist/hono/handler.mjs.map +1 -1
  27. package/dist/hono/validator.cjs +1 -1
  28. package/dist/hono/validator.cjs.map +1 -1
  29. package/dist/hono/validator.d.cts.map +1 -1
  30. package/dist/hono/validator.d.mts.map +1 -1
  31. package/dist/hono/validator.mjs +1 -1
  32. package/dist/hono/validator.mjs.map +1 -1
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.d.cts +1 -1
  35. package/dist/index.d.mts +1 -1
  36. package/dist/index.mjs +1 -1
  37. package/dist/iso/{iso_3601_1.cjs → iso-3601-1.cjs} +2 -2
  38. package/dist/iso/{iso_3601_1.cjs.map → iso-3601-1.cjs.map} +1 -1
  39. package/dist/iso/{iso_3601_1.d.mts → iso-3601-1.d.cts} +2 -2
  40. package/dist/iso/iso-3601-1.d.cts.map +1 -0
  41. package/dist/iso/{iso_3601_1.d.cts → iso-3601-1.d.mts} +2 -2
  42. package/dist/iso/iso-3601-1.d.mts.map +1 -0
  43. package/dist/iso/{iso_3601_1.mjs → iso-3601-1.mjs} +2 -2
  44. package/dist/iso/{iso_3601_1.mjs.map → iso-3601-1.mjs.map} +1 -1
  45. package/dist/polyfills/index.d.cts +1 -1
  46. package/dist/polyfills/index.d.mts +1 -1
  47. package/dist/response.cjs +3 -2
  48. package/dist/response.cjs.map +1 -1
  49. package/dist/response.d.cts.map +1 -1
  50. package/dist/response.d.mts.map +1 -1
  51. package/dist/response.mjs +3 -2
  52. package/dist/response.mjs.map +1 -1
  53. package/package.json +4 -4
  54. package/dist/iso/iso_3601_1.d.cts.map +0 -1
  55. package/dist/iso/iso_3601_1.d.mts.map +0 -1
@@ -34,11 +34,12 @@ function getErrorMessage(data, t) {
34
34
  reason: errorInfo.reason,
35
35
  message: t(errorInfo.reason, errorInfo.metadata)
36
36
  };
37
- if ((0, _shware_utils.hasText)(error.message)) return {
37
+ const message = error?.message;
38
+ if ((0, _shware_utils.hasText)(message)) return {
38
39
  reason: null,
39
- message: error.message
40
+ message
40
41
  };
41
- if (error.status) return {
42
+ if (error?.status) return {
42
43
  reason: null,
43
44
  message: t(error.status)
44
45
  };
@@ -1 +1 @@
1
- {"version":3,"file":"parse.cjs","names":["DetailType"],"sources":["../../src/error/parse.ts"],"sourcesContent":["import { hasText } from '@shware/utils';\nimport type { DefaultNamespace, Namespace, TFunction } from 'i18next';\nimport { type BadRequest, DetailType, type ErrorInfo } from './detail';\nimport type { ResolvedErrorReason } from './reason';\nimport type { ErrorBody } from './status';\n\nexport function getErrorInfo(data: unknown): ErrorInfo | null {\n if (typeof data !== 'object' || data === null || !('error' in data)) return null;\n const { error } = data as ErrorBody;\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n return errorInfo ?? null;\n}\n\n/**\n * @example For axios:\n *\n * const { t } = useTranslation('error');\n *\n * const { message } = getErrorMessage(error.response.data, t);\n * toast.error(message);\n */\nexport function getErrorMessage<Ns extends Namespace = DefaultNamespace, KPrefix = undefined>(\n data: unknown,\n t: TFunction<Ns, KPrefix>\n): { reason: ResolvedErrorReason | null; message: string } {\n // 0. unknown error\n if (typeof data !== 'object' || data === null || !('error' in data)) {\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n }\n\n const { error } = data as ErrorBody;\n\n // 1. from server via Accept-Language header or lng param\n const localizedMessage = error?.details?.find((d) => d['@type'] === DetailType.LOCALIZED_MESSAGE);\n if (localizedMessage) return { reason: null, message: localizedMessage.message };\n\n // 2. from business logic error\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n if (errorInfo) {\n return {\n reason: errorInfo.reason,\n message: t(errorInfo.reason, errorInfo.metadata),\n };\n }\n\n // 3. error message in english\n if (hasText(error.message)) return { reason: null, message: error.message };\n\n // 4. from server via status code\n if (error.status) return { reason: null, message: t(error.status) };\n\n // 5. unknown error\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n}\n\n/**\n * @example For react-hook-form:\n *\n * const { setError } = useForm();\n * const fieldViolations = getFieldViolations(error.response.data);\n * fieldViolations.forEach((violation) => {\n * setError(violation.field, { message: violation.description });\n * });\n */\nexport function getFieldViolations(data: unknown): BadRequest['fieldViolations'] {\n if (typeof data !== 'object' || data === null || !('error' in data)) return [];\n const { error } = data as ErrorBody;\n const badRequest = error?.details?.find((d) => d['@type'] === DetailType.BAD_REQUEST);\n return badRequest?.fieldViolations ?? [];\n}\n"],"mappings":";;;;AAMA,SAAgB,aAAa,MAAiC;CAC5D,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO;CAC5E,MAAM,EAAE,UAAU;CAElB,OADkB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,UAAU,KAC9D;AACtB;;;;;;;;;AAUA,SAAgB,gBACd,MACA,GACyD;CAEzD,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO;EACnE,QAAQ,MAAM,sBAAsB,IAAI;EACxC,OAAO;GAAE,QAAQ;GAAM,SAAS,EAAE,SAAS;EAAE;CAC/C;CAEA,MAAM,EAAE,UAAU;CAGlB,MAAM,mBAAmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,iBAAiB;CAChG,IAAI,kBAAkB,OAAO;EAAE,QAAQ;EAAM,SAAS,iBAAiB;CAAQ;CAG/E,MAAM,YAAY,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,UAAU;CAClF,IAAI,WACF,OAAO;EACL,QAAQ,UAAU;EAClB,SAAS,EAAE,UAAU,QAAQ,UAAU,QAAQ;CACjD;CAIF,KAAA,GAAA,cAAA,QAAA,CAAY,MAAM,OAAO,GAAG,OAAO;EAAE,QAAQ;EAAM,SAAS,MAAM;CAAQ;CAG1E,IAAI,MAAM,QAAQ,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,MAAM,MAAM;CAAE;CAGlE,QAAQ,MAAM,sBAAsB,IAAI;CACxC,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,SAAS;CAAE;AAC/C;;;;;;;;;;AAWA,SAAgB,mBAAmB,MAA8C;CAC/E,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO,CAAC;CAC7E,MAAM,EAAE,UAAU;CAElB,QADmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,WAAW,EAAA,EACjE,mBAAmB,CAAC;AACzC"}
1
+ {"version":3,"file":"parse.cjs","names":["DetailType"],"sources":["../../src/error/parse.ts"],"sourcesContent":["import { hasText } from '@shware/utils';\nimport type { DefaultNamespace, Namespace, TFunction } from 'i18next';\nimport { type BadRequest, DetailType, type ErrorInfo } from './detail';\nimport type { ResolvedErrorReason } from './reason';\nimport type { ErrorBody } from './status';\n\n/**\n * `ErrorBody` as it arrives off the wire: bodies parsed from an HTTP error\n * response with `JSON.parse` guarantee no field at runtime.\n */\ntype LooseErrorBody = {\n error?: Partial<ErrorBody['error']>;\n};\n\nexport function getErrorInfo(data: unknown): ErrorInfo | null {\n if (typeof data !== 'object' || data === null || !('error' in data)) return null;\n const { error } = data as LooseErrorBody;\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n return errorInfo ?? null;\n}\n\n/**\n * @example For axios:\n *\n * const { t } = useTranslation('error');\n *\n * const { message } = getErrorMessage(error.response.data, t);\n * toast.error(message);\n */\nexport function getErrorMessage<Ns extends Namespace = DefaultNamespace, KPrefix = undefined>(\n data: unknown,\n t: TFunction<Ns, KPrefix>\n): { reason: ResolvedErrorReason | null; message: string } {\n // 0. unknown error\n if (typeof data !== 'object' || data === null || !('error' in data)) {\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n }\n\n const { error } = data as LooseErrorBody;\n\n // 1. from server via Accept-Language header or lng param\n const localizedMessage = error?.details?.find((d) => d['@type'] === DetailType.LOCALIZED_MESSAGE);\n if (localizedMessage) return { reason: null, message: localizedMessage.message };\n\n // 2. from business logic error\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n if (errorInfo) {\n return {\n reason: errorInfo.reason,\n message: t(errorInfo.reason, errorInfo.metadata),\n };\n }\n\n // 3. error message in english\n const message = error?.message;\n if (hasText(message)) return { reason: null, message };\n\n // 4. from server via status code\n if (error?.status) return { reason: null, message: t(error.status) };\n\n // 5. unknown error\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n}\n\n/**\n * @example For react-hook-form:\n *\n * const { setError } = useForm();\n * const fieldViolations = getFieldViolations(error.response.data);\n * fieldViolations.forEach((violation) => {\n * setError(violation.field, { message: violation.description });\n * });\n */\nexport function getFieldViolations(data: unknown): BadRequest['fieldViolations'] {\n if (typeof data !== 'object' || data === null || !('error' in data)) return [];\n const { error } = data as LooseErrorBody;\n const badRequest = error?.details?.find((d) => d['@type'] === DetailType.BAD_REQUEST);\n return badRequest?.fieldViolations ?? [];\n}\n"],"mappings":";;;;AAcA,SAAgB,aAAa,MAAiC;CAC5D,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO;CAC5E,MAAM,EAAE,UAAU;CAElB,OADkB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,UAAU,KAC9D;AACtB;;;;;;;;;AAUA,SAAgB,gBACd,MACA,GACyD;CAEzD,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO;EACnE,QAAQ,MAAM,sBAAsB,IAAI;EACxC,OAAO;GAAE,QAAQ;GAAM,SAAS,EAAE,SAAS;EAAE;CAC/C;CAEA,MAAM,EAAE,UAAU;CAGlB,MAAM,mBAAmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,iBAAiB;CAChG,IAAI,kBAAkB,OAAO;EAAE,QAAQ;EAAM,SAAS,iBAAiB;CAAQ;CAG/E,MAAM,YAAY,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,UAAU;CAClF,IAAI,WACF,OAAO;EACL,QAAQ,UAAU;EAClB,SAAS,EAAE,UAAU,QAAQ,UAAU,QAAQ;CACjD;CAIF,MAAM,UAAU,OAAO;CACvB,KAAA,GAAA,cAAA,QAAA,CAAY,OAAO,GAAG,OAAO;EAAE,QAAQ;EAAM;CAAQ;CAGrD,IAAI,OAAO,QAAQ,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,MAAM,MAAM;CAAE;CAGnE,QAAQ,MAAM,sBAAsB,IAAI;CACxC,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,SAAS;CAAE;AAC/C;;;;;;;;;;AAWA,SAAgB,mBAAmB,MAA8C;CAC/E,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO,CAAC;CAC7E,MAAM,EAAE,UAAU;CAElB,QADmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAaA,qBAAAA,WAAW,WAAW,EAAA,EACjE,mBAAmB,CAAC;AACzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.cts","names":[],"sources":["../../src/error/parse.ts"],"mappings":";;;;iBAMgB,aAAa,gBAAgB;;;;;;;;;iBAe7B,gBAAgB,WAAW,YAAY,kBAAkB,qBACvE,eACA,GAAG,UAAU,IAAI;EACd,QAAQ;EAA4B;;;;;;;;;;;iBA0CzB,mBAAmB,gBAAgB"}
1
+ {"version":3,"file":"parse.d.cts","names":[],"sources":["../../src/error/parse.ts"],"mappings":";;;;iBAcgB,aAAa,gBAAgB;;;;;;;;;iBAe7B,gBAAgB,WAAW,YAAY,kBAAkB,qBACvE,eACA,GAAG,UAAU,IAAI;EACd,QAAQ;EAA4B;;;;;;;;;;;iBA2CzB,mBAAmB,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.mts","names":[],"sources":["../../src/error/parse.ts"],"mappings":";;;;iBAMgB,aAAa,gBAAgB;;;;;;;;;iBAe7B,gBAAgB,WAAW,YAAY,kBAAkB,qBACvE,eACA,GAAG,UAAU,IAAI;EACd,QAAQ;EAA4B;;;;;;;;;;;iBA0CzB,mBAAmB,gBAAgB"}
1
+ {"version":3,"file":"parse.d.mts","names":[],"sources":["../../src/error/parse.ts"],"mappings":";;;;iBAcgB,aAAa,gBAAgB;;;;;;;;;iBAe7B,gBAAgB,WAAW,YAAY,kBAAkB,qBACvE,eACA,GAAG,UAAU,IAAI;EACd,QAAQ;EAA4B;;;;;;;;;;;iBA2CzB,mBAAmB,gBAAgB"}
@@ -33,11 +33,12 @@ function getErrorMessage(data, t) {
33
33
  reason: errorInfo.reason,
34
34
  message: t(errorInfo.reason, errorInfo.metadata)
35
35
  };
36
- if (hasText(error.message)) return {
36
+ const message = error?.message;
37
+ if (hasText(message)) return {
37
38
  reason: null,
38
- message: error.message
39
+ message
39
40
  };
40
- if (error.status) return {
41
+ if (error?.status) return {
41
42
  reason: null,
42
43
  message: t(error.status)
43
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"parse.mjs","names":[],"sources":["../../src/error/parse.ts"],"sourcesContent":["import { hasText } from '@shware/utils';\nimport type { DefaultNamespace, Namespace, TFunction } from 'i18next';\nimport { type BadRequest, DetailType, type ErrorInfo } from './detail';\nimport type { ResolvedErrorReason } from './reason';\nimport type { ErrorBody } from './status';\n\nexport function getErrorInfo(data: unknown): ErrorInfo | null {\n if (typeof data !== 'object' || data === null || !('error' in data)) return null;\n const { error } = data as ErrorBody;\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n return errorInfo ?? null;\n}\n\n/**\n * @example For axios:\n *\n * const { t } = useTranslation('error');\n *\n * const { message } = getErrorMessage(error.response.data, t);\n * toast.error(message);\n */\nexport function getErrorMessage<Ns extends Namespace = DefaultNamespace, KPrefix = undefined>(\n data: unknown,\n t: TFunction<Ns, KPrefix>\n): { reason: ResolvedErrorReason | null; message: string } {\n // 0. unknown error\n if (typeof data !== 'object' || data === null || !('error' in data)) {\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n }\n\n const { error } = data as ErrorBody;\n\n // 1. from server via Accept-Language header or lng param\n const localizedMessage = error?.details?.find((d) => d['@type'] === DetailType.LOCALIZED_MESSAGE);\n if (localizedMessage) return { reason: null, message: localizedMessage.message };\n\n // 2. from business logic error\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n if (errorInfo) {\n return {\n reason: errorInfo.reason,\n message: t(errorInfo.reason, errorInfo.metadata),\n };\n }\n\n // 3. error message in english\n if (hasText(error.message)) return { reason: null, message: error.message };\n\n // 4. from server via status code\n if (error.status) return { reason: null, message: t(error.status) };\n\n // 5. unknown error\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n}\n\n/**\n * @example For react-hook-form:\n *\n * const { setError } = useForm();\n * const fieldViolations = getFieldViolations(error.response.data);\n * fieldViolations.forEach((violation) => {\n * setError(violation.field, { message: violation.description });\n * });\n */\nexport function getFieldViolations(data: unknown): BadRequest['fieldViolations'] {\n if (typeof data !== 'object' || data === null || !('error' in data)) return [];\n const { error } = data as ErrorBody;\n const badRequest = error?.details?.find((d) => d['@type'] === DetailType.BAD_REQUEST);\n return badRequest?.fieldViolations ?? [];\n}\n"],"mappings":";;;AAMA,SAAgB,aAAa,MAAiC;CAC5D,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO;CAC5E,MAAM,EAAE,UAAU;CAElB,OADkB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,UAAU,KAC9D;AACtB;;;;;;;;;AAUA,SAAgB,gBACd,MACA,GACyD;CAEzD,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO;EACnE,QAAQ,MAAM,sBAAsB,IAAI;EACxC,OAAO;GAAE,QAAQ;GAAM,SAAS,EAAE,SAAS;EAAE;CAC/C;CAEA,MAAM,EAAE,UAAU;CAGlB,MAAM,mBAAmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,iBAAiB;CAChG,IAAI,kBAAkB,OAAO;EAAE,QAAQ;EAAM,SAAS,iBAAiB;CAAQ;CAG/E,MAAM,YAAY,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,UAAU;CAClF,IAAI,WACF,OAAO;EACL,QAAQ,UAAU;EAClB,SAAS,EAAE,UAAU,QAAQ,UAAU,QAAQ;CACjD;CAIF,IAAI,QAAQ,MAAM,OAAO,GAAG,OAAO;EAAE,QAAQ;EAAM,SAAS,MAAM;CAAQ;CAG1E,IAAI,MAAM,QAAQ,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,MAAM,MAAM;CAAE;CAGlE,QAAQ,MAAM,sBAAsB,IAAI;CACxC,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,SAAS;CAAE;AAC/C;;;;;;;;;;AAWA,SAAgB,mBAAmB,MAA8C;CAC/E,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO,CAAC;CAC7E,MAAM,EAAE,UAAU;CAElB,QADmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,WAAW,EAAA,EACjE,mBAAmB,CAAC;AACzC"}
1
+ {"version":3,"file":"parse.mjs","names":[],"sources":["../../src/error/parse.ts"],"sourcesContent":["import { hasText } from '@shware/utils';\nimport type { DefaultNamespace, Namespace, TFunction } from 'i18next';\nimport { type BadRequest, DetailType, type ErrorInfo } from './detail';\nimport type { ResolvedErrorReason } from './reason';\nimport type { ErrorBody } from './status';\n\n/**\n * `ErrorBody` as it arrives off the wire: bodies parsed from an HTTP error\n * response with `JSON.parse` guarantee no field at runtime.\n */\ntype LooseErrorBody = {\n error?: Partial<ErrorBody['error']>;\n};\n\nexport function getErrorInfo(data: unknown): ErrorInfo | null {\n if (typeof data !== 'object' || data === null || !('error' in data)) return null;\n const { error } = data as LooseErrorBody;\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n return errorInfo ?? null;\n}\n\n/**\n * @example For axios:\n *\n * const { t } = useTranslation('error');\n *\n * const { message } = getErrorMessage(error.response.data, t);\n * toast.error(message);\n */\nexport function getErrorMessage<Ns extends Namespace = DefaultNamespace, KPrefix = undefined>(\n data: unknown,\n t: TFunction<Ns, KPrefix>\n): { reason: ResolvedErrorReason | null; message: string } {\n // 0. unknown error\n if (typeof data !== 'object' || data === null || !('error' in data)) {\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n }\n\n const { error } = data as LooseErrorBody;\n\n // 1. from server via Accept-Language header or lng param\n const localizedMessage = error?.details?.find((d) => d['@type'] === DetailType.LOCALIZED_MESSAGE);\n if (localizedMessage) return { reason: null, message: localizedMessage.message };\n\n // 2. from business logic error\n const errorInfo = error?.details?.find((d) => d['@type'] === DetailType.ERROR_INFO);\n if (errorInfo) {\n return {\n reason: errorInfo.reason,\n message: t(errorInfo.reason, errorInfo.metadata),\n };\n }\n\n // 3. error message in english\n const message = error?.message;\n if (hasText(message)) return { reason: null, message };\n\n // 4. from server via status code\n if (error?.status) return { reason: null, message: t(error.status) };\n\n // 5. unknown error\n console.error('Unknown API error:', data);\n return { reason: null, message: t('UNKNOWN') };\n}\n\n/**\n * @example For react-hook-form:\n *\n * const { setError } = useForm();\n * const fieldViolations = getFieldViolations(error.response.data);\n * fieldViolations.forEach((violation) => {\n * setError(violation.field, { message: violation.description });\n * });\n */\nexport function getFieldViolations(data: unknown): BadRequest['fieldViolations'] {\n if (typeof data !== 'object' || data === null || !('error' in data)) return [];\n const { error } = data as LooseErrorBody;\n const badRequest = error?.details?.find((d) => d['@type'] === DetailType.BAD_REQUEST);\n return badRequest?.fieldViolations ?? [];\n}\n"],"mappings":";;;AAcA,SAAgB,aAAa,MAAiC;CAC5D,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO;CAC5E,MAAM,EAAE,UAAU;CAElB,OADkB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,UAAU,KAC9D;AACtB;;;;;;;;;AAUA,SAAgB,gBACd,MACA,GACyD;CAEzD,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO;EACnE,QAAQ,MAAM,sBAAsB,IAAI;EACxC,OAAO;GAAE,QAAQ;GAAM,SAAS,EAAE,SAAS;EAAE;CAC/C;CAEA,MAAM,EAAE,UAAU;CAGlB,MAAM,mBAAmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,iBAAiB;CAChG,IAAI,kBAAkB,OAAO;EAAE,QAAQ;EAAM,SAAS,iBAAiB;CAAQ;CAG/E,MAAM,YAAY,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,UAAU;CAClF,IAAI,WACF,OAAO;EACL,QAAQ,UAAU;EAClB,SAAS,EAAE,UAAU,QAAQ,UAAU,QAAQ;CACjD;CAIF,MAAM,UAAU,OAAO;CACvB,IAAI,QAAQ,OAAO,GAAG,OAAO;EAAE,QAAQ;EAAM;CAAQ;CAGrD,IAAI,OAAO,QAAQ,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,MAAM,MAAM;CAAE;CAGnE,QAAQ,MAAM,sBAAsB,IAAI;CACxC,OAAO;EAAE,QAAQ;EAAM,SAAS,EAAE,SAAS;CAAE;AAC/C;;;;;;;;;;AAWA,SAAgB,mBAAmB,MAA8C;CAC/E,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO,OAAO,CAAC;CAC7E,MAAM,EAAE,UAAU;CAElB,QADmB,OAAO,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,WAAW,EAAA,EACjE,mBAAmB,CAAC;AACzC"}
@@ -44,7 +44,7 @@ var StatusError = class StatusError extends Error {
44
44
  status;
45
45
  body;
46
46
  constructor(status, body) {
47
- super(body?.error?.message ?? `Status Error: ${status}`);
47
+ super(body?.error.message ?? `Status Error: ${status}`);
48
48
  this.name = "StatusError";
49
49
  this.status = status;
50
50
  this.body = body;
@@ -1 +1 @@
1
- {"version":3,"file":"status.cjs","names":[],"sources":["../../src/error/status.ts"],"sourcesContent":["import type { Detail, Details } from './detail';\n\nexport const Code = {\n // Not an error; returned on success\n //\n // HTTP Mapping: 200 OK\n OK: 200,\n\n // The operation was cancelled, typically by the caller.\n //\n // HTTP Mapping: 499 Client Closed Request\n CANCELLED: 499,\n\n // Unknown error. For example, this error may be returned when\n // a `Status` value received from another address space belongs to\n // an error space that is not known in this address space. Also,\n // errors raised by APIs that do not return enough error information\n // may be converted to this error.\n //\n // HTTP Mapping: 500 Internal Server Error\n UNKNOWN: 500,\n\n // The client specified an invalid argument. Note that this differs\n // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments\n // that are problematic regardless of the state of the system\n // (e.g., a malformed file name).\n //\n // HTTP Mapping: 400 Bad Request\n INVALID_ARGUMENT: 400,\n\n // The deadline expired before the operation could complete. For operations\n // that change the state of the system, this error may be returned\n // even if the operation has completed successfully. For example, a\n // successful response from a server could have been delayed long\n // enough for the deadline to expire.\n //\n // HTTP Mapping: 504 Gateway Timeout\n DEADLINE_EXCEEDED: 504,\n\n // Some requested entity (e.g., file or directory) was not found.\n //\n // Note to server developers: if a request is denied for an entire class\n // of users, such as gradual feature rollout or undocumented whitelist,\n // `NOT_FOUND` may be used. If a request is denied for some users within\n // a class of users, such as user-based access control, `PERMISSION_DENIED`\n // must be used.\n //\n // HTTP Mapping: 404 Not Found\n NOT_FOUND: 404,\n\n // The entity that a client attempted to create (e.g., file or directory)\n // already exists.\n //\n // HTTP Mapping: 409 Conflict\n ALREADY_EXISTS: 409,\n\n // The caller does not have permission to execute the specified\n // operation. `PERMISSION_DENIED` must not be used for rejections\n // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`\n // instead for those errors). `PERMISSION_DENIED` must not be\n // used if the caller can not be identified (use `UNAUTHENTICATED`\n // instead for those errors). This error code does not imply the\n // request is valid or the requested entity exists or satisfies\n // other pre-conditions.\n //\n // HTTP Mapping: 403 Forbidden\n PERMISSION_DENIED: 403,\n\n // Some resource has been exhausted, perhaps a per-user quota, or\n // perhaps the entire file system is out of space.\n //\n // HTTP Mapping: 429 Too Many Requests\n RESOURCE_EXHAUSTED: 429,\n\n // The operation was rejected because the system is not in a state\n // required for the operation's execution. For example, the directory\n // to be deleted is non-empty, a rmdir operation is applied to\n // a non-directory, etc.\n //\n // Service implementors can use the following guidelines to decide\n // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:\n // (a) Use `UNAVAILABLE` if the client can retry just the failing call.\n // (b) Use `ABORTED` if the client should retry at a higher level\n // (e.g., when a client-specified test-and-set fails, indicating the\n // client should restart a read-modify-write sequence).\n // (c) Use `FAILED_PRECONDITION` if the client should not retry until\n // the system state has been explicitly fixed. E.g., if a \"rmdir\"\n // fails because the directory is non-empty, `FAILED_PRECONDITION`\n // should be returned since the client should not retry unless\n // the files are deleted from the directory.\n //\n // HTTP Mapping: 400 Bad Request\n FAILED_PRECONDITION: 400,\n\n // The operation was aborted, typically due to a concurrency issue such as\n // a sequencer check failure or transaction abort.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 409 Conflict\n ABORTED: 409,\n\n // The operation was attempted past the valid range. E.g., seeking or\n // reading past end-of-file.\n //\n // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may\n // be fixed if the system state changes. For example, a 32-bit file\n // system will generate `INVALID_ARGUMENT` if asked to read at an\n // offset that is not in the range [0,2^32-1], but it will generate\n // `OUT_OF_RANGE` if asked to read from an offset past the current\n // file size.\n //\n // There is a fair bit of overlap between `FAILED_PRECONDITION` and\n // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific\n // error) when it applies so that callers who are iterating through\n // a space can easily look for an `OUT_OF_RANGE` error to detect when\n // they are done.\n //\n // HTTP Mapping: 400 Bad Request\n OUT_OF_RANGE: 400,\n\n // The operation is not implemented or is not supported/enabled in this\n // service.\n //\n // HTTP Mapping: 501 Not Implemented\n UNIMPLEMENTED: 501,\n\n // Internal errors. This means that some invariants expected by the\n // underlying system have been broken. This error code is reserved\n // for serious errors.\n //\n // HTTP Mapping: 500 Internal Server Error\n INTERNAL: 500,\n\n // The service is currently unavailable. This is most likely a\n // transient condition, which can be corrected by retrying with\n // a backoff. Note that it is not always safe to retry\n // non-idempotent operations.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 503 Service Unavailable\n UNAVAILABLE: 503,\n\n // Unrecoverable data loss or corruption.\n //\n // HTTP Mapping: 500 Internal Server Error\n DATA_LOSS: 500,\n\n // The request does not have valid authentication credentials for the\n // operation.\n //\n // HTTP Mapping: 401 Unauthorized\n UNAUTHENTICATED: 401,\n\n // The request method is not supported by the server and cannot be handled.\n //\n // HTTP Mapping: 405 Method Not Allowed\n METHOD_NOT_ALLOWED: 405,\n} as const;\n\nexport const DEFAULT_MESSAGES: Record<keyof typeof Code, string> = {\n OK: 'OK',\n CANCELLED: 'The operation was cancelled',\n UNKNOWN: 'Unknown error',\n INVALID_ARGUMENT: 'The client specified an invalid argument',\n DEADLINE_EXCEEDED: 'The deadline expired before the operation could complete',\n NOT_FOUND: 'Some requested entity was not found',\n ALREADY_EXISTS: 'The entity that a client attempted to create already exists',\n PERMISSION_DENIED: 'The caller does not have permission to execute the specified operation',\n RESOURCE_EXHAUSTED: 'Some resource has been exhausted',\n FAILED_PRECONDITION:\n \"The operation was rejected because the system is not in a state required for the operation's execution\",\n ABORTED: 'The operation was aborted',\n OUT_OF_RANGE: 'The operation was attempted past the valid range',\n UNIMPLEMENTED: 'The operation is not implemented or is not supported/enabled in this service',\n INTERNAL: 'Internal errors',\n UNAVAILABLE: 'The service is currently unavailable',\n DATA_LOSS: 'Unrecoverable data loss or corruption',\n UNAUTHENTICATED: 'The request does not have valid authentication credentials for the operation',\n METHOD_NOT_ALLOWED: 'The request method is not supported by the server and cannot be handled',\n};\n\nexport interface ErrorBody {\n error: {\n code: number;\n status: keyof typeof Code;\n message: string;\n details: Detail[];\n };\n}\n\nexport class StatusError extends Error {\n readonly status: number;\n readonly body?: ErrorBody;\n\n constructor(status: number, body?: ErrorBody) {\n super(body?.error?.message ?? `Status Error: ${status}`);\n this.name = 'StatusError';\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, StatusError.prototype);\n }\n}\n\nexport class StatusCode {\n code: keyof typeof Code;\n message?: string;\n private constructor(code: keyof typeof Code, message?: string) {\n this.code = code;\n this.message = message;\n }\n\n static of(code: keyof typeof Code, message?: string) {\n return new StatusCode(code, message ?? DEFAULT_MESSAGES[code]);\n }\n\n body(details?: Details): ErrorBody {\n return {\n error: {\n code: Code[this.code],\n status: this.code,\n message: this.message ?? '',\n details: details?.list ?? [],\n },\n };\n }\n\n error(details?: Details): Error {\n const body = this.body(details);\n if (Status.adapter) return Status.adapter(Code[this.code], body);\n return new StatusError(Code[this.code], body);\n }\n\n response(details?: Details): Response {\n const body = this.body(details);\n return Response.json(body, { status: body.error.code });\n }\n}\n\nexport class Status {\n static adapter?: (status: number, response: ErrorBody) => Error;\n\n static ok = (message?: string) => StatusCode.of('OK', message);\n static cancelled = (message?: string) => StatusCode.of('CANCELLED', message);\n static unknown = (message?: string) => StatusCode.of('UNKNOWN', message);\n static invalidArgument = (message?: string) => StatusCode.of('INVALID_ARGUMENT', message);\n static deadlineExceeded = (message?: string) => StatusCode.of('DEADLINE_EXCEEDED', message);\n static notFound = (message?: string) => StatusCode.of('NOT_FOUND', message);\n static alreadyExists = (message?: string) => StatusCode.of('ALREADY_EXISTS', message);\n static permissionDenied = (message?: string) => StatusCode.of('PERMISSION_DENIED', message);\n static unauthorized = (message?: string) => StatusCode.of('UNAUTHENTICATED', message);\n static resourceExhausted = (message?: string) => StatusCode.of('RESOURCE_EXHAUSTED', message);\n static failedPrecondition = (message?: string) => StatusCode.of('FAILED_PRECONDITION', message);\n static aborted = (message?: string) => StatusCode.of('ABORTED', message);\n static outOfRange = (message?: string) => StatusCode.of('OUT_OF_RANGE', message);\n static unimplemented = (message?: string) => StatusCode.of('UNIMPLEMENTED', message);\n static internal = (message?: string) => StatusCode.of('INTERNAL', message);\n static unavailable = (message?: string) => StatusCode.of('UNAVAILABLE', message);\n static dataLoss = (message?: string) => StatusCode.of('DATA_LOSS', message);\n static methodNotAllowed = (message?: string) => StatusCode.of('METHOD_NOT_ALLOWED', message);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"status.cjs","names":[],"sources":["../../src/error/status.ts"],"sourcesContent":["import type { Detail, Details } from './detail';\n\nexport const Code = {\n // Not an error; returned on success\n //\n // HTTP Mapping: 200 OK\n OK: 200,\n\n // The operation was cancelled, typically by the caller.\n //\n // HTTP Mapping: 499 Client Closed Request\n CANCELLED: 499,\n\n // Unknown error. For example, this error may be returned when\n // a `Status` value received from another address space belongs to\n // an error space that is not known in this address space. Also,\n // errors raised by APIs that do not return enough error information\n // may be converted to this error.\n //\n // HTTP Mapping: 500 Internal Server Error\n UNKNOWN: 500,\n\n // The client specified an invalid argument. Note that this differs\n // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments\n // that are problematic regardless of the state of the system\n // (e.g., a malformed file name).\n //\n // HTTP Mapping: 400 Bad Request\n INVALID_ARGUMENT: 400,\n\n // The deadline expired before the operation could complete. For operations\n // that change the state of the system, this error may be returned\n // even if the operation has completed successfully. For example, a\n // successful response from a server could have been delayed long\n // enough for the deadline to expire.\n //\n // HTTP Mapping: 504 Gateway Timeout\n DEADLINE_EXCEEDED: 504,\n\n // Some requested entity (e.g., file or directory) was not found.\n //\n // Note to server developers: if a request is denied for an entire class\n // of users, such as gradual feature rollout or undocumented whitelist,\n // `NOT_FOUND` may be used. If a request is denied for some users within\n // a class of users, such as user-based access control, `PERMISSION_DENIED`\n // must be used.\n //\n // HTTP Mapping: 404 Not Found\n NOT_FOUND: 404,\n\n // The entity that a client attempted to create (e.g., file or directory)\n // already exists.\n //\n // HTTP Mapping: 409 Conflict\n ALREADY_EXISTS: 409,\n\n // The caller does not have permission to execute the specified\n // operation. `PERMISSION_DENIED` must not be used for rejections\n // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`\n // instead for those errors). `PERMISSION_DENIED` must not be\n // used if the caller can not be identified (use `UNAUTHENTICATED`\n // instead for those errors). This error code does not imply the\n // request is valid or the requested entity exists or satisfies\n // other pre-conditions.\n //\n // HTTP Mapping: 403 Forbidden\n PERMISSION_DENIED: 403,\n\n // Some resource has been exhausted, perhaps a per-user quota, or\n // perhaps the entire file system is out of space.\n //\n // HTTP Mapping: 429 Too Many Requests\n RESOURCE_EXHAUSTED: 429,\n\n // The operation was rejected because the system is not in a state\n // required for the operation's execution. For example, the directory\n // to be deleted is non-empty, a rmdir operation is applied to\n // a non-directory, etc.\n //\n // Service implementors can use the following guidelines to decide\n // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:\n // (a) Use `UNAVAILABLE` if the client can retry just the failing call.\n // (b) Use `ABORTED` if the client should retry at a higher level\n // (e.g., when a client-specified test-and-set fails, indicating the\n // client should restart a read-modify-write sequence).\n // (c) Use `FAILED_PRECONDITION` if the client should not retry until\n // the system state has been explicitly fixed. E.g., if a \"rmdir\"\n // fails because the directory is non-empty, `FAILED_PRECONDITION`\n // should be returned since the client should not retry unless\n // the files are deleted from the directory.\n //\n // HTTP Mapping: 400 Bad Request\n FAILED_PRECONDITION: 400,\n\n // The operation was aborted, typically due to a concurrency issue such as\n // a sequencer check failure or transaction abort.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 409 Conflict\n ABORTED: 409,\n\n // The operation was attempted past the valid range. E.g., seeking or\n // reading past end-of-file.\n //\n // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may\n // be fixed if the system state changes. For example, a 32-bit file\n // system will generate `INVALID_ARGUMENT` if asked to read at an\n // offset that is not in the range [0,2^32-1], but it will generate\n // `OUT_OF_RANGE` if asked to read from an offset past the current\n // file size.\n //\n // There is a fair bit of overlap between `FAILED_PRECONDITION` and\n // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific\n // error) when it applies so that callers who are iterating through\n // a space can easily look for an `OUT_OF_RANGE` error to detect when\n // they are done.\n //\n // HTTP Mapping: 400 Bad Request\n OUT_OF_RANGE: 400,\n\n // The operation is not implemented or is not supported/enabled in this\n // service.\n //\n // HTTP Mapping: 501 Not Implemented\n UNIMPLEMENTED: 501,\n\n // Internal errors. This means that some invariants expected by the\n // underlying system have been broken. This error code is reserved\n // for serious errors.\n //\n // HTTP Mapping: 500 Internal Server Error\n INTERNAL: 500,\n\n // The service is currently unavailable. This is most likely a\n // transient condition, which can be corrected by retrying with\n // a backoff. Note that it is not always safe to retry\n // non-idempotent operations.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 503 Service Unavailable\n UNAVAILABLE: 503,\n\n // Unrecoverable data loss or corruption.\n //\n // HTTP Mapping: 500 Internal Server Error\n DATA_LOSS: 500,\n\n // The request does not have valid authentication credentials for the\n // operation.\n //\n // HTTP Mapping: 401 Unauthorized\n UNAUTHENTICATED: 401,\n\n // The request method is not supported by the server and cannot be handled.\n //\n // HTTP Mapping: 405 Method Not Allowed\n METHOD_NOT_ALLOWED: 405,\n} as const;\n\nexport const DEFAULT_MESSAGES: Record<keyof typeof Code, string> = {\n OK: 'OK',\n CANCELLED: 'The operation was cancelled',\n UNKNOWN: 'Unknown error',\n INVALID_ARGUMENT: 'The client specified an invalid argument',\n DEADLINE_EXCEEDED: 'The deadline expired before the operation could complete',\n NOT_FOUND: 'Some requested entity was not found',\n ALREADY_EXISTS: 'The entity that a client attempted to create already exists',\n PERMISSION_DENIED: 'The caller does not have permission to execute the specified operation',\n RESOURCE_EXHAUSTED: 'Some resource has been exhausted',\n FAILED_PRECONDITION:\n \"The operation was rejected because the system is not in a state required for the operation's execution\",\n ABORTED: 'The operation was aborted',\n OUT_OF_RANGE: 'The operation was attempted past the valid range',\n UNIMPLEMENTED: 'The operation is not implemented or is not supported/enabled in this service',\n INTERNAL: 'Internal errors',\n UNAVAILABLE: 'The service is currently unavailable',\n DATA_LOSS: 'Unrecoverable data loss or corruption',\n UNAUTHENTICATED: 'The request does not have valid authentication credentials for the operation',\n METHOD_NOT_ALLOWED: 'The request method is not supported by the server and cannot be handled',\n};\n\nexport interface ErrorBody {\n error: {\n code: number;\n status: keyof typeof Code;\n message: string;\n details: Detail[];\n };\n}\n\nexport class StatusError extends Error {\n readonly status: number;\n readonly body?: ErrorBody;\n\n constructor(status: number, body?: ErrorBody) {\n super(body?.error.message ?? `Status Error: ${status}`);\n this.name = 'StatusError';\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, StatusError.prototype);\n }\n}\n\nexport class StatusCode {\n code: keyof typeof Code;\n message?: string;\n private constructor(code: keyof typeof Code, message?: string) {\n this.code = code;\n this.message = message;\n }\n\n static of(code: keyof typeof Code, message?: string) {\n return new StatusCode(code, message ?? DEFAULT_MESSAGES[code]);\n }\n\n body(details?: Details): ErrorBody {\n return {\n error: {\n code: Code[this.code],\n status: this.code,\n message: this.message ?? '',\n details: details?.list ?? [],\n },\n };\n }\n\n error(details?: Details): Error {\n const body = this.body(details);\n if (Status.adapter) return Status.adapter(Code[this.code], body);\n return new StatusError(Code[this.code], body);\n }\n\n response(details?: Details): Response {\n const body = this.body(details);\n return Response.json(body, { status: body.error.code });\n }\n}\n\nexport class Status {\n static adapter?: (status: number, response: ErrorBody) => Error;\n\n static ok = (message?: string) => StatusCode.of('OK', message);\n static cancelled = (message?: string) => StatusCode.of('CANCELLED', message);\n static unknown = (message?: string) => StatusCode.of('UNKNOWN', message);\n static invalidArgument = (message?: string) => StatusCode.of('INVALID_ARGUMENT', message);\n static deadlineExceeded = (message?: string) => StatusCode.of('DEADLINE_EXCEEDED', message);\n static notFound = (message?: string) => StatusCode.of('NOT_FOUND', message);\n static alreadyExists = (message?: string) => StatusCode.of('ALREADY_EXISTS', message);\n static permissionDenied = (message?: string) => StatusCode.of('PERMISSION_DENIED', message);\n static unauthorized = (message?: string) => StatusCode.of('UNAUTHENTICATED', message);\n static resourceExhausted = (message?: string) => StatusCode.of('RESOURCE_EXHAUSTED', message);\n static failedPrecondition = (message?: string) => StatusCode.of('FAILED_PRECONDITION', message);\n static aborted = (message?: string) => StatusCode.of('ABORTED', message);\n static outOfRange = (message?: string) => StatusCode.of('OUT_OF_RANGE', message);\n static unimplemented = (message?: string) => StatusCode.of('UNIMPLEMENTED', message);\n static internal = (message?: string) => StatusCode.of('INTERNAL', message);\n static unavailable = (message?: string) => StatusCode.of('UNAVAILABLE', message);\n static dataLoss = (message?: string) => StatusCode.of('DATA_LOSS', message);\n static methodNotAllowed = (message?: string) => StatusCode.of('METHOD_NOT_ALLOWED', message);\n}\n"],"mappings":"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"}
@@ -43,7 +43,7 @@ var StatusError = class StatusError extends Error {
43
43
  status;
44
44
  body;
45
45
  constructor(status, body) {
46
- super(body?.error?.message ?? `Status Error: ${status}`);
46
+ super(body?.error.message ?? `Status Error: ${status}`);
47
47
  this.name = "StatusError";
48
48
  this.status = status;
49
49
  this.body = body;
@@ -1 +1 @@
1
- {"version":3,"file":"status.mjs","names":[],"sources":["../../src/error/status.ts"],"sourcesContent":["import type { Detail, Details } from './detail';\n\nexport const Code = {\n // Not an error; returned on success\n //\n // HTTP Mapping: 200 OK\n OK: 200,\n\n // The operation was cancelled, typically by the caller.\n //\n // HTTP Mapping: 499 Client Closed Request\n CANCELLED: 499,\n\n // Unknown error. For example, this error may be returned when\n // a `Status` value received from another address space belongs to\n // an error space that is not known in this address space. Also,\n // errors raised by APIs that do not return enough error information\n // may be converted to this error.\n //\n // HTTP Mapping: 500 Internal Server Error\n UNKNOWN: 500,\n\n // The client specified an invalid argument. Note that this differs\n // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments\n // that are problematic regardless of the state of the system\n // (e.g., a malformed file name).\n //\n // HTTP Mapping: 400 Bad Request\n INVALID_ARGUMENT: 400,\n\n // The deadline expired before the operation could complete. For operations\n // that change the state of the system, this error may be returned\n // even if the operation has completed successfully. For example, a\n // successful response from a server could have been delayed long\n // enough for the deadline to expire.\n //\n // HTTP Mapping: 504 Gateway Timeout\n DEADLINE_EXCEEDED: 504,\n\n // Some requested entity (e.g., file or directory) was not found.\n //\n // Note to server developers: if a request is denied for an entire class\n // of users, such as gradual feature rollout or undocumented whitelist,\n // `NOT_FOUND` may be used. If a request is denied for some users within\n // a class of users, such as user-based access control, `PERMISSION_DENIED`\n // must be used.\n //\n // HTTP Mapping: 404 Not Found\n NOT_FOUND: 404,\n\n // The entity that a client attempted to create (e.g., file or directory)\n // already exists.\n //\n // HTTP Mapping: 409 Conflict\n ALREADY_EXISTS: 409,\n\n // The caller does not have permission to execute the specified\n // operation. `PERMISSION_DENIED` must not be used for rejections\n // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`\n // instead for those errors). `PERMISSION_DENIED` must not be\n // used if the caller can not be identified (use `UNAUTHENTICATED`\n // instead for those errors). This error code does not imply the\n // request is valid or the requested entity exists or satisfies\n // other pre-conditions.\n //\n // HTTP Mapping: 403 Forbidden\n PERMISSION_DENIED: 403,\n\n // Some resource has been exhausted, perhaps a per-user quota, or\n // perhaps the entire file system is out of space.\n //\n // HTTP Mapping: 429 Too Many Requests\n RESOURCE_EXHAUSTED: 429,\n\n // The operation was rejected because the system is not in a state\n // required for the operation's execution. For example, the directory\n // to be deleted is non-empty, a rmdir operation is applied to\n // a non-directory, etc.\n //\n // Service implementors can use the following guidelines to decide\n // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:\n // (a) Use `UNAVAILABLE` if the client can retry just the failing call.\n // (b) Use `ABORTED` if the client should retry at a higher level\n // (e.g., when a client-specified test-and-set fails, indicating the\n // client should restart a read-modify-write sequence).\n // (c) Use `FAILED_PRECONDITION` if the client should not retry until\n // the system state has been explicitly fixed. E.g., if a \"rmdir\"\n // fails because the directory is non-empty, `FAILED_PRECONDITION`\n // should be returned since the client should not retry unless\n // the files are deleted from the directory.\n //\n // HTTP Mapping: 400 Bad Request\n FAILED_PRECONDITION: 400,\n\n // The operation was aborted, typically due to a concurrency issue such as\n // a sequencer check failure or transaction abort.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 409 Conflict\n ABORTED: 409,\n\n // The operation was attempted past the valid range. E.g., seeking or\n // reading past end-of-file.\n //\n // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may\n // be fixed if the system state changes. For example, a 32-bit file\n // system will generate `INVALID_ARGUMENT` if asked to read at an\n // offset that is not in the range [0,2^32-1], but it will generate\n // `OUT_OF_RANGE` if asked to read from an offset past the current\n // file size.\n //\n // There is a fair bit of overlap between `FAILED_PRECONDITION` and\n // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific\n // error) when it applies so that callers who are iterating through\n // a space can easily look for an `OUT_OF_RANGE` error to detect when\n // they are done.\n //\n // HTTP Mapping: 400 Bad Request\n OUT_OF_RANGE: 400,\n\n // The operation is not implemented or is not supported/enabled in this\n // service.\n //\n // HTTP Mapping: 501 Not Implemented\n UNIMPLEMENTED: 501,\n\n // Internal errors. This means that some invariants expected by the\n // underlying system have been broken. This error code is reserved\n // for serious errors.\n //\n // HTTP Mapping: 500 Internal Server Error\n INTERNAL: 500,\n\n // The service is currently unavailable. This is most likely a\n // transient condition, which can be corrected by retrying with\n // a backoff. Note that it is not always safe to retry\n // non-idempotent operations.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 503 Service Unavailable\n UNAVAILABLE: 503,\n\n // Unrecoverable data loss or corruption.\n //\n // HTTP Mapping: 500 Internal Server Error\n DATA_LOSS: 500,\n\n // The request does not have valid authentication credentials for the\n // operation.\n //\n // HTTP Mapping: 401 Unauthorized\n UNAUTHENTICATED: 401,\n\n // The request method is not supported by the server and cannot be handled.\n //\n // HTTP Mapping: 405 Method Not Allowed\n METHOD_NOT_ALLOWED: 405,\n} as const;\n\nexport const DEFAULT_MESSAGES: Record<keyof typeof Code, string> = {\n OK: 'OK',\n CANCELLED: 'The operation was cancelled',\n UNKNOWN: 'Unknown error',\n INVALID_ARGUMENT: 'The client specified an invalid argument',\n DEADLINE_EXCEEDED: 'The deadline expired before the operation could complete',\n NOT_FOUND: 'Some requested entity was not found',\n ALREADY_EXISTS: 'The entity that a client attempted to create already exists',\n PERMISSION_DENIED: 'The caller does not have permission to execute the specified operation',\n RESOURCE_EXHAUSTED: 'Some resource has been exhausted',\n FAILED_PRECONDITION:\n \"The operation was rejected because the system is not in a state required for the operation's execution\",\n ABORTED: 'The operation was aborted',\n OUT_OF_RANGE: 'The operation was attempted past the valid range',\n UNIMPLEMENTED: 'The operation is not implemented or is not supported/enabled in this service',\n INTERNAL: 'Internal errors',\n UNAVAILABLE: 'The service is currently unavailable',\n DATA_LOSS: 'Unrecoverable data loss or corruption',\n UNAUTHENTICATED: 'The request does not have valid authentication credentials for the operation',\n METHOD_NOT_ALLOWED: 'The request method is not supported by the server and cannot be handled',\n};\n\nexport interface ErrorBody {\n error: {\n code: number;\n status: keyof typeof Code;\n message: string;\n details: Detail[];\n };\n}\n\nexport class StatusError extends Error {\n readonly status: number;\n readonly body?: ErrorBody;\n\n constructor(status: number, body?: ErrorBody) {\n super(body?.error?.message ?? `Status Error: ${status}`);\n this.name = 'StatusError';\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, StatusError.prototype);\n }\n}\n\nexport class StatusCode {\n code: keyof typeof Code;\n message?: string;\n private constructor(code: keyof typeof Code, message?: string) {\n this.code = code;\n this.message = message;\n }\n\n static of(code: keyof typeof Code, message?: string) {\n return new StatusCode(code, message ?? DEFAULT_MESSAGES[code]);\n }\n\n body(details?: Details): ErrorBody {\n return {\n error: {\n code: Code[this.code],\n status: this.code,\n message: this.message ?? '',\n details: details?.list ?? [],\n },\n };\n }\n\n error(details?: Details): Error {\n const body = this.body(details);\n if (Status.adapter) return Status.adapter(Code[this.code], body);\n return new StatusError(Code[this.code], body);\n }\n\n response(details?: Details): Response {\n const body = this.body(details);\n return Response.json(body, { status: body.error.code });\n }\n}\n\nexport class Status {\n static adapter?: (status: number, response: ErrorBody) => Error;\n\n static ok = (message?: string) => StatusCode.of('OK', message);\n static cancelled = (message?: string) => StatusCode.of('CANCELLED', message);\n static unknown = (message?: string) => StatusCode.of('UNKNOWN', message);\n static invalidArgument = (message?: string) => StatusCode.of('INVALID_ARGUMENT', message);\n static deadlineExceeded = (message?: string) => StatusCode.of('DEADLINE_EXCEEDED', message);\n static notFound = (message?: string) => StatusCode.of('NOT_FOUND', message);\n static alreadyExists = (message?: string) => StatusCode.of('ALREADY_EXISTS', message);\n static permissionDenied = (message?: string) => StatusCode.of('PERMISSION_DENIED', message);\n static unauthorized = (message?: string) => StatusCode.of('UNAUTHENTICATED', message);\n static resourceExhausted = (message?: string) => StatusCode.of('RESOURCE_EXHAUSTED', message);\n static failedPrecondition = (message?: string) => StatusCode.of('FAILED_PRECONDITION', message);\n static aborted = (message?: string) => StatusCode.of('ABORTED', message);\n static outOfRange = (message?: string) => StatusCode.of('OUT_OF_RANGE', message);\n static unimplemented = (message?: string) => StatusCode.of('UNIMPLEMENTED', message);\n static internal = (message?: string) => StatusCode.of('INTERNAL', message);\n static unavailable = (message?: string) => StatusCode.of('UNAVAILABLE', message);\n static dataLoss = (message?: string) => StatusCode.of('DATA_LOSS', message);\n static methodNotAllowed = (message?: string) => StatusCode.of('METHOD_NOT_ALLOWED', message);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"status.mjs","names":[],"sources":["../../src/error/status.ts"],"sourcesContent":["import type { Detail, Details } from './detail';\n\nexport const Code = {\n // Not an error; returned on success\n //\n // HTTP Mapping: 200 OK\n OK: 200,\n\n // The operation was cancelled, typically by the caller.\n //\n // HTTP Mapping: 499 Client Closed Request\n CANCELLED: 499,\n\n // Unknown error. For example, this error may be returned when\n // a `Status` value received from another address space belongs to\n // an error space that is not known in this address space. Also,\n // errors raised by APIs that do not return enough error information\n // may be converted to this error.\n //\n // HTTP Mapping: 500 Internal Server Error\n UNKNOWN: 500,\n\n // The client specified an invalid argument. Note that this differs\n // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments\n // that are problematic regardless of the state of the system\n // (e.g., a malformed file name).\n //\n // HTTP Mapping: 400 Bad Request\n INVALID_ARGUMENT: 400,\n\n // The deadline expired before the operation could complete. For operations\n // that change the state of the system, this error may be returned\n // even if the operation has completed successfully. For example, a\n // successful response from a server could have been delayed long\n // enough for the deadline to expire.\n //\n // HTTP Mapping: 504 Gateway Timeout\n DEADLINE_EXCEEDED: 504,\n\n // Some requested entity (e.g., file or directory) was not found.\n //\n // Note to server developers: if a request is denied for an entire class\n // of users, such as gradual feature rollout or undocumented whitelist,\n // `NOT_FOUND` may be used. If a request is denied for some users within\n // a class of users, such as user-based access control, `PERMISSION_DENIED`\n // must be used.\n //\n // HTTP Mapping: 404 Not Found\n NOT_FOUND: 404,\n\n // The entity that a client attempted to create (e.g., file or directory)\n // already exists.\n //\n // HTTP Mapping: 409 Conflict\n ALREADY_EXISTS: 409,\n\n // The caller does not have permission to execute the specified\n // operation. `PERMISSION_DENIED` must not be used for rejections\n // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`\n // instead for those errors). `PERMISSION_DENIED` must not be\n // used if the caller can not be identified (use `UNAUTHENTICATED`\n // instead for those errors). This error code does not imply the\n // request is valid or the requested entity exists or satisfies\n // other pre-conditions.\n //\n // HTTP Mapping: 403 Forbidden\n PERMISSION_DENIED: 403,\n\n // Some resource has been exhausted, perhaps a per-user quota, or\n // perhaps the entire file system is out of space.\n //\n // HTTP Mapping: 429 Too Many Requests\n RESOURCE_EXHAUSTED: 429,\n\n // The operation was rejected because the system is not in a state\n // required for the operation's execution. For example, the directory\n // to be deleted is non-empty, a rmdir operation is applied to\n // a non-directory, etc.\n //\n // Service implementors can use the following guidelines to decide\n // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:\n // (a) Use `UNAVAILABLE` if the client can retry just the failing call.\n // (b) Use `ABORTED` if the client should retry at a higher level\n // (e.g., when a client-specified test-and-set fails, indicating the\n // client should restart a read-modify-write sequence).\n // (c) Use `FAILED_PRECONDITION` if the client should not retry until\n // the system state has been explicitly fixed. E.g., if a \"rmdir\"\n // fails because the directory is non-empty, `FAILED_PRECONDITION`\n // should be returned since the client should not retry unless\n // the files are deleted from the directory.\n //\n // HTTP Mapping: 400 Bad Request\n FAILED_PRECONDITION: 400,\n\n // The operation was aborted, typically due to a concurrency issue such as\n // a sequencer check failure or transaction abort.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 409 Conflict\n ABORTED: 409,\n\n // The operation was attempted past the valid range. E.g., seeking or\n // reading past end-of-file.\n //\n // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may\n // be fixed if the system state changes. For example, a 32-bit file\n // system will generate `INVALID_ARGUMENT` if asked to read at an\n // offset that is not in the range [0,2^32-1], but it will generate\n // `OUT_OF_RANGE` if asked to read from an offset past the current\n // file size.\n //\n // There is a fair bit of overlap between `FAILED_PRECONDITION` and\n // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific\n // error) when it applies so that callers who are iterating through\n // a space can easily look for an `OUT_OF_RANGE` error to detect when\n // they are done.\n //\n // HTTP Mapping: 400 Bad Request\n OUT_OF_RANGE: 400,\n\n // The operation is not implemented or is not supported/enabled in this\n // service.\n //\n // HTTP Mapping: 501 Not Implemented\n UNIMPLEMENTED: 501,\n\n // Internal errors. This means that some invariants expected by the\n // underlying system have been broken. This error code is reserved\n // for serious errors.\n //\n // HTTP Mapping: 500 Internal Server Error\n INTERNAL: 500,\n\n // The service is currently unavailable. This is most likely a\n // transient condition, which can be corrected by retrying with\n // a backoff. Note that it is not always safe to retry\n // non-idempotent operations.\n //\n // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n // `ABORTED`, and `UNAVAILABLE`.\n //\n // HTTP Mapping: 503 Service Unavailable\n UNAVAILABLE: 503,\n\n // Unrecoverable data loss or corruption.\n //\n // HTTP Mapping: 500 Internal Server Error\n DATA_LOSS: 500,\n\n // The request does not have valid authentication credentials for the\n // operation.\n //\n // HTTP Mapping: 401 Unauthorized\n UNAUTHENTICATED: 401,\n\n // The request method is not supported by the server and cannot be handled.\n //\n // HTTP Mapping: 405 Method Not Allowed\n METHOD_NOT_ALLOWED: 405,\n} as const;\n\nexport const DEFAULT_MESSAGES: Record<keyof typeof Code, string> = {\n OK: 'OK',\n CANCELLED: 'The operation was cancelled',\n UNKNOWN: 'Unknown error',\n INVALID_ARGUMENT: 'The client specified an invalid argument',\n DEADLINE_EXCEEDED: 'The deadline expired before the operation could complete',\n NOT_FOUND: 'Some requested entity was not found',\n ALREADY_EXISTS: 'The entity that a client attempted to create already exists',\n PERMISSION_DENIED: 'The caller does not have permission to execute the specified operation',\n RESOURCE_EXHAUSTED: 'Some resource has been exhausted',\n FAILED_PRECONDITION:\n \"The operation was rejected because the system is not in a state required for the operation's execution\",\n ABORTED: 'The operation was aborted',\n OUT_OF_RANGE: 'The operation was attempted past the valid range',\n UNIMPLEMENTED: 'The operation is not implemented or is not supported/enabled in this service',\n INTERNAL: 'Internal errors',\n UNAVAILABLE: 'The service is currently unavailable',\n DATA_LOSS: 'Unrecoverable data loss or corruption',\n UNAUTHENTICATED: 'The request does not have valid authentication credentials for the operation',\n METHOD_NOT_ALLOWED: 'The request method is not supported by the server and cannot be handled',\n};\n\nexport interface ErrorBody {\n error: {\n code: number;\n status: keyof typeof Code;\n message: string;\n details: Detail[];\n };\n}\n\nexport class StatusError extends Error {\n readonly status: number;\n readonly body?: ErrorBody;\n\n constructor(status: number, body?: ErrorBody) {\n super(body?.error.message ?? `Status Error: ${status}`);\n this.name = 'StatusError';\n this.status = status;\n this.body = body;\n Object.setPrototypeOf(this, StatusError.prototype);\n }\n}\n\nexport class StatusCode {\n code: keyof typeof Code;\n message?: string;\n private constructor(code: keyof typeof Code, message?: string) {\n this.code = code;\n this.message = message;\n }\n\n static of(code: keyof typeof Code, message?: string) {\n return new StatusCode(code, message ?? DEFAULT_MESSAGES[code]);\n }\n\n body(details?: Details): ErrorBody {\n return {\n error: {\n code: Code[this.code],\n status: this.code,\n message: this.message ?? '',\n details: details?.list ?? [],\n },\n };\n }\n\n error(details?: Details): Error {\n const body = this.body(details);\n if (Status.adapter) return Status.adapter(Code[this.code], body);\n return new StatusError(Code[this.code], body);\n }\n\n response(details?: Details): Response {\n const body = this.body(details);\n return Response.json(body, { status: body.error.code });\n }\n}\n\nexport class Status {\n static adapter?: (status: number, response: ErrorBody) => Error;\n\n static ok = (message?: string) => StatusCode.of('OK', message);\n static cancelled = (message?: string) => StatusCode.of('CANCELLED', message);\n static unknown = (message?: string) => StatusCode.of('UNKNOWN', message);\n static invalidArgument = (message?: string) => StatusCode.of('INVALID_ARGUMENT', message);\n static deadlineExceeded = (message?: string) => StatusCode.of('DEADLINE_EXCEEDED', message);\n static notFound = (message?: string) => StatusCode.of('NOT_FOUND', message);\n static alreadyExists = (message?: string) => StatusCode.of('ALREADY_EXISTS', message);\n static permissionDenied = (message?: string) => StatusCode.of('PERMISSION_DENIED', message);\n static unauthorized = (message?: string) => StatusCode.of('UNAUTHENTICATED', message);\n static resourceExhausted = (message?: string) => StatusCode.of('RESOURCE_EXHAUSTED', message);\n static failedPrecondition = (message?: string) => StatusCode.of('FAILED_PRECONDITION', message);\n static aborted = (message?: string) => StatusCode.of('ABORTED', message);\n static outOfRange = (message?: string) => StatusCode.of('OUT_OF_RANGE', message);\n static unimplemented = (message?: string) => StatusCode.of('UNIMPLEMENTED', message);\n static internal = (message?: string) => StatusCode.of('INTERNAL', message);\n static unavailable = (message?: string) => StatusCode.of('UNAVAILABLE', message);\n static dataLoss = (message?: string) => StatusCode.of('DATA_LOSS', message);\n static methodNotAllowed = (message?: string) => StatusCode.of('METHOD_NOT_ALLOWED', message);\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ let script = null;
4
4
  function loadScript() {
5
5
  return new Promise((resolve, reject) => {
6
6
  if (script) {
7
- if (window.google?.accounts?.id) resolve();
7
+ if (window.google?.accounts.id) resolve();
8
8
  else {
9
9
  script.addEventListener("load", () => resolve());
10
10
  script.addEventListener("error", () => {
@@ -33,7 +33,7 @@ function isFedcmSupported() {
33
33
  }
34
34
  /** debug: chrome://settings/content/federatedIdentityApi */
35
35
  async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_prompt = true, cancel_on_tap_outside = false }) {
36
- if (use_fedcm_for_prompt !== false && !isFedcmSupported()) return {
36
+ if (use_fedcm_for_prompt && !isFedcmSupported()) return {
37
37
  authorized: false,
38
38
  unsupported: true,
39
39
  reason: "fedcm_unsupported"
@@ -47,6 +47,12 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
47
47
  reason: "script_load_failed"
48
48
  };
49
49
  }
50
+ const accounts = window.google?.accounts;
51
+ if (!accounts) return {
52
+ authorized: false,
53
+ unsupported: true,
54
+ reason: "script_load_failed"
55
+ };
50
56
  return new Promise((resolve) => {
51
57
  let settled = false;
52
58
  const settle = (result) => {
@@ -55,7 +61,7 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
55
61
  resolve(result);
56
62
  };
57
63
  try {
58
- window.google.accounts.id.initialize({
64
+ accounts.id.initialize({
59
65
  ux_mode: "popup",
60
66
  context: "signin",
61
67
  auto_select,
@@ -72,7 +78,7 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
72
78
  credential
73
79
  })
74
80
  });
75
- window.google.accounts.id.prompt((notification) => {
81
+ accounts.id.prompt((notification) => {
76
82
  if (settled) return;
77
83
  const skipped = notification.isSkippedMoment();
78
84
  const dismissed = notification.isDismissedMoment();
@@ -106,6 +112,15 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
106
112
  */
107
113
  async function requestCode({ client_id, scope = "openid email profile", ux_mode = "popup", redirect_uri, state, login_hint, hd }) {
108
114
  await loadScript();
115
+ const accounts = window.google?.accounts;
116
+ if (!accounts) {
117
+ const error = /* @__PURE__ */ new Error("Google Identity Services API is unavailable");
118
+ error.type = "unknown";
119
+ return {
120
+ ok: false,
121
+ error
122
+ };
123
+ }
109
124
  return new Promise((resolve) => {
110
125
  let settled = false;
111
126
  const settle = (result) => {
@@ -113,7 +128,7 @@ async function requestCode({ client_id, scope = "openid email profile", ux_mode
113
128
  settled = true;
114
129
  resolve(result);
115
130
  };
116
- window.google.accounts.oauth2.initCodeClient({
131
+ accounts.oauth2.initCodeClient({
117
132
  client_id,
118
133
  scope,
119
134
  ux_mode,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/google-one-tap/index.ts"],"sourcesContent":["import type {\n ClientConfigError,\n CodeResponse,\n CredentialResponse,\n GoogleAccounts,\n PromptMomentNotification,\n} from './types';\n\ndeclare global {\n interface Window {\n google: {\n accounts: GoogleAccounts;\n };\n }\n}\n\nexport type Props = {\n /**\n * Opaque nonce string forwarded as-is to Google. Google echoes it back in\n * the ID Token's `nonce` claim per the OIDC spec; the caller is responsible\n * for any hashing/encoding required by its verifier.\n */\n nonce?: string;\n client_id: string;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n use_fedcm_for_prompt?: boolean;\n};\n\nexport type PromptMoment = {\n skipped: boolean;\n dismissed: boolean;\n dismissedReason: ReturnType<PromptMomentNotification['getDismissedReason']>;\n};\n\n/**\n * Why prompt() could not run One Tap, so the caller can decide to fall back\n * to requestCode().\n * - `fedcm_unsupported`: browser has no FedCM (Safari/Firefox/pre-117 Chrome).\n * - `script_load_failed`: the GSI script failed to load (network/CSP).\n * - `prompt_error`: GIS threw synchronously while initializing/prompting.\n */\nexport type UnsupportedReason = 'fedcm_unsupported' | 'script_load_failed' | 'prompt_error';\n\nexport type PromptResult =\n | { authorized: true; credential: CredentialResponse }\n | { authorized: false; moment: PromptMoment }\n | { authorized: false; unsupported: true; reason: UnsupportedReason };\n\nlet script: HTMLScriptElement | null = null;\n\nfunction loadScript(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (script) {\n if (window.google?.accounts?.id) {\n resolve();\n } else {\n script.addEventListener('load', () => resolve());\n script.addEventListener('error', () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n });\n }\n return;\n }\n\n script = document.createElement('script');\n script.id = 'google-one-tap';\n script.async = true;\n script.defer = true;\n script.src = 'https://accounts.google.com/gsi/client';\n script.onload = () => resolve();\n script.onerror = () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n };\n\n document.head.appendChild(script);\n });\n}\n\n/** FedCM feature detection — `IdentityCredential` is absent on Safari, Firefox and pre-117 Chrome. */\nfunction isFedcmSupported(): boolean {\n return typeof window !== 'undefined' && 'IdentityCredential' in window;\n}\n\n/** debug: chrome://settings/content/federatedIdentityApi */\nexport async function prompt({\n nonce,\n client_id,\n auto_select = false,\n use_fedcm_for_prompt = true,\n cancel_on_tap_outside = false,\n}: Props): Promise<PromptResult> {\n // FedCM is mandatory for One Tap on capable browsers (GIS completed the\n // migration in early 2025) and there is no viable legacy fallback anymore.\n // When the caller wants FedCM but the browser lacks it, bail early instead\n // of letting GIS throw an async `NotSupportedError: Missing request type`\n // from navigator.credentials.get. Callers should fall back to requestCode().\n if (use_fedcm_for_prompt !== false && !isFedcmSupported()) {\n return { authorized: false, unsupported: true, reason: 'fedcm_unsupported' };\n }\n\n try {\n await loadScript();\n } catch {\n return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n }\n\n return new Promise<PromptResult>((resolve) => {\n let settled = false;\n const settle = (result: PromptResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n try {\n window.google.accounts.id.initialize({\n ux_mode: 'popup',\n context: 'signin',\n auto_select,\n nonce,\n client_id,\n use_fedcm_for_prompt,\n cancel_on_tap_outside,\n callback: (credential) => settle({ authorized: true, credential }),\n native_callback: (credential) => settle({ authorized: true, credential }),\n });\n\n window.google.accounts.id.prompt((notification) => {\n if (settled) return;\n\n const skipped = notification.isSkippedMoment();\n const dismissed = notification.isDismissedMoment();\n const dismissedReason = notification.getDismissedReason();\n\n if (skipped || (dismissed && dismissedReason !== 'credential_returned')) {\n settle({\n authorized: false,\n moment: { skipped, dismissed, dismissedReason },\n });\n }\n });\n } catch {\n settle({ authorized: false, unsupported: true, reason: 'prompt_error' });\n }\n });\n}\n\nexport type CodeProps = {\n client_id: string;\n /**\n * Space-delimited OAuth scopes. Defaults to OpenID Connect scopes so the\n * backend can exchange the code for an ID token, mirroring One Tap's intent.\n */\n scope?: string;\n /** 'popup' resolves the returned promise; 'redirect' navigates away and never resolves. */\n ux_mode?: 'popup' | 'redirect';\n /** Required when ux_mode is 'redirect'; for 'popup' Google uses postMessage. */\n redirect_uri?: string;\n state?: string;\n login_hint?: string;\n hd?: string;\n};\n\nexport type CodeResult =\n | { ok: true; response: CodeResponse }\n | { ok: false; error: ClientConfigError | CodeResponse };\n\n/**\n * OAuth 2.0 authorization-code fallback for environments where One Tap / FedCM\n * is unavailable (i.e. prompt() resolved with `unsupported: true`). It reuses\n * the same GSI script, so there is no extra dependency.\n *\n * Unlike One Tap it returns an authorization `code` for the backend to\n * exchange, not an ID token, and it must be triggered by a user gesture or the\n * popup may be blocked.\n */\nexport async function requestCode({\n client_id,\n scope = 'openid email profile',\n ux_mode = 'popup',\n redirect_uri,\n state,\n login_hint,\n hd,\n}: CodeProps): Promise<CodeResult> {\n await loadScript();\n\n return new Promise<CodeResult>((resolve) => {\n let settled = false;\n const settle = (result: CodeResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n const client = window.google.accounts.oauth2.initCodeClient({\n client_id,\n scope,\n ux_mode,\n redirect_uri,\n state,\n login_hint,\n hd,\n callback: (response) =>\n settle(response.code ? { ok: true, response } : { ok: false, error: response }),\n error_callback: (error) => settle({ ok: false, error }),\n });\n\n client.requestCode();\n });\n}\n"],"mappings":";;AAiDA,IAAI,SAAmC;AAEvC,SAAS,aAA4B;CACnC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ;GACV,IAAI,OAAO,QAAQ,UAAU,IAC3B,QAAQ;QACH;IACL,OAAO,iBAAiB,cAAc,QAAQ,CAAC;IAC/C,OAAO,iBAAiB,eAAe;KACrC,SAAS;KACT,uBAAO,IAAI,MAAM,sCAAsC,CAAC;IAC1D,CAAC;GACH;GACA;EACF;EAEA,SAAS,SAAS,cAAc,QAAQ;EACxC,OAAO,KAAK;EACZ,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,OAAO,MAAM;EACb,OAAO,eAAe,QAAQ;EAC9B,OAAO,gBAAgB;GACrB,SAAS;GACT,uBAAO,IAAI,MAAM,sCAAsC,CAAC;EAC1D;EAEA,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;AACH;;AAGA,SAAS,mBAA4B;CACnC,OAAO,OAAO,WAAW,eAAe,wBAAwB;AAClE;;AAGA,eAAsB,OAAO,EAC3B,OACA,WACA,cAAc,OACd,uBAAuB,MACvB,wBAAwB,SACO;CAM/B,IAAI,yBAAyB,SAAS,CAAC,iBAAiB,GACtD,OAAO;EAAE,YAAY;EAAO,aAAa;EAAM,QAAQ;CAAoB;CAG7E,IAAI;EACF,MAAM,WAAW;CACnB,QAAQ;EACN,OAAO;GAAE,YAAY;GAAO,aAAa;GAAM,QAAQ;EAAqB;CAC9E;CAEA,OAAO,IAAI,SAAuB,YAAY;EAC5C,IAAI,UAAU;EACd,MAAM,UAAU,WAAyB;GACvC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,MAAM;EAChB;EAEA,IAAI;GACF,OAAO,OAAO,SAAS,GAAG,WAAW;IACnC,SAAS;IACT,SAAS;IACT;IACA;IACA;IACA;IACA;IACA,WAAW,eAAe,OAAO;KAAE,YAAY;KAAM;IAAW,CAAC;IACjE,kBAAkB,eAAe,OAAO;KAAE,YAAY;KAAM;IAAW,CAAC;GAC1E,CAAC;GAED,OAAO,OAAO,SAAS,GAAG,QAAQ,iBAAiB;IACjD,IAAI,SAAS;IAEb,MAAM,UAAU,aAAa,gBAAgB;IAC7C,MAAM,YAAY,aAAa,kBAAkB;IACjD,MAAM,kBAAkB,aAAa,mBAAmB;IAExD,IAAI,WAAY,aAAa,oBAAoB,uBAC/C,OAAO;KACL,YAAY;KACZ,QAAQ;MAAE;MAAS;MAAW;KAAgB;IAChD,CAAC;GAEL,CAAC;EACH,QAAQ;GACN,OAAO;IAAE,YAAY;IAAO,aAAa;IAAM,QAAQ;GAAe,CAAC;EACzE;CACF,CAAC;AACH;;;;;;;;;;AA+BA,eAAsB,YAAY,EAChC,WACA,QAAQ,wBACR,UAAU,SACV,cACA,OACA,YACA,MACiC;CACjC,MAAM,WAAW;CAEjB,OAAO,IAAI,SAAqB,YAAY;EAC1C,IAAI,UAAU;EACd,MAAM,UAAU,WAAuB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,MAAM;EAChB;EAeA,OAbsB,OAAO,SAAS,OAAO,eAAe;GAC1D;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,aACT,OAAO,SAAS,OAAO;IAAE,IAAI;IAAM;GAAS,IAAI;IAAE,IAAI;IAAO,OAAO;GAAS,CAAC;GAChF,iBAAiB,UAAU,OAAO;IAAE,IAAI;IAAO;GAAM,CAAC;EACxD,CAEK,CAAC,CAAC,YAAY;CACrB,CAAC;AACH"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/google-one-tap/index.ts"],"sourcesContent":["import type {\n ClientConfigError,\n CodeResponse,\n CredentialResponse,\n GoogleAccounts,\n PromptMomentNotification,\n} from './types';\n\ndeclare global {\n interface Window {\n /** Set by the external GSI script once it loads — absent until then. */\n google?: {\n accounts: GoogleAccounts;\n };\n }\n}\n\nexport type Props = {\n /**\n * Opaque nonce string forwarded as-is to Google. Google echoes it back in\n * the ID Token's `nonce` claim per the OIDC spec; the caller is responsible\n * for any hashing/encoding required by its verifier.\n */\n nonce?: string;\n client_id: string;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n use_fedcm_for_prompt?: boolean;\n};\n\nexport type PromptMoment = {\n skipped: boolean;\n dismissed: boolean;\n dismissedReason: ReturnType<PromptMomentNotification['getDismissedReason']>;\n};\n\n/**\n * Why prompt() could not run One Tap, so the caller can decide to fall back\n * to requestCode().\n * - `fedcm_unsupported`: browser has no FedCM (Safari/Firefox/pre-117 Chrome).\n * - `script_load_failed`: the GSI script failed to load (network/CSP).\n * - `prompt_error`: GIS threw synchronously while initializing/prompting.\n */\nexport type UnsupportedReason = 'fedcm_unsupported' | 'script_load_failed' | 'prompt_error';\n\nexport type PromptResult =\n | { authorized: true; credential: CredentialResponse }\n | { authorized: false; moment: PromptMoment }\n | { authorized: false; unsupported: true; reason: UnsupportedReason };\n\nlet script: HTMLScriptElement | null = null;\n\nfunction loadScript(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (script) {\n if (window.google?.accounts.id) {\n resolve();\n } else {\n script.addEventListener('load', () => resolve());\n script.addEventListener('error', () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n });\n }\n return;\n }\n\n script = document.createElement('script');\n script.id = 'google-one-tap';\n script.async = true;\n script.defer = true;\n script.src = 'https://accounts.google.com/gsi/client';\n script.onload = () => resolve();\n script.onerror = () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n };\n\n document.head.appendChild(script);\n });\n}\n\n/** FedCM feature detection — `IdentityCredential` is absent on Safari, Firefox and pre-117 Chrome. */\nfunction isFedcmSupported(): boolean {\n return typeof window !== 'undefined' && 'IdentityCredential' in window;\n}\n\n/** debug: chrome://settings/content/federatedIdentityApi */\nexport async function prompt({\n nonce,\n client_id,\n auto_select = false,\n use_fedcm_for_prompt = true,\n cancel_on_tap_outside = false,\n}: Props): Promise<PromptResult> {\n // FedCM is mandatory for One Tap on capable browsers (GIS completed the\n // migration in early 2025) and there is no viable legacy fallback anymore.\n // When the caller wants FedCM but the browser lacks it, bail early instead\n // of letting GIS throw an async `NotSupportedError: Missing request type`\n // from navigator.credentials.get. Callers should fall back to requestCode().\n if (use_fedcm_for_prompt && !isFedcmSupported()) {\n return { authorized: false, unsupported: true, reason: 'fedcm_unsupported' };\n }\n\n try {\n await loadScript();\n } catch {\n return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n }\n\n const accounts = window.google?.accounts;\n if (!accounts) {\n // The script loaded but did not expose the API — treat it as a load failure.\n return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n }\n\n return new Promise<PromptResult>((resolve) => {\n let settled = false;\n const settle = (result: PromptResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n try {\n accounts.id.initialize({\n ux_mode: 'popup',\n context: 'signin',\n auto_select,\n nonce,\n client_id,\n use_fedcm_for_prompt,\n cancel_on_tap_outside,\n callback: (credential) => settle({ authorized: true, credential }),\n native_callback: (credential) => settle({ authorized: true, credential }),\n });\n\n accounts.id.prompt((notification) => {\n if (settled) return;\n\n const skipped = notification.isSkippedMoment();\n const dismissed = notification.isDismissedMoment();\n const dismissedReason = notification.getDismissedReason();\n\n if (skipped || (dismissed && dismissedReason !== 'credential_returned')) {\n settle({\n authorized: false,\n moment: { skipped, dismissed, dismissedReason },\n });\n }\n });\n } catch {\n settle({ authorized: false, unsupported: true, reason: 'prompt_error' });\n }\n });\n}\n\nexport type CodeProps = {\n client_id: string;\n /**\n * Space-delimited OAuth scopes. Defaults to OpenID Connect scopes so the\n * backend can exchange the code for an ID token, mirroring One Tap's intent.\n */\n scope?: string;\n /** 'popup' resolves the returned promise; 'redirect' navigates away and never resolves. */\n ux_mode?: 'popup' | 'redirect';\n /** Required when ux_mode is 'redirect'; for 'popup' Google uses postMessage. */\n redirect_uri?: string;\n state?: string;\n login_hint?: string;\n hd?: string;\n};\n\nexport type CodeResult =\n | { ok: true; response: CodeResponse }\n | { ok: false; error: ClientConfigError | CodeResponse };\n\n/**\n * OAuth 2.0 authorization-code fallback for environments where One Tap / FedCM\n * is unavailable (i.e. prompt() resolved with `unsupported: true`). It reuses\n * the same GSI script, so there is no extra dependency.\n *\n * Unlike One Tap it returns an authorization `code` for the backend to\n * exchange, not an ID token, and it must be triggered by a user gesture or the\n * popup may be blocked.\n */\nexport async function requestCode({\n client_id,\n scope = 'openid email profile',\n ux_mode = 'popup',\n redirect_uri,\n state,\n login_hint,\n hd,\n}: CodeProps): Promise<CodeResult> {\n await loadScript();\n\n const accounts = window.google?.accounts;\n if (!accounts) {\n // The script loaded but did not expose the API — surface a config error.\n const error = new Error('Google Identity Services API is unavailable') as ClientConfigError;\n error.type = 'unknown';\n return { ok: false, error };\n }\n\n return new Promise<CodeResult>((resolve) => {\n let settled = false;\n const settle = (result: CodeResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n const client = accounts.oauth2.initCodeClient({\n client_id,\n scope,\n ux_mode,\n redirect_uri,\n state,\n login_hint,\n hd,\n callback: (response) =>\n settle(response.code ? { ok: true, response } : { ok: false, error: response }),\n error_callback: (error) => settle({ ok: false, error }),\n });\n\n client.requestCode();\n });\n}\n"],"mappings":"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"}
@@ -2,7 +2,8 @@ import { ClientConfigError, CodeResponse, CredentialResponse, GoogleAccounts, Pr
2
2
  //#region src/google-one-tap/index.d.ts
3
3
  declare global {
4
4
  interface Window {
5
- google: {
5
+ /** Set by the external GSI script once it loads — absent until then. */
6
+ google?: {
6
7
  accounts: GoogleAccounts;
7
8
  };
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/google-one-tap/index.ts"],"mappings":";;QAQQ;YACI;IACR;MACE,UAAU;;;;KAKJ;;;;;;EAMV;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA,iBAAiB,WAAW;;;;;;;;;KAUlB;KAEA;EACN;EAAkB,YAAY;;EAC9B;EAAmB,QAAQ;;EAC3B;EAAmB;EAAmB,QAAQ;;;iBAwC9B,SACpB,OACA,WACA,aACA,sBACA,yBACC,QAAQ,QAAQ;KAyDP;EACV;;;;;EAKA;;EAEA;;EAEA;EACA;EACA;EACA;;KAGU;EACN;EAAU,UAAU;;EACpB;EAAW,OAAO,oBAAoB;;;;;;;;;;;iBAWtB,cACpB,WACA,OACA,SACA,cACA,OACA,YACA,MACC,YAAY,QAAQ"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/google-one-tap/index.ts"],"mappings":";;QAQQ;YACI;;IAER;MACE,UAAU;;;;KAKJ;;;;;;EAMV;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA,iBAAiB,WAAW;;;;;;;;;KAUlB;KAEA;EACN;EAAkB,YAAY;;EAC9B;EAAmB,QAAQ;;EAC3B;EAAmB;EAAmB,QAAQ;;;iBAwC9B,SACpB,OACA,WACA,aACA,sBACA,yBACC,QAAQ,QAAQ;KA+DP;EACV;;;;;EAKA;;EAEA;;EAEA;EACA;EACA;EACA;;KAGU;EACN;EAAU,UAAU;;EACpB;EAAW,OAAO,oBAAoB;;;;;;;;;;;iBAWtB,cACpB,WACA,OACA,SACA,cACA,OACA,YACA,MACC,YAAY,QAAQ"}
@@ -2,7 +2,8 @@ import { ClientConfigError, CodeResponse, CredentialResponse, GoogleAccounts, Pr
2
2
  //#region src/google-one-tap/index.d.ts
3
3
  declare global {
4
4
  interface Window {
5
- google: {
5
+ /** Set by the external GSI script once it loads — absent until then. */
6
+ google?: {
6
7
  accounts: GoogleAccounts;
7
8
  };
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/google-one-tap/index.ts"],"mappings":";;QAQQ;YACI;IACR;MACE,UAAU;;;;KAKJ;;;;;;EAMV;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA,iBAAiB,WAAW;;;;;;;;;KAUlB;KAEA;EACN;EAAkB,YAAY;;EAC9B;EAAmB,QAAQ;;EAC3B;EAAmB;EAAmB,QAAQ;;;iBAwC9B,SACpB,OACA,WACA,aACA,sBACA,yBACC,QAAQ,QAAQ;KAyDP;EACV;;;;;EAKA;;EAEA;;EAEA;EACA;EACA;EACA;;KAGU;EACN;EAAU,UAAU;;EACpB;EAAW,OAAO,oBAAoB;;;;;;;;;;;iBAWtB,cACpB,WACA,OACA,SACA,cACA,OACA,YACA,MACC,YAAY,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/google-one-tap/index.ts"],"mappings":";;QAQQ;YACI;;IAER;MACE,UAAU;;;;KAKJ;;;;;;EAMV;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA,iBAAiB,WAAW;;;;;;;;;KAUlB;KAEA;EACN;EAAkB,YAAY;;EAC9B;EAAmB,QAAQ;;EAC3B;EAAmB;EAAmB,QAAQ;;;iBAwC9B,SACpB,OACA,WACA,aACA,sBACA,yBACC,QAAQ,QAAQ;KA+DP;EACV;;;;;EAKA;;EAEA;;EAEA;EACA;EACA;EACA;;KAGU;EACN;EAAU,UAAU;;EACpB;EAAW,OAAO,oBAAoB;;;;;;;;;;;iBAWtB,cACpB,WACA,OACA,SACA,cACA,OACA,YACA,MACC,YAAY,QAAQ"}
@@ -3,7 +3,7 @@ let script = null;
3
3
  function loadScript() {
4
4
  return new Promise((resolve, reject) => {
5
5
  if (script) {
6
- if (window.google?.accounts?.id) resolve();
6
+ if (window.google?.accounts.id) resolve();
7
7
  else {
8
8
  script.addEventListener("load", () => resolve());
9
9
  script.addEventListener("error", () => {
@@ -32,7 +32,7 @@ function isFedcmSupported() {
32
32
  }
33
33
  /** debug: chrome://settings/content/federatedIdentityApi */
34
34
  async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_prompt = true, cancel_on_tap_outside = false }) {
35
- if (use_fedcm_for_prompt !== false && !isFedcmSupported()) return {
35
+ if (use_fedcm_for_prompt && !isFedcmSupported()) return {
36
36
  authorized: false,
37
37
  unsupported: true,
38
38
  reason: "fedcm_unsupported"
@@ -46,6 +46,12 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
46
46
  reason: "script_load_failed"
47
47
  };
48
48
  }
49
+ const accounts = window.google?.accounts;
50
+ if (!accounts) return {
51
+ authorized: false,
52
+ unsupported: true,
53
+ reason: "script_load_failed"
54
+ };
49
55
  return new Promise((resolve) => {
50
56
  let settled = false;
51
57
  const settle = (result) => {
@@ -54,7 +60,7 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
54
60
  resolve(result);
55
61
  };
56
62
  try {
57
- window.google.accounts.id.initialize({
63
+ accounts.id.initialize({
58
64
  ux_mode: "popup",
59
65
  context: "signin",
60
66
  auto_select,
@@ -71,7 +77,7 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
71
77
  credential
72
78
  })
73
79
  });
74
- window.google.accounts.id.prompt((notification) => {
80
+ accounts.id.prompt((notification) => {
75
81
  if (settled) return;
76
82
  const skipped = notification.isSkippedMoment();
77
83
  const dismissed = notification.isDismissedMoment();
@@ -105,6 +111,15 @@ async function prompt({ nonce, client_id, auto_select = false, use_fedcm_for_pro
105
111
  */
106
112
  async function requestCode({ client_id, scope = "openid email profile", ux_mode = "popup", redirect_uri, state, login_hint, hd }) {
107
113
  await loadScript();
114
+ const accounts = window.google?.accounts;
115
+ if (!accounts) {
116
+ const error = /* @__PURE__ */ new Error("Google Identity Services API is unavailable");
117
+ error.type = "unknown";
118
+ return {
119
+ ok: false,
120
+ error
121
+ };
122
+ }
108
123
  return new Promise((resolve) => {
109
124
  let settled = false;
110
125
  const settle = (result) => {
@@ -112,7 +127,7 @@ async function requestCode({ client_id, scope = "openid email profile", ux_mode
112
127
  settled = true;
113
128
  resolve(result);
114
129
  };
115
- window.google.accounts.oauth2.initCodeClient({
130
+ accounts.oauth2.initCodeClient({
116
131
  client_id,
117
132
  scope,
118
133
  ux_mode,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/google-one-tap/index.ts"],"sourcesContent":["import type {\n ClientConfigError,\n CodeResponse,\n CredentialResponse,\n GoogleAccounts,\n PromptMomentNotification,\n} from './types';\n\ndeclare global {\n interface Window {\n google: {\n accounts: GoogleAccounts;\n };\n }\n}\n\nexport type Props = {\n /**\n * Opaque nonce string forwarded as-is to Google. Google echoes it back in\n * the ID Token's `nonce` claim per the OIDC spec; the caller is responsible\n * for any hashing/encoding required by its verifier.\n */\n nonce?: string;\n client_id: string;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n use_fedcm_for_prompt?: boolean;\n};\n\nexport type PromptMoment = {\n skipped: boolean;\n dismissed: boolean;\n dismissedReason: ReturnType<PromptMomentNotification['getDismissedReason']>;\n};\n\n/**\n * Why prompt() could not run One Tap, so the caller can decide to fall back\n * to requestCode().\n * - `fedcm_unsupported`: browser has no FedCM (Safari/Firefox/pre-117 Chrome).\n * - `script_load_failed`: the GSI script failed to load (network/CSP).\n * - `prompt_error`: GIS threw synchronously while initializing/prompting.\n */\nexport type UnsupportedReason = 'fedcm_unsupported' | 'script_load_failed' | 'prompt_error';\n\nexport type PromptResult =\n | { authorized: true; credential: CredentialResponse }\n | { authorized: false; moment: PromptMoment }\n | { authorized: false; unsupported: true; reason: UnsupportedReason };\n\nlet script: HTMLScriptElement | null = null;\n\nfunction loadScript(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (script) {\n if (window.google?.accounts?.id) {\n resolve();\n } else {\n script.addEventListener('load', () => resolve());\n script.addEventListener('error', () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n });\n }\n return;\n }\n\n script = document.createElement('script');\n script.id = 'google-one-tap';\n script.async = true;\n script.defer = true;\n script.src = 'https://accounts.google.com/gsi/client';\n script.onload = () => resolve();\n script.onerror = () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n };\n\n document.head.appendChild(script);\n });\n}\n\n/** FedCM feature detection — `IdentityCredential` is absent on Safari, Firefox and pre-117 Chrome. */\nfunction isFedcmSupported(): boolean {\n return typeof window !== 'undefined' && 'IdentityCredential' in window;\n}\n\n/** debug: chrome://settings/content/federatedIdentityApi */\nexport async function prompt({\n nonce,\n client_id,\n auto_select = false,\n use_fedcm_for_prompt = true,\n cancel_on_tap_outside = false,\n}: Props): Promise<PromptResult> {\n // FedCM is mandatory for One Tap on capable browsers (GIS completed the\n // migration in early 2025) and there is no viable legacy fallback anymore.\n // When the caller wants FedCM but the browser lacks it, bail early instead\n // of letting GIS throw an async `NotSupportedError: Missing request type`\n // from navigator.credentials.get. Callers should fall back to requestCode().\n if (use_fedcm_for_prompt !== false && !isFedcmSupported()) {\n return { authorized: false, unsupported: true, reason: 'fedcm_unsupported' };\n }\n\n try {\n await loadScript();\n } catch {\n return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n }\n\n return new Promise<PromptResult>((resolve) => {\n let settled = false;\n const settle = (result: PromptResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n try {\n window.google.accounts.id.initialize({\n ux_mode: 'popup',\n context: 'signin',\n auto_select,\n nonce,\n client_id,\n use_fedcm_for_prompt,\n cancel_on_tap_outside,\n callback: (credential) => settle({ authorized: true, credential }),\n native_callback: (credential) => settle({ authorized: true, credential }),\n });\n\n window.google.accounts.id.prompt((notification) => {\n if (settled) return;\n\n const skipped = notification.isSkippedMoment();\n const dismissed = notification.isDismissedMoment();\n const dismissedReason = notification.getDismissedReason();\n\n if (skipped || (dismissed && dismissedReason !== 'credential_returned')) {\n settle({\n authorized: false,\n moment: { skipped, dismissed, dismissedReason },\n });\n }\n });\n } catch {\n settle({ authorized: false, unsupported: true, reason: 'prompt_error' });\n }\n });\n}\n\nexport type CodeProps = {\n client_id: string;\n /**\n * Space-delimited OAuth scopes. Defaults to OpenID Connect scopes so the\n * backend can exchange the code for an ID token, mirroring One Tap's intent.\n */\n scope?: string;\n /** 'popup' resolves the returned promise; 'redirect' navigates away and never resolves. */\n ux_mode?: 'popup' | 'redirect';\n /** Required when ux_mode is 'redirect'; for 'popup' Google uses postMessage. */\n redirect_uri?: string;\n state?: string;\n login_hint?: string;\n hd?: string;\n};\n\nexport type CodeResult =\n | { ok: true; response: CodeResponse }\n | { ok: false; error: ClientConfigError | CodeResponse };\n\n/**\n * OAuth 2.0 authorization-code fallback for environments where One Tap / FedCM\n * is unavailable (i.e. prompt() resolved with `unsupported: true`). It reuses\n * the same GSI script, so there is no extra dependency.\n *\n * Unlike One Tap it returns an authorization `code` for the backend to\n * exchange, not an ID token, and it must be triggered by a user gesture or the\n * popup may be blocked.\n */\nexport async function requestCode({\n client_id,\n scope = 'openid email profile',\n ux_mode = 'popup',\n redirect_uri,\n state,\n login_hint,\n hd,\n}: CodeProps): Promise<CodeResult> {\n await loadScript();\n\n return new Promise<CodeResult>((resolve) => {\n let settled = false;\n const settle = (result: CodeResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n const client = window.google.accounts.oauth2.initCodeClient({\n client_id,\n scope,\n ux_mode,\n redirect_uri,\n state,\n login_hint,\n hd,\n callback: (response) =>\n settle(response.code ? { ok: true, response } : { ok: false, error: response }),\n error_callback: (error) => settle({ ok: false, error }),\n });\n\n client.requestCode();\n });\n}\n"],"mappings":";AAiDA,IAAI,SAAmC;AAEvC,SAAS,aAA4B;CACnC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ;GACV,IAAI,OAAO,QAAQ,UAAU,IAC3B,QAAQ;QACH;IACL,OAAO,iBAAiB,cAAc,QAAQ,CAAC;IAC/C,OAAO,iBAAiB,eAAe;KACrC,SAAS;KACT,uBAAO,IAAI,MAAM,sCAAsC,CAAC;IAC1D,CAAC;GACH;GACA;EACF;EAEA,SAAS,SAAS,cAAc,QAAQ;EACxC,OAAO,KAAK;EACZ,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,OAAO,MAAM;EACb,OAAO,eAAe,QAAQ;EAC9B,OAAO,gBAAgB;GACrB,SAAS;GACT,uBAAO,IAAI,MAAM,sCAAsC,CAAC;EAC1D;EAEA,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;AACH;;AAGA,SAAS,mBAA4B;CACnC,OAAO,OAAO,WAAW,eAAe,wBAAwB;AAClE;;AAGA,eAAsB,OAAO,EAC3B,OACA,WACA,cAAc,OACd,uBAAuB,MACvB,wBAAwB,SACO;CAM/B,IAAI,yBAAyB,SAAS,CAAC,iBAAiB,GACtD,OAAO;EAAE,YAAY;EAAO,aAAa;EAAM,QAAQ;CAAoB;CAG7E,IAAI;EACF,MAAM,WAAW;CACnB,QAAQ;EACN,OAAO;GAAE,YAAY;GAAO,aAAa;GAAM,QAAQ;EAAqB;CAC9E;CAEA,OAAO,IAAI,SAAuB,YAAY;EAC5C,IAAI,UAAU;EACd,MAAM,UAAU,WAAyB;GACvC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,MAAM;EAChB;EAEA,IAAI;GACF,OAAO,OAAO,SAAS,GAAG,WAAW;IACnC,SAAS;IACT,SAAS;IACT;IACA;IACA;IACA;IACA;IACA,WAAW,eAAe,OAAO;KAAE,YAAY;KAAM;IAAW,CAAC;IACjE,kBAAkB,eAAe,OAAO;KAAE,YAAY;KAAM;IAAW,CAAC;GAC1E,CAAC;GAED,OAAO,OAAO,SAAS,GAAG,QAAQ,iBAAiB;IACjD,IAAI,SAAS;IAEb,MAAM,UAAU,aAAa,gBAAgB;IAC7C,MAAM,YAAY,aAAa,kBAAkB;IACjD,MAAM,kBAAkB,aAAa,mBAAmB;IAExD,IAAI,WAAY,aAAa,oBAAoB,uBAC/C,OAAO;KACL,YAAY;KACZ,QAAQ;MAAE;MAAS;MAAW;KAAgB;IAChD,CAAC;GAEL,CAAC;EACH,QAAQ;GACN,OAAO;IAAE,YAAY;IAAO,aAAa;IAAM,QAAQ;GAAe,CAAC;EACzE;CACF,CAAC;AACH;;;;;;;;;;AA+BA,eAAsB,YAAY,EAChC,WACA,QAAQ,wBACR,UAAU,SACV,cACA,OACA,YACA,MACiC;CACjC,MAAM,WAAW;CAEjB,OAAO,IAAI,SAAqB,YAAY;EAC1C,IAAI,UAAU;EACd,MAAM,UAAU,WAAuB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,MAAM;EAChB;EAeA,OAbsB,OAAO,SAAS,OAAO,eAAe;GAC1D;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,aACT,OAAO,SAAS,OAAO;IAAE,IAAI;IAAM;GAAS,IAAI;IAAE,IAAI;IAAO,OAAO;GAAS,CAAC;GAChF,iBAAiB,UAAU,OAAO;IAAE,IAAI;IAAO;GAAM,CAAC;EACxD,CAEK,CAAC,CAAC,YAAY;CACrB,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/google-one-tap/index.ts"],"sourcesContent":["import type {\n ClientConfigError,\n CodeResponse,\n CredentialResponse,\n GoogleAccounts,\n PromptMomentNotification,\n} from './types';\n\ndeclare global {\n interface Window {\n /** Set by the external GSI script once it loads — absent until then. */\n google?: {\n accounts: GoogleAccounts;\n };\n }\n}\n\nexport type Props = {\n /**\n * Opaque nonce string forwarded as-is to Google. Google echoes it back in\n * the ID Token's `nonce` claim per the OIDC spec; the caller is responsible\n * for any hashing/encoding required by its verifier.\n */\n nonce?: string;\n client_id: string;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n use_fedcm_for_prompt?: boolean;\n};\n\nexport type PromptMoment = {\n skipped: boolean;\n dismissed: boolean;\n dismissedReason: ReturnType<PromptMomentNotification['getDismissedReason']>;\n};\n\n/**\n * Why prompt() could not run One Tap, so the caller can decide to fall back\n * to requestCode().\n * - `fedcm_unsupported`: browser has no FedCM (Safari/Firefox/pre-117 Chrome).\n * - `script_load_failed`: the GSI script failed to load (network/CSP).\n * - `prompt_error`: GIS threw synchronously while initializing/prompting.\n */\nexport type UnsupportedReason = 'fedcm_unsupported' | 'script_load_failed' | 'prompt_error';\n\nexport type PromptResult =\n | { authorized: true; credential: CredentialResponse }\n | { authorized: false; moment: PromptMoment }\n | { authorized: false; unsupported: true; reason: UnsupportedReason };\n\nlet script: HTMLScriptElement | null = null;\n\nfunction loadScript(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (script) {\n if (window.google?.accounts.id) {\n resolve();\n } else {\n script.addEventListener('load', () => resolve());\n script.addEventListener('error', () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n });\n }\n return;\n }\n\n script = document.createElement('script');\n script.id = 'google-one-tap';\n script.async = true;\n script.defer = true;\n script.src = 'https://accounts.google.com/gsi/client';\n script.onload = () => resolve();\n script.onerror = () => {\n script = null; // allow a later call to recreate the element instead of hanging\n reject(new Error('Failed to load Google One Tap script'));\n };\n\n document.head.appendChild(script);\n });\n}\n\n/** FedCM feature detection — `IdentityCredential` is absent on Safari, Firefox and pre-117 Chrome. */\nfunction isFedcmSupported(): boolean {\n return typeof window !== 'undefined' && 'IdentityCredential' in window;\n}\n\n/** debug: chrome://settings/content/federatedIdentityApi */\nexport async function prompt({\n nonce,\n client_id,\n auto_select = false,\n use_fedcm_for_prompt = true,\n cancel_on_tap_outside = false,\n}: Props): Promise<PromptResult> {\n // FedCM is mandatory for One Tap on capable browsers (GIS completed the\n // migration in early 2025) and there is no viable legacy fallback anymore.\n // When the caller wants FedCM but the browser lacks it, bail early instead\n // of letting GIS throw an async `NotSupportedError: Missing request type`\n // from navigator.credentials.get. Callers should fall back to requestCode().\n if (use_fedcm_for_prompt && !isFedcmSupported()) {\n return { authorized: false, unsupported: true, reason: 'fedcm_unsupported' };\n }\n\n try {\n await loadScript();\n } catch {\n return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n }\n\n const accounts = window.google?.accounts;\n if (!accounts) {\n // The script loaded but did not expose the API — treat it as a load failure.\n return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n }\n\n return new Promise<PromptResult>((resolve) => {\n let settled = false;\n const settle = (result: PromptResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n try {\n accounts.id.initialize({\n ux_mode: 'popup',\n context: 'signin',\n auto_select,\n nonce,\n client_id,\n use_fedcm_for_prompt,\n cancel_on_tap_outside,\n callback: (credential) => settle({ authorized: true, credential }),\n native_callback: (credential) => settle({ authorized: true, credential }),\n });\n\n accounts.id.prompt((notification) => {\n if (settled) return;\n\n const skipped = notification.isSkippedMoment();\n const dismissed = notification.isDismissedMoment();\n const dismissedReason = notification.getDismissedReason();\n\n if (skipped || (dismissed && dismissedReason !== 'credential_returned')) {\n settle({\n authorized: false,\n moment: { skipped, dismissed, dismissedReason },\n });\n }\n });\n } catch {\n settle({ authorized: false, unsupported: true, reason: 'prompt_error' });\n }\n });\n}\n\nexport type CodeProps = {\n client_id: string;\n /**\n * Space-delimited OAuth scopes. Defaults to OpenID Connect scopes so the\n * backend can exchange the code for an ID token, mirroring One Tap's intent.\n */\n scope?: string;\n /** 'popup' resolves the returned promise; 'redirect' navigates away and never resolves. */\n ux_mode?: 'popup' | 'redirect';\n /** Required when ux_mode is 'redirect'; for 'popup' Google uses postMessage. */\n redirect_uri?: string;\n state?: string;\n login_hint?: string;\n hd?: string;\n};\n\nexport type CodeResult =\n | { ok: true; response: CodeResponse }\n | { ok: false; error: ClientConfigError | CodeResponse };\n\n/**\n * OAuth 2.0 authorization-code fallback for environments where One Tap / FedCM\n * is unavailable (i.e. prompt() resolved with `unsupported: true`). It reuses\n * the same GSI script, so there is no extra dependency.\n *\n * Unlike One Tap it returns an authorization `code` for the backend to\n * exchange, not an ID token, and it must be triggered by a user gesture or the\n * popup may be blocked.\n */\nexport async function requestCode({\n client_id,\n scope = 'openid email profile',\n ux_mode = 'popup',\n redirect_uri,\n state,\n login_hint,\n hd,\n}: CodeProps): Promise<CodeResult> {\n await loadScript();\n\n const accounts = window.google?.accounts;\n if (!accounts) {\n // The script loaded but did not expose the API — surface a config error.\n const error = new Error('Google Identity Services API is unavailable') as ClientConfigError;\n error.type = 'unknown';\n return { ok: false, error };\n }\n\n return new Promise<CodeResult>((resolve) => {\n let settled = false;\n const settle = (result: CodeResult) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n const client = accounts.oauth2.initCodeClient({\n client_id,\n scope,\n ux_mode,\n redirect_uri,\n state,\n login_hint,\n hd,\n callback: (response) =>\n settle(response.code ? { ok: true, response } : { ok: false, error: response }),\n error_callback: (error) => settle({ ok: false, error }),\n });\n\n client.requestCode();\n });\n}\n"],"mappings":";AAkDA,IAAI,SAAmC;AAEvC,SAAS,aAA4B;CACnC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ;GACV,IAAI,OAAO,QAAQ,SAAS,IAC1B,QAAQ;QACH;IACL,OAAO,iBAAiB,cAAc,QAAQ,CAAC;IAC/C,OAAO,iBAAiB,eAAe;KACrC,SAAS;KACT,uBAAO,IAAI,MAAM,sCAAsC,CAAC;IAC1D,CAAC;GACH;GACA;EACF;EAEA,SAAS,SAAS,cAAc,QAAQ;EACxC,OAAO,KAAK;EACZ,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,OAAO,MAAM;EACb,OAAO,eAAe,QAAQ;EAC9B,OAAO,gBAAgB;GACrB,SAAS;GACT,uBAAO,IAAI,MAAM,sCAAsC,CAAC;EAC1D;EAEA,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;AACH;;AAGA,SAAS,mBAA4B;CACnC,OAAO,OAAO,WAAW,eAAe,wBAAwB;AAClE;;AAGA,eAAsB,OAAO,EAC3B,OACA,WACA,cAAc,OACd,uBAAuB,MACvB,wBAAwB,SACO;CAM/B,IAAI,wBAAwB,CAAC,iBAAiB,GAC5C,OAAO;EAAE,YAAY;EAAO,aAAa;EAAM,QAAQ;CAAoB;CAG7E,IAAI;EACF,MAAM,WAAW;CACnB,QAAQ;EACN,OAAO;GAAE,YAAY;GAAO,aAAa;GAAM,QAAQ;EAAqB;CAC9E;CAEA,MAAM,WAAW,OAAO,QAAQ;CAChC,IAAI,CAAC,UAEH,OAAO;EAAE,YAAY;EAAO,aAAa;EAAM,QAAQ;CAAqB;CAG9E,OAAO,IAAI,SAAuB,YAAY;EAC5C,IAAI,UAAU;EACd,MAAM,UAAU,WAAyB;GACvC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,MAAM;EAChB;EAEA,IAAI;GACF,SAAS,GAAG,WAAW;IACrB,SAAS;IACT,SAAS;IACT;IACA;IACA;IACA;IACA;IACA,WAAW,eAAe,OAAO;KAAE,YAAY;KAAM;IAAW,CAAC;IACjE,kBAAkB,eAAe,OAAO;KAAE,YAAY;KAAM;IAAW,CAAC;GAC1E,CAAC;GAED,SAAS,GAAG,QAAQ,iBAAiB;IACnC,IAAI,SAAS;IAEb,MAAM,UAAU,aAAa,gBAAgB;IAC7C,MAAM,YAAY,aAAa,kBAAkB;IACjD,MAAM,kBAAkB,aAAa,mBAAmB;IAExD,IAAI,WAAY,aAAa,oBAAoB,uBAC/C,OAAO;KACL,YAAY;KACZ,QAAQ;MAAE;MAAS;MAAW;KAAgB;IAChD,CAAC;GAEL,CAAC;EACH,QAAQ;GACN,OAAO;IAAE,YAAY;IAAO,aAAa;IAAM,QAAQ;GAAe,CAAC;EACzE;CACF,CAAC;AACH;;;;;;;;;;AA+BA,eAAsB,YAAY,EAChC,WACA,QAAQ,wBACR,UAAU,SACV,cACA,OACA,YACA,MACiC;CACjC,MAAM,WAAW;CAEjB,MAAM,WAAW,OAAO,QAAQ;CAChC,IAAI,CAAC,UAAU;EAEb,MAAM,wBAAQ,IAAI,MAAM,6CAA6C;EACrE,MAAM,OAAO;EACb,OAAO;GAAE,IAAI;GAAO;EAAM;CAC5B;CAEA,OAAO,IAAI,SAAqB,YAAY;EAC1C,IAAI,UAAU;EACd,MAAM,UAAU,WAAuB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,QAAQ,MAAM;EAChB;EAeA,SAbwB,OAAO,eAAe;GAC5C;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,aACT,OAAO,SAAS,OAAO;IAAE,IAAI;IAAM;GAAS,IAAI;IAAE,IAAI;IAAO,OAAO;GAAS,CAAC;GAChF,iBAAiB,UAAU,OAAO;IAAE,IAAI;IAAO;GAAM,CAAC;EACxD,CAEK,CAAC,CAAC,YAAY;CACrB,CAAC;AACH"}
@@ -60,11 +60,11 @@ function csrf(config = {}) {
60
60
  await next();
61
61
  return;
62
62
  }
63
- if (config.origin && config.origin.includes(c.req.header("origin") ?? "")) {
63
+ if (config.origin?.includes(c.req.header("origin") ?? "")) {
64
64
  await next();
65
65
  return;
66
66
  }
67
- if (config.secFetchSite && config.secFetchSite.includes(c.req.header("sec-fetch-site") ?? "")) {
67
+ if (config.secFetchSite?.includes(c.req.header("sec-fetch-site") ?? "")) {
68
68
  await next();
69
69
  return;
70
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"csrf.cjs","names":["SmartRouter","RegExpRouter","TrieRouter","METHOD_NAME_ALL","Status"],"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'node:crypto';\nimport type { MiddlewareHandler } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n /**\n * Cookie name for CSRF token\n * @default 'XSRF-TOKEN'\n */\n cookieName?: string;\n\n /**\n * Header name for CSRF token\n * @default 'X-XSRF-TOKEN'\n */\n headerName?: string;\n\n /**\n * Ignore rules for specific paths and methods\n * @example\n * [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' }, // ignores all methods\n * ]\n */\n ignores?: CSRFIgnoreRule[];\n\n /**\n * Skip CSRF check for these methods\n * @default ['GET', 'HEAD', 'OPTIONS']\n */\n safeMethods?: HTTPMethod[];\n\n /**\n * Origin allowed to bypass CSRF check\n * @default undefined\n */\n origin?: string[];\n\n /**\n * Sec-Fetch-Site allowed to bypass CSRF check\n * @default undefined\n */\n secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;\n\n /**\n * Custom error message\n * @default 'CSRF token validation failed'\n */\n errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length === 0 || b.length === 0) return false;\n\n const bufferA = Buffer.from(a, 'utf-8');\n const bufferB = Buffer.from(b, 'utf-8');\n if (bufferA.length !== bufferB.length) return false;\n return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n * cookieName: 'csrf-token',\n * headerName: 'X-CSRF-Token',\n * ignores: [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' },\n * ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n // initialize router for matching ignore rules\n const router = new SmartRouter<boolean>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n // register ignore rules\n if (config.ignores) {\n for (const rule of config.ignores) {\n if (typeof rule === 'string') {\n router.add(METHOD_NAME_ALL, rule, true);\n } else if (rule.methods && rule.methods.length > 0) {\n for (const method of rule.methods) {\n router.add(method, rule.path, true);\n }\n } else {\n // if no methods are specified, ignore all methods\n router.add(METHOD_NAME_ALL, rule.path, true);\n }\n }\n }\n\n // return middleware\n return async (c, next) => {\n const method = c.req.method;\n const path = c.req.path;\n\n // check if the request should be ignored\n // 1. ignore safe methods\n if (safeMethods.has(method)) {\n await next();\n return;\n }\n\n // 2. ignore configured origin\n if (config.origin && config.origin.includes(c.req.header('origin') ?? '')) {\n await next();\n return;\n }\n\n // 3. ignore configured secFetchSite\n if (\n config.secFetchSite &&\n config.secFetchSite.includes((c.req.header('sec-fetch-site') ?? '') as never)\n ) {\n await next();\n return;\n }\n\n // 4. ignore configured ignore rules\n const [matched] = router.match(method, path);\n if (matched.length > 0) {\n await next();\n return;\n }\n\n const cookieToken = getCookie(c, cookieName);\n const headerToken = c.req.header(headerName);\n\n if (!cookieToken || !headerToken) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n if (!safeCompare(cookieToken, headerToken)) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n await next();\n };\n}\n"],"mappings":";;;;;;;;;;AA8DA,SAAS,YAAY,GAAW,GAAoB;CAClD,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO;CAC3D,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,GAAG,OAAO;CAE7C,MAAM,UAAU,OAAO,KAAK,GAAG,OAAO;CACtC,MAAM,UAAU,OAAO,KAAK,GAAG,OAAO;CACtC,IAAI,QAAQ,WAAW,QAAQ,QAAQ,OAAO;CAC9C,QAAA,GAAA,YAAA,gBAAA,CAAuB,SAAS,OAAO;AACzC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,KAAK,SAAqB,CAAC,GAAsB;CAC/D,MAAM,aAAa,OAAO,cAAc;CACxC,MAAM,aAAa,OAAO,cAAc;CACxC,MAAM,cAAc,IAAI,IAAI,OAAO,eAAe;EAAC;EAAO;EAAQ;CAAS,CAAC;CAC5E,MAAM,eAAe,OAAO,gBAAgB;CAG5C,MAAM,SAAS,IAAIA,yBAAAA,YAAqB,EACtC,SAAS,CAAC,IAAIC,2BAAAA,aAAa,GAAG,IAAIC,wBAAAA,WAAW,CAAC,EAChD,CAAC;CAGD,IAAI,OAAO,SACT,KAAK,MAAM,QAAQ,OAAO,SACxB,IAAI,OAAO,SAAS,UAClB,OAAO,IAAIC,YAAAA,iBAAiB,MAAM,IAAI;MACjC,IAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GAC/C,KAAK,MAAM,UAAU,KAAK,SACxB,OAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;MAIpC,OAAO,IAAIA,YAAAA,iBAAiB,KAAK,MAAM,IAAI;CAMjD,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,SAAS,EAAE,IAAI;EACrB,MAAM,OAAO,EAAE,IAAI;EAInB,IAAI,YAAY,IAAI,MAAM,GAAG;GAC3B,MAAM,KAAK;GACX;EACF;EAGA,IAAI,OAAO,UAAU,OAAO,OAAO,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK,EAAE,GAAG;GACzE,MAAM,KAAK;GACX;EACF;EAGA,IACE,OAAO,gBACP,OAAO,aAAa,SAAU,EAAE,IAAI,OAAO,gBAAgB,KAAK,EAAY,GAC5E;GACA,MAAM,KAAK;GACX;EACF;EAGA,MAAM,CAAC,WAAW,OAAO,MAAM,QAAQ,IAAI;EAC3C,IAAI,QAAQ,SAAS,GAAG;GACtB,MAAM,KAAK;GACX;EACF;EAEA,MAAM,eAAA,GAAA,YAAA,UAAA,CAAwB,GAAG,UAAU;EAC3C,MAAM,cAAc,EAAE,IAAI,OAAO,UAAU;EAE3C,IAAI,CAAC,eAAe,CAAC,aACnB,MAAMC,qBAAAA,OAAO,iBAAiB,YAAY,CAAC,CAAC,MAAM;EAGpD,IAAI,CAAC,YAAY,aAAa,WAAW,GACvC,MAAMA,qBAAAA,OAAO,iBAAiB,YAAY,CAAC,CAAC,MAAM;EAGpD,MAAM,KAAK;CACb;AACF"}
1
+ {"version":3,"file":"csrf.cjs","names":["SmartRouter","RegExpRouter","TrieRouter","METHOD_NAME_ALL","Status"],"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'node:crypto';\nimport type { MiddlewareHandler } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n /**\n * Cookie name for CSRF token\n * @default 'XSRF-TOKEN'\n */\n cookieName?: string;\n\n /**\n * Header name for CSRF token\n * @default 'X-XSRF-TOKEN'\n */\n headerName?: string;\n\n /**\n * Ignore rules for specific paths and methods\n * @example\n * [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' }, // ignores all methods\n * ]\n */\n ignores?: CSRFIgnoreRule[];\n\n /**\n * Skip CSRF check for these methods\n * @default ['GET', 'HEAD', 'OPTIONS']\n */\n safeMethods?: HTTPMethod[];\n\n /**\n * Origin allowed to bypass CSRF check\n * @default undefined\n */\n origin?: string[];\n\n /**\n * Sec-Fetch-Site allowed to bypass CSRF check\n * @default undefined\n */\n secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;\n\n /**\n * Custom error message\n * @default 'CSRF token validation failed'\n */\n errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length === 0 || b.length === 0) return false;\n\n const bufferA = Buffer.from(a, 'utf-8');\n const bufferB = Buffer.from(b, 'utf-8');\n if (bufferA.length !== bufferB.length) return false;\n return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n * cookieName: 'csrf-token',\n * headerName: 'X-CSRF-Token',\n * ignores: [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' },\n * ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n // initialize router for matching ignore rules\n const router = new SmartRouter<boolean>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n // register ignore rules\n if (config.ignores) {\n for (const rule of config.ignores) {\n if (typeof rule === 'string') {\n router.add(METHOD_NAME_ALL, rule, true);\n } else if (rule.methods && rule.methods.length > 0) {\n for (const method of rule.methods) {\n router.add(method, rule.path, true);\n }\n } else {\n // if no methods are specified, ignore all methods\n router.add(METHOD_NAME_ALL, rule.path, true);\n }\n }\n }\n\n // return middleware\n return async (c, next) => {\n const method = c.req.method;\n const path = c.req.path;\n\n // check if the request should be ignored\n // 1. ignore safe methods\n if (safeMethods.has(method)) {\n await next();\n return;\n }\n\n // 2. ignore configured origin\n if (config.origin?.includes(c.req.header('origin') ?? '')) {\n await next();\n return;\n }\n\n // 3. ignore configured secFetchSite\n if (config.secFetchSite?.includes((c.req.header('sec-fetch-site') ?? '') as never)) {\n await next();\n return;\n }\n\n // 4. ignore configured ignore rules\n const [matched] = router.match(method, path);\n if (matched.length > 0) {\n await next();\n return;\n }\n\n const cookieToken = getCookie(c, cookieName);\n const headerToken = c.req.header(headerName);\n\n if (!cookieToken || !headerToken) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n if (!safeCompare(cookieToken, headerToken)) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n await next();\n };\n}\n"],"mappings":";;;;;;;;;;AA8DA,SAAS,YAAY,GAAW,GAAoB;CAClD,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO;CAC3D,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,GAAG,OAAO;CAE7C,MAAM,UAAU,OAAO,KAAK,GAAG,OAAO;CACtC,MAAM,UAAU,OAAO,KAAK,GAAG,OAAO;CACtC,IAAI,QAAQ,WAAW,QAAQ,QAAQ,OAAO;CAC9C,QAAA,GAAA,YAAA,gBAAA,CAAuB,SAAS,OAAO;AACzC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,KAAK,SAAqB,CAAC,GAAsB;CAC/D,MAAM,aAAa,OAAO,cAAc;CACxC,MAAM,aAAa,OAAO,cAAc;CACxC,MAAM,cAAc,IAAI,IAAI,OAAO,eAAe;EAAC;EAAO;EAAQ;CAAS,CAAC;CAC5E,MAAM,eAAe,OAAO,gBAAgB;CAG5C,MAAM,SAAS,IAAIA,yBAAAA,YAAqB,EACtC,SAAS,CAAC,IAAIC,2BAAAA,aAAa,GAAG,IAAIC,wBAAAA,WAAW,CAAC,EAChD,CAAC;CAGD,IAAI,OAAO,SACT,KAAK,MAAM,QAAQ,OAAO,SACxB,IAAI,OAAO,SAAS,UAClB,OAAO,IAAIC,YAAAA,iBAAiB,MAAM,IAAI;MACjC,IAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GAC/C,KAAK,MAAM,UAAU,KAAK,SACxB,OAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;MAIpC,OAAO,IAAIA,YAAAA,iBAAiB,KAAK,MAAM,IAAI;CAMjD,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,SAAS,EAAE,IAAI;EACrB,MAAM,OAAO,EAAE,IAAI;EAInB,IAAI,YAAY,IAAI,MAAM,GAAG;GAC3B,MAAM,KAAK;GACX;EACF;EAGA,IAAI,OAAO,QAAQ,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK,EAAE,GAAG;GACzD,MAAM,KAAK;GACX;EACF;EAGA,IAAI,OAAO,cAAc,SAAU,EAAE,IAAI,OAAO,gBAAgB,KAAK,EAAY,GAAG;GAClF,MAAM,KAAK;GACX;EACF;EAGA,MAAM,CAAC,WAAW,OAAO,MAAM,QAAQ,IAAI;EAC3C,IAAI,QAAQ,SAAS,GAAG;GACtB,MAAM,KAAK;GACX;EACF;EAEA,MAAM,eAAA,GAAA,YAAA,UAAA,CAAwB,GAAG,UAAU;EAC3C,MAAM,cAAc,EAAE,IAAI,OAAO,UAAU;EAE3C,IAAI,CAAC,eAAe,CAAC,aACnB,MAAMC,qBAAAA,OAAO,iBAAiB,YAAY,CAAC,CAAC,MAAM;EAGpD,IAAI,CAAC,YAAY,aAAa,WAAW,GACvC,MAAMA,qBAAAA,OAAO,iBAAiB,YAAY,CAAC,CAAC,MAAM;EAGpD,MAAM,KAAK;CACb;AACF"}
@@ -59,11 +59,11 @@ function csrf(config = {}) {
59
59
  await next();
60
60
  return;
61
61
  }
62
- if (config.origin && config.origin.includes(c.req.header("origin") ?? "")) {
62
+ if (config.origin?.includes(c.req.header("origin") ?? "")) {
63
63
  await next();
64
64
  return;
65
65
  }
66
- if (config.secFetchSite && config.secFetchSite.includes(c.req.header("sec-fetch-site") ?? "")) {
66
+ if (config.secFetchSite?.includes(c.req.header("sec-fetch-site") ?? "")) {
67
67
  await next();
68
68
  return;
69
69
  }