lazypock 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  getFileUrl: () => getFileUrl,
40
40
  getScaleUrl: () => getScaleUrl,
41
41
  getThumbUrl: () => getThumbUrl,
42
+ getVariantUrl: () => getVariantUrl,
42
43
  schemaFieldType: () => schemaFieldType,
43
44
  wsUrlFromBaseUrl: () => wsUrlFromBaseUrl
44
45
  });
@@ -641,9 +642,6 @@ function isSafeBrowserUrl(url) {
641
642
  return false;
642
643
  }
643
644
  }
644
- function isRecordAuth(value) {
645
- return !!value && typeof value === "object" && typeof value.token === "string" && !!value.record && typeof value.record === "object";
646
- }
647
645
  function authorizeWithOAuth2Popup(opts) {
648
646
  return new Promise((resolve, reject) => {
649
647
  if (typeof window === "undefined") {
@@ -686,18 +684,20 @@ function authorizeWithOAuth2Popup(opts) {
686
684
  const data = event.data;
687
685
  if (!data || typeof data !== "object") return;
688
686
  if (data.type === "lazypock:oauth2") {
689
- if (isRecordAuth(data.result)) {
687
+ const code = data.code;
688
+ if (typeof code === "string" && code) {
690
689
  if (popup) {
691
690
  try {
692
691
  popup.close();
693
692
  } catch {
694
693
  }
695
694
  }
696
- finish(null, data.result);
695
+ const state = typeof data.state === "string" ? data.state : void 0;
696
+ finish(null, { code, state });
697
697
  } else {
698
698
  finish(
699
699
  new ApiError(
700
- "OAuth2 callback returned an invalid result.",
700
+ "OAuth2 callback returned no authorization code.",
701
701
  data,
702
702
  400,
703
703
  false,
@@ -708,16 +708,9 @@ function authorizeWithOAuth2Popup(opts) {
708
708
  return;
709
709
  }
710
710
  if (data.type === "lazypock:oauth2:error") {
711
- const result = data.result ?? {};
712
- const message = typeof result.message === "string" && result.message ? result.message : "OAuth2 authorization failed.";
711
+ const message = typeof data.message === "string" && data.message ? data.message : "OAuth2 authorization failed.";
713
712
  finish(
714
- new ApiError(
715
- message,
716
- result,
717
- typeof result.code === "number" ? result.code : 400,
718
- false,
719
- "oauth2_provider_error"
720
- )
713
+ new ApiError(message, data, 400, false, "oauth2_provider_error")
721
714
  );
722
715
  }
723
716
  };
@@ -1237,7 +1230,7 @@ var CollectionService = class _CollectionService {
1237
1230
  requestKey,
1238
1231
  autoCancel,
1239
1232
  cancelKey,
1240
- fetch,
1233
+ fetch: fetch2,
1241
1234
  headers,
1242
1235
  signal,
1243
1236
  singleFlight,
@@ -1281,7 +1274,7 @@ var CollectionService = class _CollectionService {
1281
1274
  requestKey,
1282
1275
  autoCancel,
1283
1276
  cancelKey,
1284
- fetch,
1277
+ fetch: fetch2,
1285
1278
  headers,
1286
1279
  signal,
1287
1280
  singleFlight,
@@ -1638,14 +1631,13 @@ var CollectionService = class _CollectionService {
1638
1631
  /**
1639
1632
  * Sign in with an OAuth2 provider (PocketBase `authWithOAuth2` parity).
1640
1633
  *
1641
- * One call handles the whole web flow: fetch the provider's `authURL`,
1642
- * open a popup (or call `options.urlCallback`), wait for the backend's
1643
- * `postMessage` result, populate the auth store, and resolve with
1644
- * `{ token, record, meta }`.
1634
+ * One call handles the whole web flow: fetch the provider's `authURL`, open a
1635
+ * popup (or call `options.urlCallback`), receive the single-use authorization
1636
+ * `code` the backend relays via `postMessage`, exchange it, populate the auth
1637
+ * store, and resolve with `{ token, record, meta }`.
1645
1638
  *
1646
- * `options.createData` is accepted for PocketBase signature parity but is
1647
- * **not** forwarded by the backend's popup redirect flow — use
1648
- * {@link authWithOAuth2Code} when you need extra fields on first sign-up.
1639
+ * `options.createData` is forwarded on first sign-up (the exchange goes
1640
+ * through {@link authWithOAuth2Code}), matching PocketBase.
1649
1641
  *
1650
1642
  * React Native / non-browser: the popup flow depends on
1651
1643
  * `window.postMessage`. Use {@link listAuthMethods} + {@link authWithOAuth2Code}
@@ -1654,6 +1646,7 @@ var CollectionService = class _CollectionService {
1654
1646
  async authWithOAuth2(options) {
1655
1647
  const {
1656
1648
  provider,
1649
+ createData,
1657
1650
  urlCallback,
1658
1651
  popup,
1659
1652
  timeoutMs = DEFAULT_OAUTH2_TIMEOUT_MS
@@ -1671,18 +1664,19 @@ var CollectionService = class _CollectionService {
1671
1664
  "oauth2_unknown_provider"
1672
1665
  );
1673
1666
  }
1674
- const result = await authorizeWithOAuth2Popup({
1667
+ const { code } = await authorizeWithOAuth2Popup({
1675
1668
  authURL: providerInfo.authURL,
1676
1669
  expectedOrigin: oauth2RedirectOrigin(this.http.baseUrl),
1677
1670
  urlCallback,
1678
1671
  popup,
1679
1672
  timeoutMs
1680
1673
  });
1681
- if (this.authStore) {
1682
- this.authStore.setCollectionName(this.collectionName);
1683
- this.authStore.set(result.token, result.record);
1684
- }
1685
- return result;
1674
+ return this.authWithOAuth2Code({
1675
+ provider,
1676
+ code,
1677
+ codeVerifier: providerInfo.codeVerifier,
1678
+ createData
1679
+ });
1686
1680
  }
1687
1681
  /**
1688
1682
  * Exchange an OAuth2 authorization code directly (PocketBase
@@ -2006,9 +2000,28 @@ function getThumbUrl(baseUrl, fileId, size) {
2006
2000
  function getScaleUrl(baseUrl, fileId, size) {
2007
2001
  return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId) + "/scale/" + encodeURIComponent(size);
2008
2002
  }
2003
+ function getVariantUrl(baseUrl, fileId, preset) {
2004
+ return getScaleUrl(baseUrl, fileId, preset);
2005
+ }
2006
+ function parseUploadTarget(rawUrl, allowedHosts) {
2007
+ let parsed;
2008
+ try {
2009
+ parsed = new URL(rawUrl);
2010
+ } catch {
2011
+ throw new Error("Direct upload: the server returned an invalid upload URL");
2012
+ }
2013
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
2014
+ throw new Error(`Direct upload: unsupported protocol ${parsed.protocol}`);
2015
+ }
2016
+ if (allowedHosts && allowedHosts.length > 0 && !allowedHosts.includes(parsed.host)) {
2017
+ throw new Error(`Direct upload: host ${parsed.host} is not in the allowlist`);
2018
+ }
2019
+ return parsed;
2020
+ }
2009
2021
  var FilesService = class {
2010
- constructor(http) {
2022
+ constructor(http, allowedUploadHosts) {
2011
2023
  this.http = http;
2024
+ this.allowedUploadHosts = allowedUploadHosts;
2012
2025
  }
2013
2026
  /**
2014
2027
  * Upload a file or blob.
@@ -2029,6 +2042,8 @@ var FilesService = class {
2029
2042
  formData.append("collection_name", meta.collectionName);
2030
2043
  if (meta?.recordId) formData.append("record_id", meta.recordId);
2031
2044
  if (meta?.fieldName) formData.append("field_name", meta.fieldName);
2045
+ if (meta?.origin) formData.append("origin", meta.origin);
2046
+ if (meta?.variants?.length) formData.append("variants", meta.variants.join(","));
2032
2047
  const data = await this.http.request(
2033
2048
  "POST",
2034
2049
  "/files",
@@ -2037,6 +2052,62 @@ var FilesService = class {
2037
2052
  );
2038
2053
  return data;
2039
2054
  }
2055
+ /**
2056
+ * Direct upload (Mode B): the browser PUTs straight to S3/R2.
2057
+ *
2058
+ * Presigns, uploads, then asks the server to verify the object. Falls back to
2059
+ * `upload()` when the server has no direct-upload support configured.
2060
+ */
2061
+ async uploadDirect(file, opts) {
2062
+ const filename = opts?.filename || (file instanceof File ? file.name : "file");
2063
+ const mime = file.type || "application/octet-stream";
2064
+ const presigned = await this.presign({
2065
+ filename,
2066
+ size: file.size,
2067
+ mime,
2068
+ collectionName: opts?.collectionName,
2069
+ fieldName: opts?.fieldName,
2070
+ recordId: opts?.recordId
2071
+ });
2072
+ if (!presigned) return this.upload(file, filename, { signal: opts?.signal }, { collectionName: opts?.collectionName, fieldName: opts?.fieldName, recordId: opts?.recordId });
2073
+ const target = parseUploadTarget(presigned.url, this.allowedUploadHosts);
2074
+ const put = await fetch(target.href, {
2075
+ method: presigned.method || "PUT",
2076
+ headers: presigned.headers,
2077
+ body: file,
2078
+ signal: opts?.signal
2079
+ });
2080
+ if (!put.ok) throw new Error(`Direct upload failed: HTTP ${put.status}`);
2081
+ return this.complete(presigned.id, { variant: opts?.variant, signal: opts?.signal });
2082
+ }
2083
+ /** Step 1 of a direct upload: reserve an object and get a presigned PUT. */
2084
+ async presign(params) {
2085
+ const data = await this.http.request(
2086
+ "POST",
2087
+ "/files/presign",
2088
+ {
2089
+ filename: params.filename,
2090
+ size: params.size,
2091
+ mime: params.mime,
2092
+ collection_name: params.collectionName,
2093
+ field_name: params.fieldName,
2094
+ record_id: params.recordId
2095
+ }
2096
+ );
2097
+ if (!data || typeof data.url !== "string") return null;
2098
+ return data;
2099
+ }
2100
+ /** Step 2 of a direct upload: ask the server to verify the object. */
2101
+ async complete(fileId, opts) {
2102
+ const query = opts?.variant ? `?variants=${encodeURIComponent(opts.variant)}` : "";
2103
+ const data = await this.http.request(
2104
+ "POST",
2105
+ `/files/${encodeURIComponent(fileId)}/complete${query}`,
2106
+ {},
2107
+ { signal: opts?.signal }
2108
+ );
2109
+ return data;
2110
+ }
2040
2111
  /**
2041
2112
  * List uploaded files (newest first), with optional filters.
2042
2113
  *
@@ -2849,6 +2920,7 @@ function createClient(options) {
2849
2920
  getFileUrl,
2850
2921
  getScaleUrl,
2851
2922
  getThumbUrl,
2923
+ getVariantUrl,
2852
2924
  schemaFieldType,
2853
2925
  wsUrlFromBaseUrl
2854
2926
  });