@scalemule/nextjs 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/client.d.mts +16 -1
- package/dist/client.d.ts +16 -1
- package/dist/client.js +199 -13
- package/dist/client.mjs +199 -13
- package/dist/index-Tq5WdDfS.d.mts +939 -0
- package/dist/index-Tq5WdDfS.d.ts +939 -0
- package/dist/index.d.mts +28 -4
- package/dist/index.d.ts +28 -4
- package/dist/index.js +1268 -43
- package/dist/index.mjs +1269 -45
- package/dist/ledvery-CxPzZpxP.d.mts +44 -0
- package/dist/ledvery-CxPzZpxP.d.ts +44 -0
- package/dist/server/auth.js +6 -1
- package/dist/server/auth.mjs +6 -1
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +645 -675
- package/dist/server/index.mjs +646 -676
- package/dist/server/ledvery.d.mts +1 -0
- package/dist/server/ledvery.d.ts +1 -0
- package/dist/server/ledvery.js +336 -0
- package/dist/server/ledvery.mjs +333 -0
- package/dist/server/webhook-handler.d.mts +2 -2
- package/dist/server/webhook-handler.d.ts +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/webhook-handler-Cfa7-yKN.d.ts +808 -0
- package/dist/webhook-handler-DN4WBjbm.d.mts +808 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -321,21 +321,93 @@ function buildClientContextHeaders(context) {
|
|
|
321
321
|
if (context.userAgent) headers["X-Client-User-Agent"] = context.userAgent;
|
|
322
322
|
if (context.deviceFingerprint) headers["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
|
|
323
323
|
if (context.referrer) headers["X-Client-Referrer"] = context.referrer;
|
|
324
|
+
if (context.anonymousId) headers["x-anonymous-id"] = context.anonymousId;
|
|
324
325
|
return headers;
|
|
325
326
|
}
|
|
327
|
+
var STORAGE_KEYS = {
|
|
328
|
+
SESSION: "scalemule_session",
|
|
329
|
+
USER_ID: "scalemule_user_id",
|
|
330
|
+
WORKSPACE_ID: "scalemule_workspace_id",
|
|
331
|
+
ANONYMOUS_ID: "scalemule_anonymous_id",
|
|
332
|
+
SESSION_POOL: "scalemule_session_pool",
|
|
333
|
+
ACTIVE_ACCOUNT: "scalemule_active_account",
|
|
334
|
+
KNOWN_ACCOUNTS: "scalemule_known_accounts",
|
|
335
|
+
OFFLINE_QUEUE: "scalemule_offline_queue"
|
|
336
|
+
};
|
|
337
|
+
var LEGACY_ANONYMOUS_ID_KEYS = ["sm_anonymous_id"];
|
|
338
|
+
function generateAnonymousId() {
|
|
339
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
340
|
+
return crypto.randomUUID();
|
|
341
|
+
}
|
|
342
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
343
|
+
const r = Math.random() * 16 | 0;
|
|
344
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
345
|
+
return v.toString(16);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
var inFlight = /* @__PURE__ */ new WeakMap();
|
|
349
|
+
function ensureAnonymousId(storage) {
|
|
350
|
+
const existing = inFlight.get(storage);
|
|
351
|
+
if (existing) return existing;
|
|
352
|
+
const promise = (async () => {
|
|
353
|
+
try {
|
|
354
|
+
return await resolveAnonymousId(storage);
|
|
355
|
+
} finally {
|
|
356
|
+
inFlight.delete(storage);
|
|
357
|
+
}
|
|
358
|
+
})();
|
|
359
|
+
inFlight.set(storage, promise);
|
|
360
|
+
return promise;
|
|
361
|
+
}
|
|
362
|
+
async function resolveAnonymousId(storage) {
|
|
363
|
+
const canonical = await storage.getItem(STORAGE_KEYS.ANONYMOUS_ID);
|
|
364
|
+
if (canonical) {
|
|
365
|
+
for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
|
|
366
|
+
const legacy = await storage.getItem(legacyKey);
|
|
367
|
+
if (legacy !== canonical) {
|
|
368
|
+
try {
|
|
369
|
+
await storage.setItem(legacyKey, canonical);
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return canonical;
|
|
375
|
+
}
|
|
376
|
+
for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
|
|
377
|
+
const legacy = await storage.getItem(legacyKey);
|
|
378
|
+
if (legacy) {
|
|
379
|
+
try {
|
|
380
|
+
await storage.setItem(STORAGE_KEYS.ANONYMOUS_ID, legacy);
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
return legacy;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const fresh = generateAnonymousId();
|
|
387
|
+
try {
|
|
388
|
+
await storage.setItem(STORAGE_KEYS.ANONYMOUS_ID, fresh);
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
for (const legacyKey of LEGACY_ANONYMOUS_ID_KEYS) {
|
|
392
|
+
try {
|
|
393
|
+
await storage.setItem(legacyKey, fresh);
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return fresh;
|
|
398
|
+
}
|
|
326
399
|
var SDK_VERSION = "0.0.1";
|
|
327
400
|
var DEFAULT_TIMEOUT = 3e4;
|
|
328
401
|
var DEFAULT_MAX_RETRIES = 2;
|
|
329
402
|
var DEFAULT_BACKOFF_MS = 300;
|
|
330
403
|
var MAX_BACKOFF_MS = 3e4;
|
|
331
|
-
var SESSION_STORAGE_KEY =
|
|
332
|
-
var USER_ID_STORAGE_KEY =
|
|
333
|
-
var OFFLINE_QUEUE_KEY =
|
|
334
|
-
var WORKSPACE_STORAGE_KEY =
|
|
335
|
-
var
|
|
336
|
-
var
|
|
337
|
-
var
|
|
338
|
-
var KNOWN_ACCOUNTS_KEY = "scalemule_known_accounts";
|
|
404
|
+
var SESSION_STORAGE_KEY = STORAGE_KEYS.SESSION;
|
|
405
|
+
var USER_ID_STORAGE_KEY = STORAGE_KEYS.USER_ID;
|
|
406
|
+
var OFFLINE_QUEUE_KEY = STORAGE_KEYS.OFFLINE_QUEUE;
|
|
407
|
+
var WORKSPACE_STORAGE_KEY = STORAGE_KEYS.WORKSPACE_ID;
|
|
408
|
+
var SESSION_POOL_KEY = STORAGE_KEYS.SESSION_POOL;
|
|
409
|
+
var ACTIVE_ACCOUNT_KEY = STORAGE_KEYS.ACTIVE_ACCOUNT;
|
|
410
|
+
var KNOWN_ACCOUNTS_KEY = STORAGE_KEYS.KNOWN_ACCOUNTS;
|
|
339
411
|
var GATEWAY_URLS = {
|
|
340
412
|
dev: "https://api-dev.scalemule.com",
|
|
341
413
|
prod: "https://api.scalemule.com"
|
|
@@ -578,6 +650,7 @@ var ScaleMuleClient = class {
|
|
|
578
650
|
this.offlineQueue = null;
|
|
579
651
|
this.workspaceId = null;
|
|
580
652
|
this.anonymousId = null;
|
|
653
|
+
this.anonymousIdPromise = null;
|
|
581
654
|
this.sessionPool = /* @__PURE__ */ new Map();
|
|
582
655
|
this.knownAccounts = /* @__PURE__ */ new Map();
|
|
583
656
|
this.refreshPromise = null;
|
|
@@ -613,12 +686,7 @@ var ScaleMuleClient = class {
|
|
|
613
686
|
if (userId) this.userId = userId;
|
|
614
687
|
const wsId = await this.storage.getItem(WORKSPACE_STORAGE_KEY);
|
|
615
688
|
if (wsId) this.workspaceId = wsId;
|
|
616
|
-
|
|
617
|
-
if (!anonId) {
|
|
618
|
-
anonId = crypto.randomUUID();
|
|
619
|
-
await this.storage.setItem(ANONYMOUS_ID_STORAGE_KEY, anonId);
|
|
620
|
-
}
|
|
621
|
-
this.anonymousId = anonId;
|
|
689
|
+
await this.ensureAnonymousId();
|
|
622
690
|
if (this.multiSessionEnabled) {
|
|
623
691
|
const poolJson = await this.storage.getItem(SESSION_POOL_KEY);
|
|
624
692
|
if (poolJson) {
|
|
@@ -670,7 +738,7 @@ var ScaleMuleClient = class {
|
|
|
670
738
|
"[ScaleMule] Initialized, session:",
|
|
671
739
|
!!this.sessionToken,
|
|
672
740
|
"anonymousId:",
|
|
673
|
-
|
|
741
|
+
this.anonymousId,
|
|
674
742
|
"poolSize:",
|
|
675
743
|
this.sessionPool.size,
|
|
676
744
|
"knownAccounts:",
|
|
@@ -724,9 +792,38 @@ var ScaleMuleClient = class {
|
|
|
724
792
|
isAuthenticated() {
|
|
725
793
|
return this.sessionToken !== null;
|
|
726
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Sync accessor for the cached anonymous ID. Returns `null` until either
|
|
797
|
+
* `initialize()` has run or `ensureAnonymousId()` has been awaited at
|
|
798
|
+
* least once. Use `ensureAnonymousId()` if you need a guaranteed value.
|
|
799
|
+
*/
|
|
727
800
|
getAnonymousId() {
|
|
728
801
|
return this.anonymousId;
|
|
729
802
|
}
|
|
803
|
+
/**
|
|
804
|
+
* Lazy-mint or cache-then-return the anonymous ID.
|
|
805
|
+
*
|
|
806
|
+
* Always returns a usable string. Callers that need to attach the
|
|
807
|
+
* `x-anonymous-id` header before `initialize()` has completed (rare —
|
|
808
|
+
* usually only the very first unauthenticated request from a freshly
|
|
809
|
+
* constructed client) should `await` this rather than relying on
|
|
810
|
+
* `getAnonymousId()`.
|
|
811
|
+
*
|
|
812
|
+
* Concurrent callers receive the same in-flight promise — no risk of
|
|
813
|
+
* two simultaneous first requests minting two distinct IDs.
|
|
814
|
+
*/
|
|
815
|
+
async ensureAnonymousId() {
|
|
816
|
+
if (this.anonymousId) return this.anonymousId;
|
|
817
|
+
if (!this.anonymousIdPromise) {
|
|
818
|
+
this.anonymousIdPromise = ensureAnonymousId(this.storage).then((id) => {
|
|
819
|
+
this.anonymousId = id;
|
|
820
|
+
return id;
|
|
821
|
+
}).finally(() => {
|
|
822
|
+
this.anonymousIdPromise = null;
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
return this.anonymousIdPromise;
|
|
826
|
+
}
|
|
730
827
|
isMultiSessionEnabled() {
|
|
731
828
|
return this.multiSessionEnabled;
|
|
732
829
|
}
|
|
@@ -902,8 +999,16 @@ var ScaleMuleClient = class {
|
|
|
902
999
|
if (this.workspaceId) {
|
|
903
1000
|
headers["x-sm-workspace-id"] = this.workspaceId;
|
|
904
1001
|
}
|
|
905
|
-
if (!
|
|
906
|
-
|
|
1002
|
+
if (!init.skipAuth && !this.sessionToken) {
|
|
1003
|
+
if (!this.anonymousId) {
|
|
1004
|
+
try {
|
|
1005
|
+
await this.ensureAnonymousId();
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (this.anonymousId) {
|
|
1010
|
+
headers["x-anonymous-id"] = this.anonymousId;
|
|
1011
|
+
}
|
|
907
1012
|
}
|
|
908
1013
|
if (attempt > 0 && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
909
1014
|
if (!idempotencyKey) {
|
|
@@ -5706,6 +5811,15 @@ var PhotoService = class extends ServiceModule {
|
|
|
5706
5811
|
async delete(id, options) {
|
|
5707
5812
|
return this.del(`/${id}`, options);
|
|
5708
5813
|
}
|
|
5814
|
+
async getPresets(options) {
|
|
5815
|
+
return this._get("/presets", options);
|
|
5816
|
+
}
|
|
5817
|
+
async getManifest(id, params, options) {
|
|
5818
|
+
return this._get(this.withQuery(`/${id}/manifest`, params), options);
|
|
5819
|
+
}
|
|
5820
|
+
async getPublicManifest(id, params, options) {
|
|
5821
|
+
return this._get(this.withQuery(`/public/${id}/manifest`, params), options);
|
|
5822
|
+
}
|
|
5709
5823
|
/**
|
|
5710
5824
|
* Build an absolute URL for the on-demand transform endpoint.
|
|
5711
5825
|
*
|
|
@@ -5954,6 +6068,592 @@ var PhotoService = class extends ServiceModule {
|
|
|
5954
6068
|
return this.get(id);
|
|
5955
6069
|
}
|
|
5956
6070
|
};
|
|
6071
|
+
var MEDIA_PRESETS = {
|
|
6072
|
+
hero: { widths: [400, 800, 1200, 1600, 2400] },
|
|
6073
|
+
inline: { widths: [400, 800, 1200] },
|
|
6074
|
+
thumbnail: { widths: [120, 240, 360], fit: "cover" },
|
|
6075
|
+
avatar: { widths: [64, 128, 256], fit: "cover" },
|
|
6076
|
+
logo: { widths: [120, 240, 480] }
|
|
6077
|
+
};
|
|
6078
|
+
var MediaService = class extends ServiceModule {
|
|
6079
|
+
constructor(client, storage, photo, video, audio) {
|
|
6080
|
+
super(client);
|
|
6081
|
+
this.storage = storage;
|
|
6082
|
+
this.photo = photo;
|
|
6083
|
+
this.video = video;
|
|
6084
|
+
this.audio = audio;
|
|
6085
|
+
this.basePath = "";
|
|
6086
|
+
}
|
|
6087
|
+
getPresets() {
|
|
6088
|
+
return MEDIA_PRESETS;
|
|
6089
|
+
}
|
|
6090
|
+
async upload(file, options, requestOptions) {
|
|
6091
|
+
const mimeType = file.type || "application/octet-stream";
|
|
6092
|
+
const kind = detectKind(mimeType);
|
|
6093
|
+
const visibility = resolveVisibility(options);
|
|
6094
|
+
const isPublic = visibility !== "private";
|
|
6095
|
+
const isAnonymous = visibility === "anonymous_visible";
|
|
6096
|
+
const gateOnPipeline = shouldGateOnPipeline(options?.policy);
|
|
6097
|
+
const emit = (phase, progress, fileId) => {
|
|
6098
|
+
options?.onProgress?.({ phase, progress, file_id: fileId });
|
|
6099
|
+
};
|
|
6100
|
+
const uploadOptions = {
|
|
6101
|
+
filename: options?.filename,
|
|
6102
|
+
metadata: options?.metadata,
|
|
6103
|
+
signal: options?.signal,
|
|
6104
|
+
onProgress: (progress) => emit("uploading", progress)
|
|
6105
|
+
};
|
|
6106
|
+
emit("presigning", 0);
|
|
6107
|
+
try {
|
|
6108
|
+
if (isAnonymous) {
|
|
6109
|
+
return await this.uploadAnonymous(file, mimeType, kind, options, requestOptions, uploadOptions, emit);
|
|
6110
|
+
}
|
|
6111
|
+
if (kind === "image" && !options?.skipPhotoRegister) {
|
|
6112
|
+
return await this.uploadImage(
|
|
6113
|
+
file,
|
|
6114
|
+
mimeType,
|
|
6115
|
+
visibility,
|
|
6116
|
+
gateOnPipeline,
|
|
6117
|
+
options,
|
|
6118
|
+
requestOptions,
|
|
6119
|
+
uploadOptions,
|
|
6120
|
+
emit
|
|
6121
|
+
);
|
|
6122
|
+
}
|
|
6123
|
+
if (kind === "video" && visibility === "private") {
|
|
6124
|
+
const result = await this.video.uploadViaStorage(file, uploadOptions, requestOptions);
|
|
6125
|
+
if (result.error || !result.data) {
|
|
6126
|
+
return { data: null, error: result.error };
|
|
6127
|
+
}
|
|
6128
|
+
if (gateOnPipeline) {
|
|
6129
|
+
emit("optimizing", 100, result.data.file_id);
|
|
6130
|
+
await result.data.hls_url_promise;
|
|
6131
|
+
}
|
|
6132
|
+
const info = await this.fetchInfoBestEffort(result.data.file_id, requestOptions);
|
|
6133
|
+
const asset2 = this.buildAsset(
|
|
6134
|
+
info ?? fallbackFileInfo(result.data.file_id, mimeType, visibility, result.data.original_view_url, null),
|
|
6135
|
+
{
|
|
6136
|
+
photoId: null,
|
|
6137
|
+
preset: options?.preset,
|
|
6138
|
+
customWidths: options?.customWidths
|
|
6139
|
+
}
|
|
6140
|
+
);
|
|
6141
|
+
emit("ready", 100, result.data.file_id);
|
|
6142
|
+
return {
|
|
6143
|
+
data: {
|
|
6144
|
+
file_id: result.data.file_id,
|
|
6145
|
+
photo_id: null,
|
|
6146
|
+
original_view_url: result.data.original_view_url,
|
|
6147
|
+
optimized_url_promise: Promise.resolve(null),
|
|
6148
|
+
hls_url_promise: result.data.hls_url_promise,
|
|
6149
|
+
mime_type: mimeType,
|
|
6150
|
+
is_public: false,
|
|
6151
|
+
visibility,
|
|
6152
|
+
cdn_url: null,
|
|
6153
|
+
asset: asset2
|
|
6154
|
+
},
|
|
6155
|
+
error: null
|
|
6156
|
+
};
|
|
6157
|
+
}
|
|
6158
|
+
if (kind === "audio" && visibility === "private") {
|
|
6159
|
+
const result = await this.audio.uploadViaStorage(file, uploadOptions, requestOptions);
|
|
6160
|
+
if (result.error || !result.data) {
|
|
6161
|
+
return { data: null, error: result.error };
|
|
6162
|
+
}
|
|
6163
|
+
const info = await this.fetchInfoBestEffort(result.data.file_id, requestOptions);
|
|
6164
|
+
const asset2 = this.buildAsset(
|
|
6165
|
+
info ?? fallbackFileInfo(result.data.file_id, mimeType, visibility, result.data.original_view_url, null),
|
|
6166
|
+
{
|
|
6167
|
+
photoId: null,
|
|
6168
|
+
preset: options?.preset,
|
|
6169
|
+
customWidths: options?.customWidths
|
|
6170
|
+
}
|
|
6171
|
+
);
|
|
6172
|
+
emit("ready", 100, result.data.file_id);
|
|
6173
|
+
return {
|
|
6174
|
+
data: {
|
|
6175
|
+
file_id: result.data.file_id,
|
|
6176
|
+
photo_id: null,
|
|
6177
|
+
original_view_url: result.data.original_view_url,
|
|
6178
|
+
optimized_url_promise: Promise.resolve(null),
|
|
6179
|
+
hls_url_promise: Promise.resolve(null),
|
|
6180
|
+
mime_type: mimeType,
|
|
6181
|
+
is_public: false,
|
|
6182
|
+
visibility,
|
|
6183
|
+
cdn_url: null,
|
|
6184
|
+
asset: asset2
|
|
6185
|
+
},
|
|
6186
|
+
error: null
|
|
6187
|
+
};
|
|
6188
|
+
}
|
|
6189
|
+
const storageResult = isPublic ? await this.storage.upload(file, {
|
|
6190
|
+
...uploadOptions,
|
|
6191
|
+
visibility: "app_public",
|
|
6192
|
+
skipCompression: true
|
|
6193
|
+
}) : await this.storage.uploadPrivate(file, uploadOptions);
|
|
6194
|
+
if (storageResult.error || !storageResult.data) {
|
|
6195
|
+
return { data: null, error: storageResult.error };
|
|
6196
|
+
}
|
|
6197
|
+
const fileInfo = storageResult.data;
|
|
6198
|
+
const asset = this.buildAsset(fileInfo, {
|
|
6199
|
+
photoId: null,
|
|
6200
|
+
preset: options?.preset,
|
|
6201
|
+
customWidths: options?.customWidths
|
|
6202
|
+
});
|
|
6203
|
+
emit("ready", 100, fileInfo.id);
|
|
6204
|
+
return {
|
|
6205
|
+
data: {
|
|
6206
|
+
file_id: fileInfo.id,
|
|
6207
|
+
photo_id: null,
|
|
6208
|
+
original_view_url: fileInfo.url ?? null,
|
|
6209
|
+
optimized_url_promise: Promise.resolve(null),
|
|
6210
|
+
hls_url_promise: Promise.resolve(null),
|
|
6211
|
+
mime_type: mimeType,
|
|
6212
|
+
is_public: fileInfo.is_public ?? isPublic,
|
|
6213
|
+
visibility: resolveFileVisibility(fileInfo, visibility),
|
|
6214
|
+
cdn_url: fileInfo.cdn_url ?? null,
|
|
6215
|
+
asset
|
|
6216
|
+
},
|
|
6217
|
+
error: null
|
|
6218
|
+
};
|
|
6219
|
+
} catch (error) {
|
|
6220
|
+
return {
|
|
6221
|
+
data: null,
|
|
6222
|
+
error: normalizeThrownError(error)
|
|
6223
|
+
};
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
async get(fileId, options, requestOptions) {
|
|
6227
|
+
const result = await this.storage.getInfo(fileId, requestOptions);
|
|
6228
|
+
if (result.error || !result.data) {
|
|
6229
|
+
return { data: null, error: result.error };
|
|
6230
|
+
}
|
|
6231
|
+
return {
|
|
6232
|
+
data: this.buildAsset(result.data, {
|
|
6233
|
+
photoId: null,
|
|
6234
|
+
preset: options?.preset,
|
|
6235
|
+
customWidths: options?.customWidths
|
|
6236
|
+
}),
|
|
6237
|
+
error: null
|
|
6238
|
+
};
|
|
6239
|
+
}
|
|
6240
|
+
async list(params, requestOptions) {
|
|
6241
|
+
const result = await this.storage.list(params, requestOptions);
|
|
6242
|
+
return {
|
|
6243
|
+
data: result.data.map(
|
|
6244
|
+
(file) => this.buildAsset(file, {
|
|
6245
|
+
photoId: null,
|
|
6246
|
+
preset: params?.preset,
|
|
6247
|
+
customWidths: params?.customWidths
|
|
6248
|
+
})
|
|
6249
|
+
),
|
|
6250
|
+
metadata: result.metadata,
|
|
6251
|
+
error: result.error
|
|
6252
|
+
};
|
|
6253
|
+
}
|
|
6254
|
+
async delete(fileId, options) {
|
|
6255
|
+
try {
|
|
6256
|
+
const [, storageResult] = await Promise.all([
|
|
6257
|
+
this.photo.delete(fileId, options).catch(() => ({ data: null, error: null })),
|
|
6258
|
+
this.storage.delete(fileId, options)
|
|
6259
|
+
]);
|
|
6260
|
+
if (storageResult.error && storageResult.error.status !== 404) {
|
|
6261
|
+
return { data: null, error: storageResult.error };
|
|
6262
|
+
}
|
|
6263
|
+
return { data: { deleted: true }, error: null };
|
|
6264
|
+
} catch (error) {
|
|
6265
|
+
return { data: null, error: normalizeThrownError(error) };
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
async releaseQuarantine(fileId, reason = "Released via @scalemule/sdk media.releaseQuarantine()", options) {
|
|
6269
|
+
const response = await this.client.post(
|
|
6270
|
+
`/v1/storage/admin/files/${fileId}/scan-override`,
|
|
6271
|
+
{ scan_status: "clean", reason },
|
|
6272
|
+
options
|
|
6273
|
+
);
|
|
6274
|
+
if (response.error) {
|
|
6275
|
+
return { data: null, error: response.error };
|
|
6276
|
+
}
|
|
6277
|
+
return this.get(fileId, void 0, options);
|
|
6278
|
+
}
|
|
6279
|
+
async getManifest(fileId, options, requestOptions) {
|
|
6280
|
+
const file = await this.storage.getInfo(fileId, requestOptions);
|
|
6281
|
+
if (file.error || !file.data) {
|
|
6282
|
+
return { data: null, error: file.error };
|
|
6283
|
+
}
|
|
6284
|
+
const manifest = await this.fetchManifest(file.data, options, requestOptions);
|
|
6285
|
+
return { data: manifest, error: null };
|
|
6286
|
+
}
|
|
6287
|
+
buildAsset(file, options, manifest) {
|
|
6288
|
+
const visibility = resolveFileVisibility(file);
|
|
6289
|
+
const photoId = options?.photoId ?? null;
|
|
6290
|
+
const originalUrl = visibility === "anonymous_visible" ? file.cdn_url ?? file.url ?? null : file.url ?? file.cdn_url ?? null;
|
|
6291
|
+
return {
|
|
6292
|
+
file_id: file.id,
|
|
6293
|
+
photo_id: photoId,
|
|
6294
|
+
filename: file.filename,
|
|
6295
|
+
content_type: file.content_type,
|
|
6296
|
+
size_bytes: file.size_bytes,
|
|
6297
|
+
kind: detectKind(file.content_type),
|
|
6298
|
+
visibility,
|
|
6299
|
+
is_public: file.is_public ?? visibility !== "private",
|
|
6300
|
+
scan_status: file.scan_status,
|
|
6301
|
+
created_at: file.created_at,
|
|
6302
|
+
original_url: originalUrl,
|
|
6303
|
+
cdn_url: file.cdn_url ?? null,
|
|
6304
|
+
manifest: manifest ?? this.buildManifest(file, options)
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
buildManifest(file, options) {
|
|
6308
|
+
const kind = detectKind(file.content_type);
|
|
6309
|
+
const visibility = resolveFileVisibility(file);
|
|
6310
|
+
const originalUrl = visibility === "anonymous_visible" ? file.cdn_url ?? file.url ?? null : file.url ?? file.cdn_url ?? null;
|
|
6311
|
+
if (!originalUrl) {
|
|
6312
|
+
return null;
|
|
6313
|
+
}
|
|
6314
|
+
if (kind !== "image" || file.content_type === "image/svg+xml") {
|
|
6315
|
+
return {
|
|
6316
|
+
file_id: file.id,
|
|
6317
|
+
content_type: file.content_type,
|
|
6318
|
+
preset: "original",
|
|
6319
|
+
variants: { original: originalUrl },
|
|
6320
|
+
srcset: null,
|
|
6321
|
+
default: originalUrl
|
|
6322
|
+
};
|
|
6323
|
+
}
|
|
6324
|
+
const preset = options?.preset ?? "inline";
|
|
6325
|
+
const spec = resolvePresetSpec(preset, options?.customWidths);
|
|
6326
|
+
const variants = Object.fromEntries(
|
|
6327
|
+
spec.widths.map((width) => [
|
|
6328
|
+
String(width),
|
|
6329
|
+
visibility === "anonymous_visible" ? this.photo.getPublicTransformUrl(file.id, transformOptions(width, spec.fit)) : this.photo.getTransformUrl(file.id, transformOptions(width, spec.fit))
|
|
6330
|
+
])
|
|
6331
|
+
);
|
|
6332
|
+
const defaultWidth = pickDefaultWidth(spec.widths);
|
|
6333
|
+
const defaultUrl = variants[String(defaultWidth)] ?? Object.values(variants)[0] ?? originalUrl;
|
|
6334
|
+
return {
|
|
6335
|
+
file_id: file.id,
|
|
6336
|
+
content_type: file.content_type,
|
|
6337
|
+
preset,
|
|
6338
|
+
variants,
|
|
6339
|
+
srcset: spec.widths.map((width) => `${variants[String(width)]} ${width}w`).join(", "),
|
|
6340
|
+
default: defaultUrl
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
async uploadAnonymous(file, mimeType, kind, options, requestOptions, uploadOptions, emit) {
|
|
6344
|
+
const result = await this.storage.uploadAnonymous(file, uploadOptions);
|
|
6345
|
+
if (result.error || !result.data) {
|
|
6346
|
+
return { data: null, error: result.error };
|
|
6347
|
+
}
|
|
6348
|
+
emit("scanning", 100, result.data.id);
|
|
6349
|
+
const ready = await this.waitForAnonymousReady(result.data.id, options?.signal, emit);
|
|
6350
|
+
if (ready.error) {
|
|
6351
|
+
return { data: null, error: ready.error };
|
|
6352
|
+
}
|
|
6353
|
+
const fileInfo = await this.fetchInfoBestEffort(result.data.id, requestOptions) ?? {
|
|
6354
|
+
...result.data,
|
|
6355
|
+
cdn_url: ready.cdnUrl ?? result.data.cdn_url,
|
|
6356
|
+
scan_status: ready.scanStatus
|
|
6357
|
+
};
|
|
6358
|
+
let manifest = this.buildManifest(fileInfo, {
|
|
6359
|
+
preset: options?.preset,
|
|
6360
|
+
customWidths: options?.customWidths
|
|
6361
|
+
});
|
|
6362
|
+
if (kind === "image" && fileInfo.content_type !== "image/svg+xml" && options?.preset !== "custom") {
|
|
6363
|
+
emit("optimizing", 100, result.data.id);
|
|
6364
|
+
const persistedManifest = await this.waitForAnonymousManifest(fileInfo.id, options, requestOptions, emit);
|
|
6365
|
+
if (persistedManifest.error) {
|
|
6366
|
+
return { data: null, error: persistedManifest.error };
|
|
6367
|
+
}
|
|
6368
|
+
manifest = persistedManifest.manifest ?? manifest;
|
|
6369
|
+
}
|
|
6370
|
+
const asset = this.buildAsset(
|
|
6371
|
+
fileInfo,
|
|
6372
|
+
{
|
|
6373
|
+
photoId: null,
|
|
6374
|
+
preset: options?.preset,
|
|
6375
|
+
customWidths: options?.customWidths
|
|
6376
|
+
},
|
|
6377
|
+
manifest
|
|
6378
|
+
);
|
|
6379
|
+
emit("ready", 100, result.data.id);
|
|
6380
|
+
return {
|
|
6381
|
+
data: {
|
|
6382
|
+
file_id: result.data.id,
|
|
6383
|
+
photo_id: null,
|
|
6384
|
+
original_view_url: fileInfo.cdn_url ?? null,
|
|
6385
|
+
optimized_url_promise: Promise.resolve(manifest?.default ?? null),
|
|
6386
|
+
hls_url_promise: Promise.resolve(null),
|
|
6387
|
+
mime_type: mimeType,
|
|
6388
|
+
is_public: true,
|
|
6389
|
+
visibility: "anonymous_visible",
|
|
6390
|
+
cdn_url: fileInfo.cdn_url ?? null,
|
|
6391
|
+
asset
|
|
6392
|
+
},
|
|
6393
|
+
error: null
|
|
6394
|
+
};
|
|
6395
|
+
}
|
|
6396
|
+
async uploadImage(file, mimeType, visibility, gateOnPipeline, options, requestOptions, uploadOptions, emit) {
|
|
6397
|
+
if (visibility === "private") {
|
|
6398
|
+
const result = await this.photo.uploadViaStorage(file, uploadOptions, requestOptions);
|
|
6399
|
+
if (result.error || !result.data) {
|
|
6400
|
+
return { data: null, error: result.error };
|
|
6401
|
+
}
|
|
6402
|
+
if (gateOnPipeline) {
|
|
6403
|
+
emit("optimizing", 100, result.data.file_id);
|
|
6404
|
+
await result.data.optimized_url_promise;
|
|
6405
|
+
}
|
|
6406
|
+
const info2 = await this.fetchInfoBestEffort(result.data.file_id, requestOptions);
|
|
6407
|
+
const asset2 = this.buildAsset(
|
|
6408
|
+
info2 ?? fallbackFileInfo(result.data.file_id, mimeType, visibility, result.data.original_view_url, null),
|
|
6409
|
+
{
|
|
6410
|
+
photoId: result.data.photo_id,
|
|
6411
|
+
preset: options?.preset,
|
|
6412
|
+
customWidths: options?.customWidths
|
|
6413
|
+
}
|
|
6414
|
+
);
|
|
6415
|
+
emit("ready", 100, result.data.file_id);
|
|
6416
|
+
return {
|
|
6417
|
+
data: {
|
|
6418
|
+
file_id: result.data.file_id,
|
|
6419
|
+
photo_id: result.data.photo_id,
|
|
6420
|
+
original_view_url: result.data.original_view_url,
|
|
6421
|
+
optimized_url_promise: result.data.optimized_url_promise,
|
|
6422
|
+
hls_url_promise: Promise.resolve(null),
|
|
6423
|
+
mime_type: mimeType,
|
|
6424
|
+
is_public: false,
|
|
6425
|
+
visibility,
|
|
6426
|
+
cdn_url: null,
|
|
6427
|
+
asset: asset2
|
|
6428
|
+
},
|
|
6429
|
+
error: null
|
|
6430
|
+
};
|
|
6431
|
+
}
|
|
6432
|
+
const storageResult = await this.storage.upload(file, {
|
|
6433
|
+
...uploadOptions,
|
|
6434
|
+
visibility: "app_public",
|
|
6435
|
+
skipCompression: true
|
|
6436
|
+
});
|
|
6437
|
+
if (storageResult.error || !storageResult.data) {
|
|
6438
|
+
return { data: null, error: storageResult.error };
|
|
6439
|
+
}
|
|
6440
|
+
const originalViewUrl = storageResult.data.url ?? null;
|
|
6441
|
+
const registerResult = await this.photo.register({ fileId: storageResult.data.id }, requestOptions);
|
|
6442
|
+
const photoId = registerResult.error || !registerResult.data ? null : registerResult.data.id;
|
|
6443
|
+
const optimizedUrlPromise = photoId ? this.pollPhotoOptimizationComplete(photoId, requestOptions) : Promise.resolve(null);
|
|
6444
|
+
if (gateOnPipeline) {
|
|
6445
|
+
emit("optimizing", 100, storageResult.data.id);
|
|
6446
|
+
await optimizedUrlPromise;
|
|
6447
|
+
}
|
|
6448
|
+
const info = await this.fetchInfoBestEffort(storageResult.data.id, requestOptions) ?? storageResult.data;
|
|
6449
|
+
const asset = this.buildAsset(info, {
|
|
6450
|
+
photoId,
|
|
6451
|
+
preset: options?.preset,
|
|
6452
|
+
customWidths: options?.customWidths
|
|
6453
|
+
});
|
|
6454
|
+
emit("ready", 100, storageResult.data.id);
|
|
6455
|
+
return {
|
|
6456
|
+
data: {
|
|
6457
|
+
file_id: storageResult.data.id,
|
|
6458
|
+
photo_id: photoId,
|
|
6459
|
+
original_view_url: originalViewUrl,
|
|
6460
|
+
optimized_url_promise: optimizedUrlPromise,
|
|
6461
|
+
hls_url_promise: Promise.resolve(null),
|
|
6462
|
+
mime_type: mimeType,
|
|
6463
|
+
is_public: true,
|
|
6464
|
+
visibility,
|
|
6465
|
+
cdn_url: info.cdn_url ?? null,
|
|
6466
|
+
asset
|
|
6467
|
+
},
|
|
6468
|
+
error: null
|
|
6469
|
+
};
|
|
6470
|
+
}
|
|
6471
|
+
async waitForAnonymousReady(fileId, signal, emit) {
|
|
6472
|
+
const delays = [500, 1e3, 1500, 2e3, 2500, 3e3];
|
|
6473
|
+
for (let attempt = 0; attempt < 24; attempt++) {
|
|
6474
|
+
if (signal?.aborted) {
|
|
6475
|
+
return {
|
|
6476
|
+
cdnUrl: null,
|
|
6477
|
+
scanStatus: void 0,
|
|
6478
|
+
error: { code: "aborted", message: "Upload aborted", status: 0 }
|
|
6479
|
+
};
|
|
6480
|
+
}
|
|
6481
|
+
const status = await this.storage.getFileStatus(fileId);
|
|
6482
|
+
if (status.error || !status.data) {
|
|
6483
|
+
await sleep4(delays[Math.min(attempt, delays.length - 1)] ?? 3e3);
|
|
6484
|
+
continue;
|
|
6485
|
+
}
|
|
6486
|
+
const scanStatus = status.data.scan.status;
|
|
6487
|
+
if (status.data.urls.cdn_url) {
|
|
6488
|
+
return { cdnUrl: status.data.urls.cdn_url, scanStatus, error: null };
|
|
6489
|
+
}
|
|
6490
|
+
if (scanStatus === "threat" || scanStatus === "quarantined" || scanStatus === "error") {
|
|
6491
|
+
return {
|
|
6492
|
+
cdnUrl: null,
|
|
6493
|
+
scanStatus,
|
|
6494
|
+
error: scanError(scanStatus)
|
|
6495
|
+
};
|
|
6496
|
+
}
|
|
6497
|
+
emit("scanning", Math.min(99, 10 + attempt * 4), fileId);
|
|
6498
|
+
await sleep4(delays[Math.min(attempt, delays.length - 1)] ?? 3e3);
|
|
6499
|
+
}
|
|
6500
|
+
return {
|
|
6501
|
+
cdnUrl: null,
|
|
6502
|
+
scanStatus: "pending",
|
|
6503
|
+
error: {
|
|
6504
|
+
code: "file_scanning",
|
|
6505
|
+
message: "File scan did not complete in time. Re-fetch the file status and publish once cdn_url appears.",
|
|
6506
|
+
status: 202
|
|
6507
|
+
}
|
|
6508
|
+
};
|
|
6509
|
+
}
|
|
6510
|
+
async fetchInfoBestEffort(fileId, requestOptions) {
|
|
6511
|
+
const result = await this.storage.getInfo(fileId, requestOptions);
|
|
6512
|
+
return result.error || !result.data ? null : result.data;
|
|
6513
|
+
}
|
|
6514
|
+
async fetchManifest(file, options, requestOptions) {
|
|
6515
|
+
const kind = detectKind(file.content_type);
|
|
6516
|
+
const visibility = resolveFileVisibility(file);
|
|
6517
|
+
if (kind !== "image" || file.content_type === "image/svg+xml") {
|
|
6518
|
+
return this.buildManifest(file, options);
|
|
6519
|
+
}
|
|
6520
|
+
if (visibility !== "anonymous_visible" || options?.preset === "custom") {
|
|
6521
|
+
return this.buildManifest(file, options);
|
|
6522
|
+
}
|
|
6523
|
+
const persisted = await this.photo.getPublicManifest(
|
|
6524
|
+
file.id,
|
|
6525
|
+
{ preset: options?.preset ?? "inline" },
|
|
6526
|
+
requestOptions
|
|
6527
|
+
);
|
|
6528
|
+
if (persisted.error || !persisted.data) {
|
|
6529
|
+
return this.buildManifest(file, options);
|
|
6530
|
+
}
|
|
6531
|
+
return persisted.data;
|
|
6532
|
+
}
|
|
6533
|
+
async pollPhotoOptimizationComplete(photoId, requestOptions) {
|
|
6534
|
+
for (let attempt = 0; attempt < 40; attempt++) {
|
|
6535
|
+
const result = await this.photo.get(photoId, requestOptions);
|
|
6536
|
+
const status = result.data?.optimization_status;
|
|
6537
|
+
if (status === "completed") {
|
|
6538
|
+
return this.photo.getTransformUrl(photoId, { width: 1200 });
|
|
6539
|
+
}
|
|
6540
|
+
await sleep4(250);
|
|
6541
|
+
}
|
|
6542
|
+
return null;
|
|
6543
|
+
}
|
|
6544
|
+
async waitForAnonymousManifest(fileId, options, requestOptions, emit) {
|
|
6545
|
+
const preset = options?.preset ?? "inline";
|
|
6546
|
+
const delays = [500, 1e3, 1500, 2e3, 2500, 3e3];
|
|
6547
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
6548
|
+
if (options?.signal?.aborted) {
|
|
6549
|
+
return {
|
|
6550
|
+
manifest: null,
|
|
6551
|
+
error: { code: "aborted", message: "Upload aborted", status: 0 }
|
|
6552
|
+
};
|
|
6553
|
+
}
|
|
6554
|
+
const manifest = await this.photo.getPublicManifest(fileId, { preset }, requestOptions);
|
|
6555
|
+
if (!manifest.error && manifest.data && manifest.data.ready) {
|
|
6556
|
+
return { manifest: manifest.data, error: null };
|
|
6557
|
+
}
|
|
6558
|
+
emit("optimizing", Math.min(99, 15 + attempt * 3), fileId);
|
|
6559
|
+
await sleep4(delays[Math.min(attempt, delays.length - 1)] ?? 3e3);
|
|
6560
|
+
}
|
|
6561
|
+
return {
|
|
6562
|
+
manifest: null,
|
|
6563
|
+
error: {
|
|
6564
|
+
code: "manifest_pending",
|
|
6565
|
+
message: "Photo variants are still being generated. Re-fetch the public manifest once ready flips true.",
|
|
6566
|
+
status: 202
|
|
6567
|
+
}
|
|
6568
|
+
};
|
|
6569
|
+
}
|
|
6570
|
+
};
|
|
6571
|
+
function detectKind(contentType) {
|
|
6572
|
+
if (contentType.startsWith("image/")) return "image";
|
|
6573
|
+
if (contentType.startsWith("video/")) return "video";
|
|
6574
|
+
if (contentType.startsWith("audio/")) return "audio";
|
|
6575
|
+
if (contentType.includes("pdf") || contentType.includes("text/") || contentType.includes("word") || contentType.includes("sheet") || contentType.includes("presentation")) {
|
|
6576
|
+
return "document";
|
|
6577
|
+
}
|
|
6578
|
+
return "other";
|
|
6579
|
+
}
|
|
6580
|
+
function resolveVisibility(options) {
|
|
6581
|
+
if (options?.visibility) return options.visibility;
|
|
6582
|
+
if (options?.isPublic === true) return "app_public";
|
|
6583
|
+
return "private";
|
|
6584
|
+
}
|
|
6585
|
+
function resolveFileVisibility(file, fallback = "private") {
|
|
6586
|
+
if (file.visibility) return file.visibility;
|
|
6587
|
+
if (file.is_public === true) return "app_public";
|
|
6588
|
+
return fallback;
|
|
6589
|
+
}
|
|
6590
|
+
function shouldGateOnPipeline(policy) {
|
|
6591
|
+
return policy === "safe_public" || policy === "moderated" || policy === "compliance";
|
|
6592
|
+
}
|
|
6593
|
+
function resolvePresetSpec(preset, customWidths) {
|
|
6594
|
+
if (preset === "custom") {
|
|
6595
|
+
const widths = Array.from(
|
|
6596
|
+
new Set((customWidths ?? []).filter((value) => Number.isFinite(value) && value > 0))
|
|
6597
|
+
).sort((a, b) => a - b);
|
|
6598
|
+
return { widths: widths.length > 0 ? widths : MEDIA_PRESETS.inline.widths };
|
|
6599
|
+
}
|
|
6600
|
+
return MEDIA_PRESETS[preset];
|
|
6601
|
+
}
|
|
6602
|
+
function transformOptions(width, fit) {
|
|
6603
|
+
return fit === "cover" ? { width, height: width, fit: "cover" } : { width, fit: fit ?? "contain" };
|
|
6604
|
+
}
|
|
6605
|
+
function pickDefaultWidth(widths) {
|
|
6606
|
+
if (widths.includes(1200)) return 1200;
|
|
6607
|
+
return widths[Math.floor(widths.length / 2)] ?? widths[0] ?? 0;
|
|
6608
|
+
}
|
|
6609
|
+
function fallbackFileInfo(fileId, contentType, visibility, originalUrl, cdnUrl) {
|
|
6610
|
+
return {
|
|
6611
|
+
id: fileId,
|
|
6612
|
+
filename: fileId,
|
|
6613
|
+
content_type: contentType,
|
|
6614
|
+
size_bytes: 0,
|
|
6615
|
+
is_public: visibility !== "private",
|
|
6616
|
+
visibility,
|
|
6617
|
+
cdn_url: cdnUrl ?? void 0,
|
|
6618
|
+
url: originalUrl ?? void 0,
|
|
6619
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6620
|
+
};
|
|
6621
|
+
}
|
|
6622
|
+
function normalizeThrownError(error) {
|
|
6623
|
+
if (typeof error === "object" && error && "code" in error && "message" in error) {
|
|
6624
|
+
return error;
|
|
6625
|
+
}
|
|
6626
|
+
return {
|
|
6627
|
+
code: "upload_error",
|
|
6628
|
+
message: error instanceof Error ? error.message : "Media operation failed",
|
|
6629
|
+
status: 500
|
|
6630
|
+
};
|
|
6631
|
+
}
|
|
6632
|
+
function scanError(status) {
|
|
6633
|
+
switch (status) {
|
|
6634
|
+
case "threat":
|
|
6635
|
+
return {
|
|
6636
|
+
code: "file_threat",
|
|
6637
|
+
message: "File was flagged as malicious during scanning.",
|
|
6638
|
+
status: 409
|
|
6639
|
+
};
|
|
6640
|
+
case "quarantined":
|
|
6641
|
+
return {
|
|
6642
|
+
code: "file_quarantined",
|
|
6643
|
+
message: "File was quarantined during scanning.",
|
|
6644
|
+
status: 409
|
|
6645
|
+
};
|
|
6646
|
+
default:
|
|
6647
|
+
return {
|
|
6648
|
+
code: "upload_error",
|
|
6649
|
+
message: "File scan failed.",
|
|
6650
|
+
status: 500
|
|
6651
|
+
};
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
function sleep4(ms) {
|
|
6655
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6656
|
+
}
|
|
5957
6657
|
var AudioService = class extends ServiceModule {
|
|
5958
6658
|
/**
|
|
5959
6659
|
* @param storage Required for {@link uploadViaStorage}. Wired up by the
|
|
@@ -6867,6 +7567,7 @@ var ScaleMule = class {
|
|
|
6867
7567
|
this.functions = new FunctionsService(this._client);
|
|
6868
7568
|
this.photo = new PhotoService(this._client, this.storage);
|
|
6869
7569
|
this.audio = new AudioService(this._client, this.storage);
|
|
7570
|
+
this.media = new MediaService(this._client, this.storage, this.photo, this.video, this.audio);
|
|
6870
7571
|
this.tts = new TtsService(this._client);
|
|
6871
7572
|
this.flagContent = new FlagContentService(this._client);
|
|
6872
7573
|
this.creatorMaker = new CreatorMakerService(this._client);
|
|
@@ -7017,9 +7718,9 @@ var GATEWAY_URLS2 = {
|
|
|
7017
7718
|
dev: "https://api-dev.scalemule.com",
|
|
7018
7719
|
prod: "https://api.scalemule.com"
|
|
7019
7720
|
};
|
|
7020
|
-
var SESSION_STORAGE_KEY2 =
|
|
7021
|
-
var USER_ID_STORAGE_KEY2 =
|
|
7022
|
-
var WORKSPACE_STORAGE_KEY2 =
|
|
7721
|
+
var SESSION_STORAGE_KEY2 = STORAGE_KEYS.SESSION;
|
|
7722
|
+
var USER_ID_STORAGE_KEY2 = STORAGE_KEYS.USER_ID;
|
|
7723
|
+
var WORKSPACE_STORAGE_KEY2 = STORAGE_KEYS.WORKSPACE_ID;
|
|
7023
7724
|
var RETRYABLE_STATUS_CODES3 = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
7024
7725
|
function sleep3(ms) {
|
|
7025
7726
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -7279,6 +7980,15 @@ var ScaleMuleClient2 = class {
|
|
|
7279
7980
|
this.sessionGate = null;
|
|
7280
7981
|
this.resolveSessionGate = null;
|
|
7281
7982
|
this.workspaceId = null;
|
|
7983
|
+
// Anonymous visitor ID for identity linking. See `@scalemule/sdk/anonymous-id`
|
|
7984
|
+
// for the contract. The base SDK and this client share the same canonical
|
|
7985
|
+
// localStorage key so a visitor has exactly one ID across all packages.
|
|
7986
|
+
this.anonymousId = null;
|
|
7987
|
+
// Per-client single-flight cache for the lazy mint. Concurrent first-use
|
|
7988
|
+
// callers receive the same promise — without this, two simultaneous
|
|
7989
|
+
// unauthenticated requests on a fresh visitor could mint different IDs.
|
|
7990
|
+
// The shared helper also single-flights at the storage-adapter level.
|
|
7991
|
+
this.anonymousIdPromise = null;
|
|
7282
7992
|
this.refreshPromise = null;
|
|
7283
7993
|
this.apiKey = config.apiKey;
|
|
7284
7994
|
this.applicationId = config.applicationId || null;
|
|
@@ -7415,6 +8125,10 @@ var ScaleMuleClient2 = class {
|
|
|
7415
8125
|
if (userId) this.userId = userId;
|
|
7416
8126
|
const wsId = await this.storage.getItem(WORKSPACE_STORAGE_KEY2);
|
|
7417
8127
|
if (wsId) this.workspaceId = wsId;
|
|
8128
|
+
try {
|
|
8129
|
+
await this.ensureAnonymousId();
|
|
8130
|
+
} catch {
|
|
8131
|
+
}
|
|
7418
8132
|
if (token) {
|
|
7419
8133
|
this.resolveSessionPending();
|
|
7420
8134
|
}
|
|
@@ -7422,6 +8136,32 @@ var ScaleMuleClient2 = class {
|
|
|
7422
8136
|
console.log("[ScaleMule] Initialized with session:", !!token);
|
|
7423
8137
|
}
|
|
7424
8138
|
}
|
|
8139
|
+
/**
|
|
8140
|
+
* Sync accessor for the cached anonymous ID. Returns `null` until either
|
|
8141
|
+
* `initialize()` has run or `ensureAnonymousId()` has been awaited at
|
|
8142
|
+
* least once. Use `ensureAnonymousId()` if you need a guaranteed value.
|
|
8143
|
+
*/
|
|
8144
|
+
getAnonymousId() {
|
|
8145
|
+
return this.anonymousId;
|
|
8146
|
+
}
|
|
8147
|
+
/**
|
|
8148
|
+
* Lazy-mint or cache-then-return the anonymous ID via the shared
|
|
8149
|
+
* `@scalemule/sdk` helper. Concurrent callers receive the same in-flight
|
|
8150
|
+
* promise, so a burst of simultaneous first-use requests all see the
|
|
8151
|
+
* same minted value.
|
|
8152
|
+
*/
|
|
8153
|
+
async ensureAnonymousId() {
|
|
8154
|
+
if (this.anonymousId) return this.anonymousId;
|
|
8155
|
+
if (!this.anonymousIdPromise) {
|
|
8156
|
+
this.anonymousIdPromise = ensureAnonymousId(this.storage).then((id) => {
|
|
8157
|
+
this.anonymousId = id;
|
|
8158
|
+
return id;
|
|
8159
|
+
}).finally(() => {
|
|
8160
|
+
this.anonymousIdPromise = null;
|
|
8161
|
+
});
|
|
8162
|
+
}
|
|
8163
|
+
return this.anonymousIdPromise;
|
|
8164
|
+
}
|
|
7425
8165
|
/**
|
|
7426
8166
|
* Set session after login
|
|
7427
8167
|
*/
|
|
@@ -7496,6 +8236,9 @@ var ScaleMuleClient2 = class {
|
|
|
7496
8236
|
if (this.workspaceId) {
|
|
7497
8237
|
headers.set("x-sm-workspace-id", this.workspaceId);
|
|
7498
8238
|
}
|
|
8239
|
+
if (!options?.skipAuth && !this.sessionToken && this.anonymousId) {
|
|
8240
|
+
headers.set("x-anonymous-id", this.anonymousId);
|
|
8241
|
+
}
|
|
7499
8242
|
if (!headers.has("Content-Type") && options?.body && typeof options.body === "string") {
|
|
7500
8243
|
headers.set("Content-Type", "application/json");
|
|
7501
8244
|
}
|
|
@@ -7508,6 +8251,12 @@ var ScaleMuleClient2 = class {
|
|
|
7508
8251
|
if (this.sessionGate) {
|
|
7509
8252
|
await this.sessionGate;
|
|
7510
8253
|
}
|
|
8254
|
+
if (!options.skipAuth && !this.sessionToken && !this.anonymousId) {
|
|
8255
|
+
try {
|
|
8256
|
+
await this.ensureAnonymousId();
|
|
8257
|
+
} catch {
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
7511
8260
|
const url = `${this.gatewayUrl}${path}`;
|
|
7512
8261
|
const headers = this.buildHeaders(options);
|
|
7513
8262
|
const maxRetries = options.skipRetry ? 0 : options.retries ?? 2;
|
|
@@ -8175,13 +8924,14 @@ function ScaleMuleProvider({
|
|
|
8175
8924
|
analyticsProxyUrl,
|
|
8176
8925
|
authProxyUrl,
|
|
8177
8926
|
publishableKey,
|
|
8927
|
+
apiKey,
|
|
8178
8928
|
gatewayUrl: resolvedGatewayUrl,
|
|
8179
8929
|
environment: environment || void 0,
|
|
8180
8930
|
enableAccountSwitcher,
|
|
8181
8931
|
accountSwitcherPrivacy,
|
|
8182
8932
|
bootstrapFlags
|
|
8183
8933
|
}),
|
|
8184
|
-
[client, money$1, baseClient, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, resolvedGatewayUrl, environment, enableAccountSwitcher, accountSwitcherPrivacy, bootstrapFlags, effectiveMediaPolicy]
|
|
8934
|
+
[client, money$1, baseClient, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, apiKey, resolvedGatewayUrl, environment, enableAccountSwitcher, accountSwitcherPrivacy, bootstrapFlags, effectiveMediaPolicy]
|
|
8185
8935
|
);
|
|
8186
8936
|
return /* @__PURE__ */ jsxRuntime.jsx(ScaleMuleContext.Provider, { value, children });
|
|
8187
8937
|
}
|
|
@@ -8242,6 +8992,19 @@ function getCookie(name) {
|
|
|
8242
8992
|
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
|
|
8243
8993
|
return match ? decodeURIComponent(match[1]) : void 0;
|
|
8244
8994
|
}
|
|
8995
|
+
var browserStorage = typeof window !== "undefined" && typeof window.localStorage !== "undefined" ? {
|
|
8996
|
+
getItem: (key) => window.localStorage.getItem(key),
|
|
8997
|
+
setItem: (key, value) => window.localStorage.setItem(key, value),
|
|
8998
|
+
removeItem: (key) => window.localStorage.removeItem(key)
|
|
8999
|
+
} : null;
|
|
9000
|
+
async function getProxyAnonymousId() {
|
|
9001
|
+
if (!browserStorage) return void 0;
|
|
9002
|
+
try {
|
|
9003
|
+
return await ensureAnonymousId(browserStorage);
|
|
9004
|
+
} catch {
|
|
9005
|
+
return void 0;
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
8245
9008
|
async function proxyFetch(proxyUrl, path, options = {}) {
|
|
8246
9009
|
const method = options.method || "POST";
|
|
8247
9010
|
const headers = {};
|
|
@@ -8254,6 +9017,10 @@ async function proxyFetch(proxyUrl, path, options = {}) {
|
|
|
8254
9017
|
headers["x-csrf-token"] = csrfToken;
|
|
8255
9018
|
}
|
|
8256
9019
|
}
|
|
9020
|
+
const anonymousId = await getProxyAnonymousId();
|
|
9021
|
+
if (anonymousId) {
|
|
9022
|
+
headers["x-anonymous-id"] = anonymousId;
|
|
9023
|
+
}
|
|
8257
9024
|
const response = await fetch(`${proxyUrl}/${path}`, {
|
|
8258
9025
|
method,
|
|
8259
9026
|
headers,
|
|
@@ -9725,6 +10492,442 @@ function useTtsJob(jobId, options) {
|
|
|
9725
10492
|
}, [jobId, options?.enabled, options?.pollIntervalMs, refresh]);
|
|
9726
10493
|
return { job, loading, error, refresh };
|
|
9727
10494
|
}
|
|
10495
|
+
var SPEEDS = [1, 1.25, 1.5, 2];
|
|
10496
|
+
var EXPIRY_REFRESH_WINDOW_MS = 3e4;
|
|
10497
|
+
function formatTime(seconds) {
|
|
10498
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "0:00";
|
|
10499
|
+
const wholeSeconds = Math.floor(seconds);
|
|
10500
|
+
const minutes = Math.floor(wholeSeconds / 60);
|
|
10501
|
+
const remainder = wholeSeconds % 60;
|
|
10502
|
+
return `${minutes}:${remainder.toString().padStart(2, "0")}`;
|
|
10503
|
+
}
|
|
10504
|
+
function getNextSpeed(currentSpeed) {
|
|
10505
|
+
const currentIndex = SPEEDS.findIndex((speed) => speed === currentSpeed);
|
|
10506
|
+
if (currentIndex === -1) return SPEEDS[0];
|
|
10507
|
+
return SPEEDS[(currentIndex + 1) % SPEEDS.length];
|
|
10508
|
+
}
|
|
10509
|
+
var styles = {
|
|
10510
|
+
root: {
|
|
10511
|
+
borderRadius: "1.75rem",
|
|
10512
|
+
border: "1px solid rgba(226, 232, 240, 0.8)",
|
|
10513
|
+
background: "linear-gradient(180deg, #f8fbff 0%, #edf4ff 100%)",
|
|
10514
|
+
padding: "1rem",
|
|
10515
|
+
color: "#0f172b",
|
|
10516
|
+
boxShadow: "0 22px 70px rgba(4, 12, 24, 0.28)"
|
|
10517
|
+
},
|
|
10518
|
+
layout: {
|
|
10519
|
+
display: "flex",
|
|
10520
|
+
flexWrap: "wrap",
|
|
10521
|
+
alignItems: "center",
|
|
10522
|
+
gap: "1rem"
|
|
10523
|
+
},
|
|
10524
|
+
playButton: {
|
|
10525
|
+
display: "flex",
|
|
10526
|
+
height: "4.5rem",
|
|
10527
|
+
width: "4.5rem",
|
|
10528
|
+
flexShrink: 0,
|
|
10529
|
+
alignItems: "center",
|
|
10530
|
+
justifyContent: "center",
|
|
10531
|
+
borderRadius: "9999px",
|
|
10532
|
+
border: "none",
|
|
10533
|
+
backgroundColor: "#020618",
|
|
10534
|
+
color: "#ffffff",
|
|
10535
|
+
boxShadow: "0 10px 28px rgba(15, 23, 42, 0.25)",
|
|
10536
|
+
cursor: "pointer"
|
|
10537
|
+
},
|
|
10538
|
+
playIcon: { display: "block", width: "1.75rem", height: "1.75rem", fill: "currentColor" },
|
|
10539
|
+
playIconOffset: { marginLeft: "0.25rem" },
|
|
10540
|
+
content: { minWidth: "16rem", flex: "1 1 18rem" },
|
|
10541
|
+
metaRow: {
|
|
10542
|
+
display: "flex",
|
|
10543
|
+
alignItems: "center",
|
|
10544
|
+
justifyContent: "space-between",
|
|
10545
|
+
gap: "0.75rem",
|
|
10546
|
+
fontSize: "0.8rem",
|
|
10547
|
+
fontWeight: 500,
|
|
10548
|
+
color: "#62748e"
|
|
10549
|
+
},
|
|
10550
|
+
metaRight: { display: "flex", alignItems: "center", gap: "0.75rem" },
|
|
10551
|
+
badge: {
|
|
10552
|
+
borderRadius: "9999px",
|
|
10553
|
+
backgroundColor: "#dbeafe",
|
|
10554
|
+
padding: "0.25rem 0.75rem",
|
|
10555
|
+
fontSize: "0.65rem",
|
|
10556
|
+
fontWeight: 600,
|
|
10557
|
+
letterSpacing: "0.24em",
|
|
10558
|
+
textTransform: "uppercase",
|
|
10559
|
+
color: "#1447e6"
|
|
10560
|
+
},
|
|
10561
|
+
scrubber: {
|
|
10562
|
+
position: "relative",
|
|
10563
|
+
marginTop: "1rem",
|
|
10564
|
+
height: "3rem",
|
|
10565
|
+
cursor: "pointer",
|
|
10566
|
+
overflow: "hidden",
|
|
10567
|
+
borderRadius: "1rem",
|
|
10568
|
+
backgroundColor: "rgba(226, 232, 240, 0.9)",
|
|
10569
|
+
paddingInline: "0.25rem"
|
|
10570
|
+
},
|
|
10571
|
+
waveform: {
|
|
10572
|
+
pointerEvents: "none",
|
|
10573
|
+
position: "absolute",
|
|
10574
|
+
inset: 0,
|
|
10575
|
+
height: "100%",
|
|
10576
|
+
width: "100%"
|
|
10577
|
+
},
|
|
10578
|
+
fallbackTrack: { position: "absolute", inset: 0, backgroundColor: "rgba(226, 232, 240, 0.9)" },
|
|
10579
|
+
fallbackProgress: {
|
|
10580
|
+
position: "absolute",
|
|
10581
|
+
insetBlock: 0,
|
|
10582
|
+
left: 0,
|
|
10583
|
+
backgroundColor: "#155dfc"
|
|
10584
|
+
},
|
|
10585
|
+
chipsRow: {
|
|
10586
|
+
marginTop: "1rem",
|
|
10587
|
+
display: "flex",
|
|
10588
|
+
flexWrap: "wrap",
|
|
10589
|
+
alignItems: "center",
|
|
10590
|
+
gap: "0.5rem",
|
|
10591
|
+
fontSize: "0.72rem",
|
|
10592
|
+
fontWeight: 500,
|
|
10593
|
+
color: "#62748e"
|
|
10594
|
+
},
|
|
10595
|
+
chipStrong: {
|
|
10596
|
+
borderRadius: "9999px",
|
|
10597
|
+
border: "1px solid #e2e8f0",
|
|
10598
|
+
backgroundColor: "rgba(255, 255, 255, 0.85)",
|
|
10599
|
+
padding: "0.25rem 0.75rem",
|
|
10600
|
+
color: "#334155"
|
|
10601
|
+
},
|
|
10602
|
+
chipMuted: {
|
|
10603
|
+
borderRadius: "9999px",
|
|
10604
|
+
border: "1px solid #e2e8f0",
|
|
10605
|
+
backgroundColor: "rgba(255, 255, 255, 0.65)",
|
|
10606
|
+
padding: "0.25rem 0.75rem"
|
|
10607
|
+
},
|
|
10608
|
+
controls: {
|
|
10609
|
+
display: "flex",
|
|
10610
|
+
flexShrink: 0,
|
|
10611
|
+
alignItems: "center",
|
|
10612
|
+
gap: "0.5rem",
|
|
10613
|
+
alignSelf: "flex-start"
|
|
10614
|
+
},
|
|
10615
|
+
secondaryButton: {
|
|
10616
|
+
borderRadius: "9999px",
|
|
10617
|
+
border: "1px solid #cad5e2",
|
|
10618
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
10619
|
+
padding: "0.5rem 1rem",
|
|
10620
|
+
fontSize: "1rem",
|
|
10621
|
+
fontWeight: 600,
|
|
10622
|
+
color: "#334155",
|
|
10623
|
+
boxShadow: "0 1px 3px rgba(15, 23, 42, 0.08)",
|
|
10624
|
+
cursor: "pointer"
|
|
10625
|
+
},
|
|
10626
|
+
refreshButton: {
|
|
10627
|
+
borderRadius: "9999px",
|
|
10628
|
+
border: "1px solid #cad5e2",
|
|
10629
|
+
backgroundColor: "rgba(255, 255, 255, 0.75)",
|
|
10630
|
+
padding: "0.5rem 1rem",
|
|
10631
|
+
fontSize: "0.875rem",
|
|
10632
|
+
fontWeight: 600,
|
|
10633
|
+
color: "#334155",
|
|
10634
|
+
cursor: "pointer"
|
|
10635
|
+
},
|
|
10636
|
+
disabled: { cursor: "not-allowed", opacity: 0.6 }
|
|
10637
|
+
};
|
|
10638
|
+
function NarrationPlayer({
|
|
10639
|
+
audio,
|
|
10640
|
+
className,
|
|
10641
|
+
providerLabel = "ScaleMule",
|
|
10642
|
+
narrationLabel = "AI-Narrated",
|
|
10643
|
+
refreshing = false,
|
|
10644
|
+
onRefresh,
|
|
10645
|
+
showRefreshButton = false,
|
|
10646
|
+
onPlaybackError
|
|
10647
|
+
}) {
|
|
10648
|
+
const audioRef = React.useRef(null);
|
|
10649
|
+
const scrubRef = React.useRef(null);
|
|
10650
|
+
const pendingResumeRef = React.useRef(false);
|
|
10651
|
+
const pendingResumeTimeRef = React.useRef(null);
|
|
10652
|
+
const recoveredUrlRef = React.useRef(null);
|
|
10653
|
+
const [playing, setPlaying] = React.useState(false);
|
|
10654
|
+
const [currentTime, setCurrentTime] = React.useState(0);
|
|
10655
|
+
const [duration, setDuration] = React.useState(
|
|
10656
|
+
audio.duration_ms ? audio.duration_ms / 1e3 : 0
|
|
10657
|
+
);
|
|
10658
|
+
const [speed, setSpeed] = React.useState(1.5);
|
|
10659
|
+
const peaks = React.useMemo(() => {
|
|
10660
|
+
if (!Array.isArray(audio.waveform_peaks)) return [];
|
|
10661
|
+
const sampled = audio.waveform_peaks.filter((_, index) => index % 3 === 0).slice(0, 220);
|
|
10662
|
+
return sampled.length > 0 ? sampled : audio.waveform_peaks.slice(0, 220);
|
|
10663
|
+
}, [audio.waveform_peaks]);
|
|
10664
|
+
React.useEffect(() => {
|
|
10665
|
+
const element = audioRef.current;
|
|
10666
|
+
if (!element) return;
|
|
10667
|
+
recoveredUrlRef.current = null;
|
|
10668
|
+
const resumeTime = pendingResumeTimeRef.current;
|
|
10669
|
+
setCurrentTime(resumeTime ?? 0);
|
|
10670
|
+
setPlaying(false);
|
|
10671
|
+
element.pause();
|
|
10672
|
+
try {
|
|
10673
|
+
element.currentTime = resumeTime ?? 0;
|
|
10674
|
+
} catch {
|
|
10675
|
+
element.currentTime = 0;
|
|
10676
|
+
}
|
|
10677
|
+
if (pendingResumeRef.current) {
|
|
10678
|
+
pendingResumeRef.current = false;
|
|
10679
|
+
pendingResumeTimeRef.current = null;
|
|
10680
|
+
element.play().catch(() => {
|
|
10681
|
+
setPlaying(false);
|
|
10682
|
+
onPlaybackError?.();
|
|
10683
|
+
});
|
|
10684
|
+
return;
|
|
10685
|
+
}
|
|
10686
|
+
pendingResumeTimeRef.current = null;
|
|
10687
|
+
}, [audio.url]);
|
|
10688
|
+
React.useEffect(() => {
|
|
10689
|
+
const element = audioRef.current;
|
|
10690
|
+
if (!element) return;
|
|
10691
|
+
element.playbackRate = speed;
|
|
10692
|
+
element.preservesPitch = true;
|
|
10693
|
+
}, [speed]);
|
|
10694
|
+
function isUrlExpiringSoon() {
|
|
10695
|
+
if (!audio.expires_at) return false;
|
|
10696
|
+
const expiresAtMs = Date.parse(audio.expires_at);
|
|
10697
|
+
if (!Number.isFinite(expiresAtMs)) return false;
|
|
10698
|
+
return expiresAtMs - Date.now() <= EXPIRY_REFRESH_WINDOW_MS;
|
|
10699
|
+
}
|
|
10700
|
+
function seekToTime(nextTime) {
|
|
10701
|
+
const element = audioRef.current;
|
|
10702
|
+
if (!element || !duration) return;
|
|
10703
|
+
const clampedTime = Math.max(0, Math.min(duration, nextTime));
|
|
10704
|
+
element.currentTime = clampedTime;
|
|
10705
|
+
setCurrentTime(clampedTime);
|
|
10706
|
+
}
|
|
10707
|
+
async function refreshAndResume() {
|
|
10708
|
+
if (!onRefresh || refreshing) return false;
|
|
10709
|
+
if (recoveredUrlRef.current === (audio.url ?? null)) return false;
|
|
10710
|
+
recoveredUrlRef.current = audio.url ?? null;
|
|
10711
|
+
pendingResumeRef.current = true;
|
|
10712
|
+
pendingResumeTimeRef.current = currentTime;
|
|
10713
|
+
try {
|
|
10714
|
+
await Promise.resolve(onRefresh());
|
|
10715
|
+
return true;
|
|
10716
|
+
} catch {
|
|
10717
|
+
recoveredUrlRef.current = null;
|
|
10718
|
+
pendingResumeRef.current = false;
|
|
10719
|
+
pendingResumeTimeRef.current = null;
|
|
10720
|
+
onPlaybackError?.();
|
|
10721
|
+
return false;
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
function togglePlayback() {
|
|
10725
|
+
const element = audioRef.current;
|
|
10726
|
+
if (!element) return;
|
|
10727
|
+
if (element.paused) {
|
|
10728
|
+
if (refreshing) {
|
|
10729
|
+
pendingResumeRef.current = true;
|
|
10730
|
+
pendingResumeTimeRef.current = currentTime;
|
|
10731
|
+
return;
|
|
10732
|
+
}
|
|
10733
|
+
if (onRefresh && isUrlExpiringSoon()) {
|
|
10734
|
+
void refreshAndResume();
|
|
10735
|
+
return;
|
|
10736
|
+
}
|
|
10737
|
+
element.play().catch(() => {
|
|
10738
|
+
setPlaying(false);
|
|
10739
|
+
onPlaybackError?.();
|
|
10740
|
+
});
|
|
10741
|
+
return;
|
|
10742
|
+
}
|
|
10743
|
+
element.pause();
|
|
10744
|
+
}
|
|
10745
|
+
function seekTo(clientX) {
|
|
10746
|
+
const scrubber = scrubRef.current;
|
|
10747
|
+
if (!scrubber || !duration) return;
|
|
10748
|
+
const bounds = scrubber.getBoundingClientRect();
|
|
10749
|
+
const ratio = Math.max(0, Math.min(1, (clientX - bounds.left) / bounds.width));
|
|
10750
|
+
seekToTime(ratio * duration);
|
|
10751
|
+
}
|
|
10752
|
+
function handleScrubberKeyDown(event) {
|
|
10753
|
+
if (!duration) return;
|
|
10754
|
+
const step = Math.max(5, duration / 20);
|
|
10755
|
+
switch (event.key) {
|
|
10756
|
+
case "ArrowLeft":
|
|
10757
|
+
case "ArrowDown":
|
|
10758
|
+
event.preventDefault();
|
|
10759
|
+
seekToTime(currentTime - step);
|
|
10760
|
+
break;
|
|
10761
|
+
case "ArrowRight":
|
|
10762
|
+
case "ArrowUp":
|
|
10763
|
+
event.preventDefault();
|
|
10764
|
+
seekToTime(currentTime + step);
|
|
10765
|
+
break;
|
|
10766
|
+
case "Home":
|
|
10767
|
+
event.preventDefault();
|
|
10768
|
+
seekToTime(0);
|
|
10769
|
+
break;
|
|
10770
|
+
case "End":
|
|
10771
|
+
event.preventDefault();
|
|
10772
|
+
seekToTime(duration);
|
|
10773
|
+
break;
|
|
10774
|
+
}
|
|
10775
|
+
}
|
|
10776
|
+
function handleManualRefresh() {
|
|
10777
|
+
if (!onRefresh || refreshing) return;
|
|
10778
|
+
Promise.resolve(onRefresh()).catch(() => {
|
|
10779
|
+
onPlaybackError?.();
|
|
10780
|
+
});
|
|
10781
|
+
}
|
|
10782
|
+
const progress = duration > 0 ? Math.min(1, currentTime / duration) : 0;
|
|
10783
|
+
const expiresLabel = audio.expires_at ? new Date(audio.expires_at).toLocaleTimeString([], {
|
|
10784
|
+
hour: "numeric",
|
|
10785
|
+
minute: "2-digit"
|
|
10786
|
+
}) : null;
|
|
10787
|
+
if (!audio.url) return null;
|
|
10788
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles.root, children: [
|
|
10789
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10790
|
+
"audio",
|
|
10791
|
+
{
|
|
10792
|
+
ref: audioRef,
|
|
10793
|
+
src: audio.url ?? void 0,
|
|
10794
|
+
preload: "metadata",
|
|
10795
|
+
onLoadedMetadata: (event) => {
|
|
10796
|
+
setDuration(event.currentTarget.duration || 0);
|
|
10797
|
+
},
|
|
10798
|
+
onTimeUpdate: (event) => {
|
|
10799
|
+
setCurrentTime(event.currentTarget.currentTime);
|
|
10800
|
+
},
|
|
10801
|
+
onPlay: () => setPlaying(true),
|
|
10802
|
+
onPause: () => setPlaying(false),
|
|
10803
|
+
onEnded: () => setPlaying(false),
|
|
10804
|
+
onError: () => {
|
|
10805
|
+
if (onRefresh) {
|
|
10806
|
+
void refreshAndResume().then((didRefresh) => {
|
|
10807
|
+
if (!didRefresh) onPlaybackError?.();
|
|
10808
|
+
});
|
|
10809
|
+
return;
|
|
10810
|
+
}
|
|
10811
|
+
onPlaybackError?.();
|
|
10812
|
+
}
|
|
10813
|
+
}
|
|
10814
|
+
),
|
|
10815
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.layout, children: [
|
|
10816
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10817
|
+
"button",
|
|
10818
|
+
{
|
|
10819
|
+
type: "button",
|
|
10820
|
+
onClick: togglePlayback,
|
|
10821
|
+
"aria-label": playing ? "Pause narration" : "Play narration",
|
|
10822
|
+
style: styles.playButton,
|
|
10823
|
+
children: playing ? /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", style: styles.playIcon, "aria-hidden": "true", children: [
|
|
10824
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
|
|
10825
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
|
|
10826
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
10827
|
+
"svg",
|
|
10828
|
+
{
|
|
10829
|
+
viewBox: "0 0 24 24",
|
|
10830
|
+
style: { ...styles.playIcon, ...styles.playIconOffset },
|
|
10831
|
+
"aria-hidden": "true",
|
|
10832
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" })
|
|
10833
|
+
}
|
|
10834
|
+
)
|
|
10835
|
+
}
|
|
10836
|
+
),
|
|
10837
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.content, children: [
|
|
10838
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaRow, children: [
|
|
10839
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(currentTime / speed) }),
|
|
10840
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaRight, children: [
|
|
10841
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.badge, children: narrationLabel }),
|
|
10842
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration / speed) })
|
|
10843
|
+
] })
|
|
10844
|
+
] }),
|
|
10845
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10846
|
+
"div",
|
|
10847
|
+
{
|
|
10848
|
+
ref: scrubRef,
|
|
10849
|
+
role: "slider",
|
|
10850
|
+
"aria-label": "Narration progress",
|
|
10851
|
+
"aria-valuemin": 0,
|
|
10852
|
+
"aria-valuemax": Math.max(1, Math.floor(duration)),
|
|
10853
|
+
"aria-valuenow": Math.floor(currentTime),
|
|
10854
|
+
"aria-valuetext": `${formatTime(currentTime / speed)} of ${formatTime(duration / speed)}`,
|
|
10855
|
+
tabIndex: 0,
|
|
10856
|
+
onClick: (event) => seekTo(event.clientX),
|
|
10857
|
+
onKeyDown: handleScrubberKeyDown,
|
|
10858
|
+
style: styles.scrubber,
|
|
10859
|
+
children: peaks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
10860
|
+
"svg",
|
|
10861
|
+
{
|
|
10862
|
+
viewBox: `0 0 ${peaks.length} 40`,
|
|
10863
|
+
preserveAspectRatio: "none",
|
|
10864
|
+
style: styles.waveform,
|
|
10865
|
+
children: peaks.map((peak, index) => {
|
|
10866
|
+
const height = Math.max(2, Math.round(Math.abs(peak) * 34));
|
|
10867
|
+
const y = (40 - height) / 2;
|
|
10868
|
+
const filled = index / peaks.length < progress;
|
|
10869
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
10870
|
+
"rect",
|
|
10871
|
+
{
|
|
10872
|
+
x: index,
|
|
10873
|
+
y,
|
|
10874
|
+
width: 0.82,
|
|
10875
|
+
height,
|
|
10876
|
+
rx: 0.4,
|
|
10877
|
+
fill: filled ? "#155dfc" : "#90c5ff"
|
|
10878
|
+
},
|
|
10879
|
+
`${audio.id}-${index}`
|
|
10880
|
+
);
|
|
10881
|
+
})
|
|
10882
|
+
}
|
|
10883
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10884
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.fallbackTrack }),
|
|
10885
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...styles.fallbackProgress, width: `${progress * 100}%` } })
|
|
10886
|
+
] })
|
|
10887
|
+
}
|
|
10888
|
+
),
|
|
10889
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.chipsRow, children: [
|
|
10890
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipStrong, children: [
|
|
10891
|
+
"Provider: ",
|
|
10892
|
+
providerLabel
|
|
10893
|
+
] }),
|
|
10894
|
+
duration > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipMuted, children: [
|
|
10895
|
+
"Length ",
|
|
10896
|
+
formatTime(duration / speed)
|
|
10897
|
+
] }),
|
|
10898
|
+
expiresLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipMuted, children: [
|
|
10899
|
+
"Expires ",
|
|
10900
|
+
expiresLabel
|
|
10901
|
+
] })
|
|
10902
|
+
] })
|
|
10903
|
+
] }),
|
|
10904
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.controls, children: [
|
|
10905
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10906
|
+
"button",
|
|
10907
|
+
{
|
|
10908
|
+
type: "button",
|
|
10909
|
+
onClick: () => setSpeed((currentSpeed) => getNextSpeed(currentSpeed)),
|
|
10910
|
+
style: styles.secondaryButton,
|
|
10911
|
+
children: [
|
|
10912
|
+
speed,
|
|
10913
|
+
"\xD7"
|
|
10914
|
+
]
|
|
10915
|
+
}
|
|
10916
|
+
),
|
|
10917
|
+
onRefresh && showRefreshButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
10918
|
+
"button",
|
|
10919
|
+
{
|
|
10920
|
+
type: "button",
|
|
10921
|
+
onClick: handleManualRefresh,
|
|
10922
|
+
disabled: refreshing,
|
|
10923
|
+
style: refreshing ? { ...styles.refreshButton, ...styles.disabled } : styles.refreshButton,
|
|
10924
|
+
children: refreshing ? "Refreshing\u2026" : "Refresh"
|
|
10925
|
+
}
|
|
10926
|
+
)
|
|
10927
|
+
] })
|
|
10928
|
+
] })
|
|
10929
|
+
] });
|
|
10930
|
+
}
|
|
9728
10931
|
function useMedia() {
|
|
9729
10932
|
const { media: rawMedia, mediaPolicy: providerDefaultPolicy } = useScaleMule();
|
|
9730
10933
|
const media = rawMedia;
|
|
@@ -10304,16 +11507,14 @@ function setStorageItem(storage, key, value) {
|
|
|
10304
11507
|
} catch {
|
|
10305
11508
|
}
|
|
10306
11509
|
}
|
|
10307
|
-
function
|
|
11510
|
+
function getOrCreateSessionId(sessionStorageKey) {
|
|
10308
11511
|
if (typeof window === "undefined") {
|
|
10309
11512
|
return {
|
|
10310
11513
|
sessionId: null,
|
|
10311
|
-
anonymousId: null,
|
|
10312
11514
|
sessionStart: Date.now()
|
|
10313
11515
|
};
|
|
10314
11516
|
}
|
|
10315
11517
|
const storage = typeof sessionStorage !== "undefined" ? sessionStorage : void 0;
|
|
10316
|
-
const localStorage_ = typeof localStorage !== "undefined" ? localStorage : void 0;
|
|
10317
11518
|
let sessionId = getStorageItem(storage, sessionStorageKey);
|
|
10318
11519
|
let sessionStartStr = getStorageItem(storage, SESSION_START_KEY);
|
|
10319
11520
|
let sessionStart;
|
|
@@ -10325,12 +11526,7 @@ function getOrCreateIds(sessionStorageKey, anonymousStorageKey) {
|
|
|
10325
11526
|
} else {
|
|
10326
11527
|
sessionStart = parseInt(sessionStartStr, 10);
|
|
10327
11528
|
}
|
|
10328
|
-
|
|
10329
|
-
if (!anonymousId) {
|
|
10330
|
-
anonymousId = generateUUID();
|
|
10331
|
-
setStorageItem(localStorage_, anonymousStorageKey, anonymousId);
|
|
10332
|
-
}
|
|
10333
|
-
return { sessionId, anonymousId, sessionStart };
|
|
11529
|
+
return { sessionId, sessionStart };
|
|
10334
11530
|
}
|
|
10335
11531
|
function parseUtmParams() {
|
|
10336
11532
|
if (typeof window === "undefined") return null;
|
|
@@ -10441,12 +11637,27 @@ function useAnalytics(options = {}) {
|
|
|
10441
11637
|
autoCapturUtmParams,
|
|
10442
11638
|
autoGenerateSessionId = true,
|
|
10443
11639
|
sessionStorageKey = "sm_session_id",
|
|
10444
|
-
anonymousStorageKey
|
|
11640
|
+
// anonymousStorageKey is intentionally ignored — see UseAnalyticsOptions.
|
|
11641
|
+
// Anonymous ID now lives on the ScaleMule client under the canonical
|
|
11642
|
+
// `scalemule_anonymous_id` key (PR consolidating the contract). Reading
|
|
11643
|
+
// an override here would re-fragment a visitor's identity between
|
|
11644
|
+
// analytics events and auth request headers.
|
|
10445
11645
|
useV2 = true,
|
|
10446
11646
|
eventDedupMs = DEFAULT_EVENT_DEDUP_MS
|
|
10447
11647
|
} = options;
|
|
10448
11648
|
const shouldAutoCaptureUtmParams = autoCaptureUtmParams ?? autoCapturUtmParams ?? true;
|
|
10449
|
-
const { client, user, analyticsProxyUrl, publishableKey, gatewayUrl } = useScaleMule();
|
|
11649
|
+
const { client, user, analyticsProxyUrl, publishableKey, apiKey, gatewayUrl } = useScaleMule();
|
|
11650
|
+
const proxyModeMisconfigWarnedRef = React.useRef(false);
|
|
11651
|
+
React.useEffect(() => {
|
|
11652
|
+
if (process.env.NODE_ENV === "production") return;
|
|
11653
|
+
if (proxyModeMisconfigWarnedRef.current) return;
|
|
11654
|
+
if (apiKey === "proxy-mode" && !analyticsProxyUrl && !publishableKey) {
|
|
11655
|
+
proxyModeMisconfigWarnedRef.current = true;
|
|
11656
|
+
console.warn(
|
|
11657
|
+
'[ScaleMule] useAnalytics: apiKey="proxy-mode" but neither `analyticsProxyUrl` nor `publishableKey` is configured. Every trackEvent / trackPageView will silently 401 against the gateway. Add an analytics proxy route (createAnalyticsRoutes({ simpleProxy: true })) and pass `analyticsProxyUrl` to <ScaleMuleProvider>. See @scalemule/nextjs README \u2192 Proxy Mode.'
|
|
11658
|
+
);
|
|
11659
|
+
}
|
|
11660
|
+
}, [apiKey, analyticsProxyUrl, publishableKey]);
|
|
10450
11661
|
const [loading, setLoading] = React.useState(false);
|
|
10451
11662
|
const [error, setError] = React.useState(null);
|
|
10452
11663
|
const [utmParams, setUtmParams] = React.useState(null);
|
|
@@ -10467,14 +11678,13 @@ function useAnalytics(options = {}) {
|
|
|
10467
11678
|
idsReadyRef.current = true;
|
|
10468
11679
|
return;
|
|
10469
11680
|
}
|
|
10470
|
-
const ids =
|
|
11681
|
+
const ids = getOrCreateSessionId(sessionStorageKey);
|
|
10471
11682
|
sessionIdRef.current = ids.sessionId;
|
|
10472
|
-
anonymousIdRef.current = ids.anonymousId;
|
|
10473
11683
|
sessionStartRef.current = ids.sessionStart;
|
|
10474
|
-
idsReadyRef.current = true;
|
|
10475
11684
|
setSessionId(ids.sessionId);
|
|
10476
|
-
|
|
10477
|
-
|
|
11685
|
+
let cancelled = false;
|
|
11686
|
+
const flushQueuedEvents = () => {
|
|
11687
|
+
if (eventQueue.current.length === 0) return;
|
|
10478
11688
|
const queue = eventQueue.current;
|
|
10479
11689
|
eventQueue.current = [];
|
|
10480
11690
|
setTimeout(() => {
|
|
@@ -10482,8 +11692,22 @@ function useAnalytics(options = {}) {
|
|
|
10482
11692
|
sendEventRef.current?.(event);
|
|
10483
11693
|
}
|
|
10484
11694
|
}, 0);
|
|
10485
|
-
}
|
|
10486
|
-
|
|
11695
|
+
};
|
|
11696
|
+
client.ensureAnonymousId().then((anonId) => {
|
|
11697
|
+
if (cancelled) return;
|
|
11698
|
+
anonymousIdRef.current = anonId;
|
|
11699
|
+
setAnonymousId(anonId);
|
|
11700
|
+
idsReadyRef.current = true;
|
|
11701
|
+
flushQueuedEvents();
|
|
11702
|
+
}).catch(() => {
|
|
11703
|
+
if (cancelled) return;
|
|
11704
|
+
idsReadyRef.current = true;
|
|
11705
|
+
flushQueuedEvents();
|
|
11706
|
+
});
|
|
11707
|
+
return () => {
|
|
11708
|
+
cancelled = true;
|
|
11709
|
+
};
|
|
11710
|
+
}, [autoGenerateSessionId, sessionStorageKey, client]);
|
|
10487
11711
|
React.useEffect(() => {
|
|
10488
11712
|
if (typeof window === "undefined") return;
|
|
10489
11713
|
if (shouldAutoCaptureUtmParams) {
|
|
@@ -11214,7 +12438,7 @@ function useVote({
|
|
|
11214
12438
|
const [state, setState] = React.useState(initialState ?? ZERO);
|
|
11215
12439
|
const [isLoading, setIsLoading] = React.useState(!initialState && enabled);
|
|
11216
12440
|
const [error, setError] = React.useState(null);
|
|
11217
|
-
const
|
|
12441
|
+
const inFlight2 = React.useRef(null);
|
|
11218
12442
|
const path = `/v1/social/${encodeURIComponent(targetType)}/${encodeURIComponent(targetId)}/vote`;
|
|
11219
12443
|
const refetch = React.useCallback(async () => {
|
|
11220
12444
|
if (!enabled) return;
|
|
@@ -11249,10 +12473,10 @@ function useVote({
|
|
|
11249
12473
|
setError(e);
|
|
11250
12474
|
}
|
|
11251
12475
|
};
|
|
11252
|
-
const p =
|
|
11253
|
-
|
|
12476
|
+
const p = inFlight2.current ? inFlight2.current.then(send) : send();
|
|
12477
|
+
inFlight2.current = p;
|
|
11254
12478
|
await p;
|
|
11255
|
-
if (
|
|
12479
|
+
if (inFlight2.current === p) inFlight2.current = null;
|
|
11256
12480
|
},
|
|
11257
12481
|
[client, path, state]
|
|
11258
12482
|
);
|
|
@@ -12064,6 +13288,7 @@ Object.defineProperty(exports, "createMoneyClient", {
|
|
|
12064
13288
|
get: function () { return money.createMoneyClient; }
|
|
12065
13289
|
});
|
|
12066
13290
|
exports.FeedbackWidget = FeedbackWidget;
|
|
13291
|
+
exports.NarrationPlayer = NarrationPlayer;
|
|
12067
13292
|
exports.ScaleMuleApiError = ScaleMuleApiError;
|
|
12068
13293
|
exports.ScaleMuleClient = ScaleMuleClient2;
|
|
12069
13294
|
exports.ScaleMuleMedia = ScaleMuleMedia;
|