@wix/himalaya-cli 0.1387.0 → 0.1389.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +24 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-core.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/video/src/display.d.ts +66 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/video/src/index.d.ts +185 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +57 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +57 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-video.mjs +324 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/package.json +4 -0
- package/dist/cli.mjs +10 -5
- package/guides/iap-screen-adoption.md +53 -1
- package/guides/importable-sdks.md +1 -0
- package/guides/sdk-reference.json +261 -2
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +193 -1
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/beauty-salon/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/beauty-salon-owner/config.ts +11 -0
- package/templates/beauty-salon-owner/dev/index.ts +540 -0
- package/templates/beauty-salon-owner/himalaya.content.json +6 -0
- package/templates/beauty-salon-owner/platforms.json +2 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/branded-lite/dev/index.ts +1212 -54
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/branded-lite/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-video/index.ts +23 -32
- package/templates/branded-lite/tier5-src/himi-branded-lite-videos/index.ts +44 -38
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/community-club/dev/index.ts +193 -1
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/community-club/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/community-club/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/community-club/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/community-club-owner/config.ts +11 -0
- package/templates/community-club-owner/dev/index.ts +540 -0
- package/templates/community-club-owner/himalaya.content.json +6 -0
- package/templates/community-club-owner/platforms.json +2 -1
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/events-meetups/dev/index.ts +193 -1
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/events-meetups/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/events-meetups-owner/config.ts +11 -0
- package/templates/events-meetups-owner/dev/index.ts +540 -0
- package/templates/events-meetups-owner/himalaya.content.json +6 -0
- package/templates/events-meetups-owner/platforms.json +2 -1
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/gym-fitness/dev/index.ts +193 -1
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/gym-fitness/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/gym-fitness-owner/config.ts +11 -0
- package/templates/gym-fitness-owner/dev/index.ts +540 -0
- package/templates/gym-fitness-owner/himalaya.content.json +6 -0
- package/templates/gym-fitness-owner/platforms.json +2 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/nonprofit-cause/dev/index.ts +193 -1
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/nonprofit-cause/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/nonprofit-cause-owner/config.ts +11 -0
- package/templates/nonprofit-cause-owner/dev/index.ts +540 -0
- package/templates/nonprofit-cause-owner/himalaya.content.json +6 -0
- package/templates/nonprofit-cause-owner/platforms.json +2 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/owner-lite/config.ts +11 -0
- package/templates/owner-lite/dev/index.ts +540 -0
- package/templates/owner-lite/himalaya.content.json +6 -0
- package/templates/owner-lite/platforms.json +2 -1
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/podcast-creator/dev/index.ts +193 -1
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/podcast-creator/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/podcast-creator-owner/config.ts +11 -0
- package/templates/podcast-creator-owner/dev/index.ts +540 -0
- package/templates/podcast-creator-owner/himalaya.content.json +6 -0
- package/templates/podcast-creator-owner/platforms.json +2 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/portfolio-creative/dev/index.ts +193 -1
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/portfolio-creative/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/portfolio-creative-owner/config.ts +11 -0
- package/templates/portfolio-creative-owner/dev/index.ts +540 -0
- package/templates/portfolio-creative-owner/himalaya.content.json +6 -0
- package/templates/portfolio-creative-owner/platforms.json +2 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/restaurant/dev/index.ts +193 -1
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/restaurant/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/restaurant/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/restaurant-owner/config.ts +11 -0
- package/templates/restaurant-owner/dev/index.ts +540 -0
- package/templates/restaurant-owner/himalaya.content.json +6 -0
- package/templates/restaurant-owner/platforms.json +2 -1
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/retail-store/dev/index.ts +193 -1
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/retail-store/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/retail-store/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/retail-store-owner/config.ts +11 -0
- package/templates/retail-store-owner/dev/index.ts +540 -0
- package/templates/retail-store-owner/himalaya.content.json +6 -0
- package/templates/retail-store-owner/platforms.json +2 -1
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/yoga-studio/dev/index.ts +193 -1
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +84 -3
- package/templates/yoga-studio/tier5-src/_shared/video-caption.ts +21 -0
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +31 -1
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +36 -1
- package/templates/yoga-studio-owner/config.ts +11 -0
- package/templates/yoga-studio-owner/dev/index.ts +540 -0
- package/templates/yoga-studio-owner/himalaya.content.json +6 -0
- package/templates/yoga-studio-owner/platforms.json +2 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
- package/templates/branded-lite/tier5-src/_shared/vod.ts +0 -71
|
@@ -98,7 +98,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
98
98
|
himiBrandedBadges: { rows: 'badges', title: 'name', subtitle: 'description', view: 'grid' },
|
|
99
99
|
himiBrandedDonations: { rows: 'campaigns', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Donate' } },
|
|
100
100
|
himiBrandedGiftCards: { rows: 'cards', title: 'name', subtitle: 'description', view: 'grid' },
|
|
101
|
-
himiBrandedVideos: { rows: 'channels', title: 'title', subtitle: '
|
|
101
|
+
himiBrandedVideos: { rows: 'channels', title: 'title', subtitle: 'subtitle', image: 'coverUrl', view: 'grid', searchPlaceholder: 'Search channels', countNoun: ['channel', 'channels'] },
|
|
102
102
|
himiBrandedPodcasts: { rows: 'episodes', title: 'title', subtitle: 'subtitle', image: 'image', metadata: ['progressLabel'], view: 'list' },
|
|
103
103
|
himiBrandedForms: { rows: 'forms', title: 'name', subtitle: 'description', view: 'list' },
|
|
104
104
|
himiBrandedGalleries: { rows: 'galleries', title: 'name', subtitle: 'description', image: 'image', view: 'grid' },
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// `@himalaya/iap-checkout` → createIapCommerce), shared by every screen that shows or sells an
|
|
3
3
|
// IAP-backed item. Mapping, Apple prices, payment guards, the host scope and the sale live in the
|
|
4
4
|
// SDK; this only supplies the copy and the hydration reset.
|
|
5
|
-
import type
|
|
6
|
-
import type { IapPurchaseCopy } from "@himalaya/iap-checkout";
|
|
7
|
-
import { copyParam } from "./himi-copy.js";
|
|
5
|
+
import { hostSessionFromGlobal, type IapPriceCopy } from "@himalaya/iap";
|
|
6
|
+
import type { IapCommerce, IapPurchaseCopy, IapRestoreResult } from "@himalaya/iap-checkout";
|
|
7
|
+
import { copyParam, copyTemplate } from "./himi-copy.js";
|
|
8
8
|
|
|
9
9
|
/** Price labels from the screen's copy pack (`<prefix>.priceLoadingText`, …). */
|
|
10
10
|
export function iapPriceCopy(prefix: string): IapPriceCopy {
|
|
@@ -31,6 +31,87 @@ export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** The Restore purchases control (App Review 3.1.1) a screen binds. */
|
|
35
|
+
export interface IapRestoreFields {
|
|
36
|
+
restoreHidden: boolean;
|
|
37
|
+
restoreLabel: string;
|
|
38
|
+
restoreDisabled: boolean;
|
|
39
|
+
restoreMessage: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Keep these out of hydration: a restore's answer belongs to the run that got it. */
|
|
43
|
+
export const IAP_RESTORE_KEYS = ["restoreHidden", "restoreLabel", "restoreDisabled", "restoreMessage"] as const;
|
|
44
|
+
|
|
45
|
+
/** The control before the screen knows it sells through Apple. */
|
|
46
|
+
export const NO_IAP_RESTORE: IapRestoreFields = { restoreHidden: true, restoreLabel: "", restoreDisabled: false, restoreMessage: "" };
|
|
47
|
+
|
|
48
|
+
export interface IapRestoreControl {
|
|
49
|
+
/** `sellsThroughApple` is the screen's own signal: its item's App Store route, or Account's
|
|
50
|
+
* purchase readiness. */
|
|
51
|
+
fields(sellsThroughApple: boolean): IapRestoreFields;
|
|
52
|
+
/** Runs the shared `commerce.restore()`, repainting as it starts and when it answers. A tap while
|
|
53
|
+
* one runs is ignored. */
|
|
54
|
+
run(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Copy from the screen's pack (`<prefix>.restorePurchasesCta`, …). The restore itself, and the access
|
|
58
|
+
* re-read after it, are the shared commerce's. */
|
|
59
|
+
export function createIapRestore(
|
|
60
|
+
commerce: Pick<IapCommerce, "restore">,
|
|
61
|
+
options: { prefix: string; repaint(): void; /** The session generation; defaults to the host's. */ generation?: () => number },
|
|
62
|
+
): IapRestoreControl {
|
|
63
|
+
const generation = options.generation ?? (() => hostSessionFromGlobal().current().generation);
|
|
64
|
+
let running = false;
|
|
65
|
+
let message = "";
|
|
66
|
+
let messageGeneration = -1;
|
|
67
|
+
|
|
68
|
+
function say(text: string, forGeneration: number): void {
|
|
69
|
+
message = text;
|
|
70
|
+
messageGeneration = forGeneration;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function run(): Promise<void> {
|
|
74
|
+
if (running) return;
|
|
75
|
+
running = true;
|
|
76
|
+
const startedFor = generation();
|
|
77
|
+
say(copyParam(`${options.prefix}.restoringText`, "Restoring…"), startedFor);
|
|
78
|
+
options.repaint();
|
|
79
|
+
// Stamped with the session the tap was for: an answer landing after a sign-out is never shown.
|
|
80
|
+
say(restoreMessage(await restored(commerce), options.prefix), startedFor);
|
|
81
|
+
running = false;
|
|
82
|
+
options.repaint();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
fields: (sellsThroughApple) => ({
|
|
87
|
+
restoreHidden: !sellsThroughApple,
|
|
88
|
+
restoreLabel: copyParam(`${options.prefix}.restorePurchasesCta`, "Restore purchases"),
|
|
89
|
+
restoreDisabled: running,
|
|
90
|
+
// The answer was for the person signed in when it ran; another session never inherits it.
|
|
91
|
+
restoreMessage: sellsThroughApple && messageGeneration === generation() ? message : "",
|
|
92
|
+
}),
|
|
93
|
+
run,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function restored(commerce: Pick<IapCommerce, "restore">): Promise<IapRestoreResult> {
|
|
98
|
+
return commerce.restore().catch(() => ({ status: "failed", code: "storeUnavailable" }) as const);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function restoreMessage(result: IapRestoreResult, prefix: string): string {
|
|
102
|
+
if (result.status === "needsSignIn") return copyParam(`${prefix}.restoreSignInText`, "Sign in to restore your purchases.");
|
|
103
|
+
if (result.status === "failed") return result.code === "sessionChanged" ? "" : copyParam(`${prefix}.restoreFailedText`, "Couldn't reach the App Store. Try again.");
|
|
104
|
+
// Every part that happened is said: a recovery never hides a refusal or a purchase still pending.
|
|
105
|
+
const parts = [
|
|
106
|
+
result.recovered === 1 ? copyParam(`${prefix}.restoredOneText`, "Restored 1 purchase.") : "",
|
|
107
|
+
result.recovered > 1 ? copyTemplate(`${prefix}.restoredManyText`, "Restored {count} purchases.", { count: result.recovered }) : "",
|
|
108
|
+
// Held unfinished because Wix refused it (another member's purchase, say).
|
|
109
|
+
result.refused > 0 ? copyParam(`${prefix}.restoreRefusedText`, "A purchase on this Apple ID couldn't be added to this account. Contact support.") : "",
|
|
110
|
+
result.pending > 0 ? copyParam(`${prefix}.restorePendingText`, "A purchase is still being confirmed. Try again in a moment.") : "",
|
|
111
|
+
].filter(Boolean);
|
|
112
|
+
return parts.length ? parts.join(" ") : copyParam(`${prefix}.restoreUpToDateText`, "Your purchases are up to date.");
|
|
113
|
+
}
|
|
114
|
+
|
|
34
115
|
/** What `notHydrated` does, for a runtime that ignores the option (a binary older than it, still
|
|
35
116
|
* served new bundles): put the listed keys back to their declared values before any action runs,
|
|
36
117
|
* so a persisted price or an enabled purchase action never paints as this run's. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The video carousel's two looks: the caption ON the cover (a brand label chip over the
|
|
2
|
+
// image) or UNDER it. Every slide carries both placements; the descriptor's
|
|
3
|
+
// `videoCaption` appear param picks one, and these flags hide the other
|
|
4
|
+
// (`hiddenWhen` hides on TRUE). So a screen switches look by changing ONE param,
|
|
5
|
+
// with no second carousel and no change to the slide tree.
|
|
6
|
+
export type VideoCaption = "overlay" | "below";
|
|
7
|
+
|
|
8
|
+
/** The placement a param asks for. Anything but "below" is the default, "overlay". */
|
|
9
|
+
export function videoCaptionOf(value: unknown): VideoCaption {
|
|
10
|
+
return value === "below" ? "below" : "overlay";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CaptionFlags {
|
|
14
|
+
captionOnImage: boolean;
|
|
15
|
+
captionBelow: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Stamp each card with the gates its slide reads (`$item.captionOnImage` / `$item.captionBelow`). */
|
|
19
|
+
export function withCaption<T extends object>(cards: T[], caption: VideoCaption): (T & CaptionFlags)[] {
|
|
20
|
+
return cards.map((card) => ({ ...card, captionOnImage: caption === "overlay", captionBelow: caption === "below" }));
|
|
21
|
+
}
|
|
@@ -22,12 +22,18 @@ import { formatDateMedium } from "../_shared/engine-intl.js";
|
|
|
22
22
|
import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-saved.js";
|
|
23
23
|
import { readSavedIds } from "../_shared/himi-blog-saved.js";
|
|
24
24
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
25
|
+
import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
|
|
26
|
+
import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
|
|
25
27
|
|
|
26
28
|
/** Latched once the member feed is refused because of WHERE the app is running rather
|
|
27
29
|
* than because of the request — see the catch in `loadExtras`. Module scope, so it
|
|
28
30
|
* survives every refresh of this worker and is reset only by a fresh boot. */
|
|
29
31
|
let feedSurfaceRefused = false;
|
|
30
32
|
|
|
33
|
+
/** The same latch for Wix Video: its gateway is reachable from the native apps only, so a
|
|
34
|
+
* web bridge's refusal is permanent for this boot and the rail stays collapsed. */
|
|
35
|
+
let videoSurfaceRefused = false;
|
|
36
|
+
|
|
31
37
|
/** True for the one refusal that can never change while the app keeps running: the web
|
|
32
38
|
* bridge reporting that this host is not reachable from a browser at all. Deliberately
|
|
33
39
|
* narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
|
|
@@ -292,6 +298,10 @@ interface State {
|
|
|
292
298
|
noNextEvent: boolean;
|
|
293
299
|
homePrograms: ProgramCard[];
|
|
294
300
|
noHomePrograms: boolean;
|
|
301
|
+
/** The videos module's carousel — the newest ready Wix Video videos, the channels
|
|
302
|
+
* taking turns. Guarded on `videos`; same silent-fail contract. */
|
|
303
|
+
homeVideos: (WixVideoCard & CaptionFlags)[];
|
|
304
|
+
noHomeVideos: boolean;
|
|
295
305
|
feedHighlights: FeedHighlight[];
|
|
296
306
|
noFeedHighlights: boolean;
|
|
297
307
|
/** communityPulse member strip — the author directory of the SAME feed read
|
|
@@ -529,6 +539,7 @@ interface ExtrasSignal {
|
|
|
529
539
|
upcomingEvents: EventEntry[];
|
|
530
540
|
nextEventCards: NextEventCard[];
|
|
531
541
|
homePrograms: ProgramCard[];
|
|
542
|
+
homeVideos: (WixVideoCard & CaptionFlags)[];
|
|
532
543
|
feedHighlights: FeedHighlight[];
|
|
533
544
|
memberAvatars: CoachAvatar[];
|
|
534
545
|
groupCards: GroupCard[];
|
|
@@ -539,7 +550,7 @@ interface ExtrasSignal {
|
|
|
539
550
|
}
|
|
540
551
|
|
|
541
552
|
function emptyExtrasSignal(): ExtrasSignal {
|
|
542
|
-
return { upcomingEvents: [], nextEventCards: [], homePrograms: [], feedHighlights: [], memberAvatars: [], groupCards: [], announcementTitle: "", postCards: [], popularDishes: [], menuCategories: [] };
|
|
553
|
+
return { upcomingEvents: [], nextEventCards: [], homePrograms: [], homeVideos: [], feedHighlights: [], memberAvatars: [], groupCards: [], announcementTitle: "", postCards: [], popularDishes: [], menuCategories: [] };
|
|
543
554
|
}
|
|
544
555
|
|
|
545
556
|
/** Cover URL probe for a blog post — the himi-blog-feed imageUrl fallbacks
|
|
@@ -679,6 +690,18 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
679
690
|
}
|
|
680
691
|
}
|
|
681
692
|
|
|
693
|
+
/** ONE Wix Video read for the videos module. A failure collapses the module; the web
|
|
694
|
+
* bridges' refusal of the gateway also latches it off for the rest of this boot. */
|
|
695
|
+
async function fetchHomeVideos(): Promise<WixVideoCard[]> {
|
|
696
|
+
try {
|
|
697
|
+
const untitled = copyParam("home.videoFallbackTitle", "Video");
|
|
698
|
+
return (await listLatestVideos({ limit: 8 })).map((entry) => videoCard(entry, untitled));
|
|
699
|
+
} catch (error) {
|
|
700
|
+
if (isWixVideoWebRefusal(error)) videoSurfaceRefused = true;
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
682
705
|
function homeFeedText(item: WixFeedItem): string {
|
|
683
706
|
const blocks = item.content?.blocks;
|
|
684
707
|
if (Array.isArray(blocks)) {
|
|
@@ -731,6 +754,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
|
|
|
731
754
|
if (verticals.includes("programs")) {
|
|
732
755
|
jobs.push(fetchHomePrograms().then((cards) => { extras.homePrograms = cards; }));
|
|
733
756
|
}
|
|
757
|
+
if (verticals.includes("videos") && !videoSurfaceRefused) {
|
|
758
|
+
const caption = videoCaptionOf(paramValue(arg, "videoCaption"));
|
|
759
|
+
jobs.push(fetchHomeVideos().then((cards) => { extras.homeVideos = withCaption(cards, caption); }));
|
|
760
|
+
}
|
|
734
761
|
if (verticals.includes("blog")) {
|
|
735
762
|
// ONE limit-8 posts query feeds BOTH the communityPulse announcement
|
|
736
763
|
// (posts[0].title — the frozen contract) and the showcaseFirst/episodeFirst
|
|
@@ -847,6 +874,7 @@ function derive(state: State): State {
|
|
|
847
874
|
noUpcomingEvents: state.upcomingEvents.length === 0,
|
|
848
875
|
noNextEvent: state.nextEventCards.length === 0,
|
|
849
876
|
noHomePrograms: state.homePrograms.length === 0,
|
|
877
|
+
noHomeVideos: state.homeVideos.length === 0,
|
|
850
878
|
noFeedHighlights: state.feedHighlights.length === 0,
|
|
851
879
|
noMemberAvatars: state.memberAvatars.length === 0,
|
|
852
880
|
noGroupCards: state.groupCards.length === 0,
|
|
@@ -938,6 +966,8 @@ function baseState(): State {
|
|
|
938
966
|
noNextEvent: true,
|
|
939
967
|
homePrograms: [],
|
|
940
968
|
noHomePrograms: true,
|
|
969
|
+
homeVideos: [],
|
|
970
|
+
noHomeVideos: true,
|
|
941
971
|
feedHighlights: [],
|
|
942
972
|
noFeedHighlights: true,
|
|
943
973
|
memberAvatars: [],
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { defineService } from "@himalaya/state";
|
|
2
2
|
import { LOADING, loaded, loadFailed, needsSignIn, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
3
3
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
canWatchVideo, getVideo, getVideoStreamUrl, isHostedVideo, isVideoProcessing, listChannelVideos, videoCard, youtubeVideoId,
|
|
6
|
+
} from "@himalaya/wix-video";
|
|
5
7
|
import type { MediaItem } from "@himalaya/core";
|
|
6
8
|
|
|
9
|
+
interface VideoRow { id: string; channelId: string; title: string; subtitle: string; meta: string; coverUrl: string; hasCover: boolean; noCover: boolean }
|
|
7
10
|
interface S extends LoadFlags {
|
|
8
11
|
id: string; channelId: string;
|
|
9
|
-
title: string; description: string; videos:
|
|
12
|
+
title: string; description: string; videos: VideoRow[];
|
|
10
13
|
items: MediaItem[]; hidePlayer: boolean; hideNotFound: boolean; noEntity: boolean; hideRefresh: boolean; emptyText: string; message: string;
|
|
11
14
|
playbackState: "loading" | "ready" | "processing" | "denied" | "unavailable" | "error";
|
|
12
15
|
}
|
|
@@ -15,17 +18,6 @@ function input(key: string): string {
|
|
|
15
18
|
const value = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.[key];
|
|
16
19
|
return typeof value === "string" ? value : "";
|
|
17
20
|
}
|
|
18
|
-
const CHANNEL_QUERY = `query VodChannelVideos($channelId: ID!, $cursor: String) {
|
|
19
|
-
channel(filter: {id: $channelId}) { ${CHANNEL_ACCESS_FIELDS}
|
|
20
|
-
videos(sort: CUSTOM_DESC, cursor: $cursor) { cursor entities { ${VIDEO_FIELDS} } }
|
|
21
|
-
}
|
|
22
|
-
}`;
|
|
23
|
-
const VIDEO_QUERY = `query VodVideo($channelId: ID!, $videoId: ID!) {
|
|
24
|
-
channel(filter: {id: $channelId}) { ${CHANNEL_ACCESS_FIELDS} video(filter: {id: $videoId}) { ${VIDEO_FIELDS} } }
|
|
25
|
-
}`;
|
|
26
|
-
const PLAY_QUERY = `query VodVideoPlay($channelId: ID!, $videoId: ID!) {
|
|
27
|
-
channel(filter: {id: $channelId}) { video(filter: {id: $videoId}) { play(type: CONTENT) { entity { hls } } } }
|
|
28
|
-
}`;
|
|
29
21
|
let generation = 0;
|
|
30
22
|
const initial: S = { ...LOADING, id: "", channelId: "", title: "", description: "", videos: [], items: [], hidePlayer: true, hideNotFound: true, noEntity: false, hideRefresh: false, emptyText: "", message: "", playbackState: "loading" };
|
|
31
23
|
function unavailable(s: S, kind: S["playbackState"], message: string): S {
|
|
@@ -47,35 +39,34 @@ const svc = defineService<S, { reset: (s: S) => S; load: (s: S, arg?: unknown) =
|
|
|
47
39
|
const channelScreen = input("screenId") === "himi_video_channel";
|
|
48
40
|
if (!id || (!channelScreen && !channelId)) next = unavailable(next, "unavailable", copyParam("video.unavailableText", "This video or channel is unavailable."));
|
|
49
41
|
else if (channelScreen) {
|
|
50
|
-
const videos = await
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
const { channel, videos } = await listChannelVideos(id);
|
|
43
|
+
next.title = channel.title ?? "";
|
|
44
|
+
const untitled = copyParam("video.untitled", "Video");
|
|
45
|
+
next = { ...next, ...loaded("videos", videos.map(v => {
|
|
46
|
+
const card = videoCard({ channel, video: v }, untitled);
|
|
47
|
+
return { id: v.id, channelId: id, title: card.title, meta: card.duration, coverUrl: card.coverUrl, hasCover: card.hasCover, noCover: card.noCover, subtitle:
|
|
48
|
+
v.content?.status !== "READY" ? copyParam("video.processingText", "Video is processing.") : "" };
|
|
49
|
+
}), COPY), playbackState: "ready" };
|
|
58
50
|
} else {
|
|
59
|
-
const data = await
|
|
60
|
-
const channel = data
|
|
61
|
-
const video =
|
|
62
|
-
if (!video || video.status !== "VISIBLE") next = unavailable(next, "unavailable", copyParam("video.unavailableText", "This video is unavailable."));
|
|
51
|
+
const data = await getVideo(channelId, id);
|
|
52
|
+
const channel = data?.channel;
|
|
53
|
+
const video = data?.video;
|
|
54
|
+
if (!channel || !video || video.status !== "VISIBLE") next = unavailable(next, "unavailable", copyParam("video.unavailableText", "This video is unavailable."));
|
|
63
55
|
else {
|
|
64
56
|
next.title = video.title ?? "";
|
|
65
57
|
next.description = video.description ?? "";
|
|
66
|
-
if (video.content?.status !== "READY") next =
|
|
58
|
+
if (video.content?.status !== "READY") next = isVideoProcessing(video) ?
|
|
67
59
|
unavailable(next, "processing", copyParam("video.processingText", "Video is processing. Try again later.")) :
|
|
68
60
|
unavailable(next, "unavailable", copyParam("video.unavailableText", "This video is unavailable."));
|
|
69
|
-
else if (!
|
|
61
|
+
else if (!canWatchVideo(channel, video)) next = unavailable(next, "denied", copyParam("video.deniedText", "Your current membership or purchase does not allow this video."));
|
|
70
62
|
else {
|
|
71
63
|
let item: MediaItem | undefined;
|
|
72
64
|
if (video.source === "YOUTUBE") {
|
|
73
|
-
const providerId =
|
|
65
|
+
const providerId = youtubeVideoId(video);
|
|
74
66
|
if (providerId) item = { id, kind: "video", provider: "youtube", providerId, title: next.title };
|
|
75
|
-
} else if (
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
if (typeof url === "string" && /^https:\/\//.test(url)) item = { id, kind: "video", provider: "direct", url, streamType: "hls", title: next.title };
|
|
67
|
+
} else if (isHostedVideo(video)) {
|
|
68
|
+
const url = await getVideoStreamUrl(channelId, id);
|
|
69
|
+
if (url) item = { id, kind: "video", provider: "direct", url, streamType: "hls", title: next.title };
|
|
79
70
|
}
|
|
80
71
|
next = item ? { ...next, ...loaded("videos", [], { ...COPY, empty: "" }), emptyText: "", items: [item], hidePlayer: false, playbackState: "ready" } :
|
|
81
72
|
unavailable(next, "unavailable", copyParam("video.unavailableText", "This video is unavailable."));
|
|
@@ -1,34 +1,27 @@
|
|
|
1
|
-
// himi-branded-lite-videos — branded-lite "Videos" worker (Wix Video
|
|
1
|
+
// himi-branded-lite-videos — branded-lite "Videos" worker (Wix Video).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// manage.wix.com). a failed query is an error with Retry (or Sign in), an
|
|
3
|
+
// Wix Video has NO platformized REST @wix/auto_sdk_* domain to codegen, so the data
|
|
4
|
+
// comes through the hand-written `@himalaya/wix-video` client, which reads the same
|
|
5
|
+
// Wix Video GraphQL gateway Wix's own mobile app reads, through the raw wixApi bridge
|
|
6
|
+
// (visitor/member auth). ONE paged channel query feeds both sections of the screen:
|
|
7
|
+
// the channels list, and the video carousel (the first videos of each channel, the
|
|
8
|
+
// channels taking turns). A failed query is an error with Retry (or Sign in), an
|
|
10
9
|
// empty one a real empty state; sample channels appear only in the host's explicit
|
|
11
10
|
// sample preview (_shared/branded-load-state.ts).
|
|
12
|
-
//
|
|
13
|
-
// Endpoint + query verbatim from wix-one-app-vod/src/api/v3 (env.ts + ChannelFetch.ts).
|
|
14
11
|
import { defineService } from "../_shared/collection-service.js";
|
|
15
12
|
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
query VodChannelList($cursor: String) {
|
|
20
|
-
channels(sort: CUSTOM_ASC, cursor: $cursor) {
|
|
21
|
-
total
|
|
22
|
-
cursor
|
|
23
|
-
entities { id title description videos { total } hasPaidVideos }
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
`;
|
|
13
|
+
import { copyParam } from "../_shared/himi-copy.js";
|
|
14
|
+
import { videoCaptionOf, withCaption, type CaptionFlags, type VideoCaption } from "../_shared/video-caption.js";
|
|
15
|
+
import { channelCoverUrl, latestVideos, listChannels, videoCard, type WixVideoCard, type WixVideoChannel } from "@himalaya/wix-video";
|
|
27
16
|
|
|
28
|
-
interface
|
|
29
|
-
interface Row { id: string; title: string; subtitle: string }
|
|
17
|
+
interface Row { id: string; title: string; subtitle: string; coverUrl: string; hasCover: boolean; noCover: boolean }
|
|
30
18
|
interface S extends LoadFlags {
|
|
31
19
|
channels: Row[]; emptyText: string;
|
|
20
|
+
/** Hides the channels section header when there is no row under it. */
|
|
21
|
+
noChannels: boolean;
|
|
22
|
+
/** The carousel section: up to CAROUSEL_SIZE ready videos across the channels, each
|
|
23
|
+
* stamped with the caption placement the descriptor asked for (`videoCaption`). */
|
|
24
|
+
latestVideos: (WixVideoCard & CaptionFlags)[]; noLatestVideos: boolean;
|
|
32
25
|
isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
|
|
33
26
|
* shows only when the site really has none, and the unavailable one only when the
|
|
34
27
|
* query failed. A failed query must never render the sentence that claims the
|
|
@@ -36,17 +29,22 @@ interface S extends LoadFlags {
|
|
|
36
29
|
notMockEmpty: boolean; notMockUnavailable: boolean;
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
/** Videos read per channel for the carousel, and the carousel's length. */
|
|
33
|
+
const PER_CHANNEL = 4;
|
|
34
|
+
const CAROUSEL_SIZE = 10;
|
|
35
|
+
|
|
36
|
+
function norm(c: WixVideoChannel): Row {
|
|
40
37
|
const title = (c.title || "Channel").trim() || "Channel";
|
|
41
38
|
const count = c.videos?.total ?? 0;
|
|
42
39
|
const parts = [count === 1 ? "1 video" : `${count} videos`, c.hasPaidVideos ? "Premium" : ""].filter(Boolean);
|
|
43
|
-
|
|
40
|
+
const coverUrl = channelCoverUrl(c);
|
|
41
|
+
return { id: c.id || "channel", title, subtitle: parts.join(" · "), coverUrl, hasCover: !!coverUrl, noCover: !coverUrl };
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
const MOCK: Row[] = [
|
|
47
|
-
{ id: "sample-tutorials", title: "Tutorials", subtitle: "12 videos" },
|
|
48
|
-
{ id: "sample-behind", title: "Behind the Scenes", subtitle: "6 videos" },
|
|
49
|
-
{ id: "sample-masterclass", title: "Masterclass", subtitle: "8 videos · Premium" },
|
|
45
|
+
{ id: "sample-tutorials", title: "Tutorials", subtitle: "12 videos", coverUrl: "", hasCover: false, noCover: true },
|
|
46
|
+
{ id: "sample-behind", title: "Behind the Scenes", subtitle: "6 videos", coverUrl: "", hasCover: false, noCover: true },
|
|
47
|
+
{ id: "sample-masterclass", title: "Masterclass", subtitle: "8 videos · Premium", coverUrl: "", hasCover: false, noCover: true },
|
|
50
48
|
];
|
|
51
49
|
|
|
52
50
|
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
@@ -60,20 +58,28 @@ const COPY: LoadCopy<Row> = {
|
|
|
60
58
|
};
|
|
61
59
|
|
|
62
60
|
let generation = 0;
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
/** The latest placement an appear asked for; a param-less reload keeps it. */
|
|
62
|
+
let caption: VideoCaption = "overlay";
|
|
63
|
+
const svc = defineService<S, { load: (s: S, arg?: unknown) => Promise<S | undefined> }>("himiBrandedVideos", {
|
|
64
|
+
state: { ...LOADING, channels: [], emptyText: "", noChannels: true, latestVideos: [], noLatestVideos: true },
|
|
65
65
|
actions: {
|
|
66
|
-
load: async (s) => {
|
|
66
|
+
load: async (s, arg) => {
|
|
67
67
|
const request = ++generation;
|
|
68
|
+
const asked = (arg as { videoCaption?: unknown } | undefined)?.videoCaption;
|
|
69
|
+
if (asked !== undefined) caption = videoCaptionOf(asked);
|
|
68
70
|
try {
|
|
69
|
-
const entities = await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const entities = await listChannels({ videosPerChannel: PER_CHANNEL });
|
|
72
|
+
const untitled = copyParam("videos.untitledVideo", "Video");
|
|
73
|
+
const cards = withCaption(latestVideos(entities, CAROUSEL_SIZE).map((entry) => videoCard(entry, untitled)), caption);
|
|
74
|
+
if (request === generation) {
|
|
75
|
+
const rows = loaded("channels", entities.map(norm), COPY);
|
|
76
|
+
return { ...s, ...rows, noChannels: rows.channels.length === 0, latestVideos: cards, noLatestVideos: cards.length === 0 };
|
|
77
|
+
}
|
|
75
78
|
} catch (err) {
|
|
76
|
-
if (request === generation)
|
|
79
|
+
if (request === generation) {
|
|
80
|
+
const rows = loadFailed("channels", err, COPY);
|
|
81
|
+
return { ...s, ...rows, noChannels: rows.channels.length === 0, latestVideos: [], noLatestVideos: true };
|
|
82
|
+
}
|
|
77
83
|
}
|
|
78
84
|
return undefined;
|
|
79
85
|
},
|
|
@@ -14,6 +14,8 @@ import { searchOrders, type WixOrder } from "@himalaya/wix-ecom";
|
|
|
14
14
|
import { guestProfile, loadMemberProfile, strOf, fmtDate, titleCase, type AccountActivityRow } from "../_shared/himi-account";
|
|
15
15
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
16
16
|
import { memberAccountRoutes } from "../_shared/member-account-routes.js";
|
|
17
|
+
import { createIapCommerce, onlineProgramOrders, pricingPlanOrders, storesAppleSalesEnabled, storesProductOrders } from "@himalaya/iap-checkout";
|
|
18
|
+
import { IAP_RESTORE_KEYS, NO_IAP_RESTORE, createIapRestore, resetUnhydratedKeysOnOlderRuntime, type IapRestoreFields } from "../_shared/iap-worker.js";
|
|
17
19
|
|
|
18
20
|
interface AuthRouteInput {
|
|
19
21
|
intent?: string;
|
|
@@ -152,7 +154,7 @@ function authHeadline(s: S, isRegistering: boolean, isRecovering: boolean): { au
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
interface S {
|
|
157
|
+
interface S extends IapRestoreFields {
|
|
156
158
|
status: "loading" | "ready";
|
|
157
159
|
isMember: boolean;
|
|
158
160
|
notMember: boolean;
|
|
@@ -214,6 +216,27 @@ interface S {
|
|
|
214
216
|
noMemberAddresses: boolean;
|
|
215
217
|
/** Hides My Wallet where this runtime cannot reach saved cards. */
|
|
216
218
|
noMemberWallet: boolean;
|
|
219
|
+
/** Set once a descriptor asks for the Restore purchases row (appear param
|
|
220
|
+
* `restorePurchases: "1"`). Only then does the worker reach the App Store layer, so a
|
|
221
|
+
* descriptor that renders no such row need not declare `iap`. */
|
|
222
|
+
restorePurchases: boolean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Restore purchases (App Review 3.1.1) for every vertical the app sells through the App Store. The
|
|
226
|
+
// row shows once purchasing is ready on this host (a bound iOS binary with managed StoreKit), to a
|
|
227
|
+
// guest too: the restore itself asks them to sign in.
|
|
228
|
+
const commerce = createIapCommerce({
|
|
229
|
+
adapters: [onlineProgramOrders, pricingPlanOrders, ...(storesAppleSalesEnabled() ? [storesProductOrders] : [])],
|
|
230
|
+
onChange: () => { void svc.restorePublish(); },
|
|
231
|
+
});
|
|
232
|
+
const restore = createIapRestore(commerce, { prefix: "storeAccount", repaint: () => { void svc.restorePublish(); } });
|
|
233
|
+
let commerceEntered = false;
|
|
234
|
+
|
|
235
|
+
/** The first entry reads readiness and recovers held purchases; later ones reconcile, as on any
|
|
236
|
+
* screen that sells. */
|
|
237
|
+
function enterCommerce(): void {
|
|
238
|
+
void (commerceEntered ? commerce.reconcile() : commerce.start());
|
|
239
|
+
commerceEntered = true;
|
|
217
240
|
}
|
|
218
241
|
|
|
219
242
|
function derive(s: S): S {
|
|
@@ -234,6 +257,7 @@ function derive(s: S): S {
|
|
|
234
257
|
hideSignInChoices: isRegistering || isRecovering,
|
|
235
258
|
hasActivity,
|
|
236
259
|
noActivity: !hasActivity,
|
|
260
|
+
...restore.fields(s.restorePurchases && commerce.readiness().status === "ready"),
|
|
237
261
|
authTitle: headline.authTitle,
|
|
238
262
|
authBody: headline.authBody,
|
|
239
263
|
hasReturnPath,
|
|
@@ -288,6 +312,8 @@ async function probe(s: S, arg?: unknown): Promise<S> {
|
|
|
288
312
|
const intent = authIntentFromArg(arg, s);
|
|
289
313
|
const profile = await loadMemberProfile(copyParam("storeAccount.guestDetail", "Sign in to keep your cart and orders connected."));
|
|
290
314
|
const memberAccountLinks = s.memberAccountLinks || strField(arg, "memberAccountLinks") === "1";
|
|
315
|
+
const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
|
|
316
|
+
if (restorePurchases) enterCommerce();
|
|
291
317
|
const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
|
|
292
318
|
let activityRows: AccountActivityRow[] = [];
|
|
293
319
|
if (profile.isMember) {
|
|
@@ -313,6 +339,7 @@ async function probe(s: S, arg?: unknown): Promise<S> {
|
|
|
313
339
|
email: profile.memberEmail || s.email,
|
|
314
340
|
message: "",
|
|
315
341
|
memberAccountLinks,
|
|
342
|
+
restorePurchases,
|
|
316
343
|
noMemberAddresses: !routes.addresses,
|
|
317
344
|
noMemberWallet: !routes.wallet,
|
|
318
345
|
activityRows,
|
|
@@ -365,7 +392,10 @@ const svc = defineService<S, {
|
|
|
365
392
|
signIn: (s: S) => Promise<S | undefined>;
|
|
366
393
|
register: (s: S) => Promise<S | undefined>;
|
|
367
394
|
signOut: (s: S) => Promise<S>;
|
|
395
|
+
restorePublish: (s: S) => S;
|
|
396
|
+
iapRestore: (s: S) => Promise<undefined>;
|
|
368
397
|
}>("himiStoreAccount", {
|
|
398
|
+
notHydrated: [...IAP_RESTORE_KEYS],
|
|
369
399
|
state: derive({
|
|
370
400
|
status: "loading",
|
|
371
401
|
isMember: false,
|
|
@@ -411,6 +441,8 @@ const svc = defineService<S, {
|
|
|
411
441
|
memberAccountLinks: false,
|
|
412
442
|
noMemberAddresses: true,
|
|
413
443
|
noMemberWallet: true,
|
|
444
|
+
restorePurchases: false,
|
|
445
|
+
...NO_IAP_RESTORE,
|
|
414
446
|
}),
|
|
415
447
|
actions: {
|
|
416
448
|
load: (s, arg) => { stashCopy(arg); return probe(s, arg); },
|
|
@@ -503,8 +535,11 @@ const svc = defineService<S, {
|
|
|
503
535
|
};
|
|
504
536
|
return derive(cleared);
|
|
505
537
|
},
|
|
538
|
+
restorePublish: (s) => derive(s),
|
|
539
|
+
iapRestore: async () => { await restore.run(); return undefined; },
|
|
506
540
|
},
|
|
507
541
|
});
|
|
508
542
|
|
|
543
|
+
resetUnhydratedKeysOnOlderRuntime(svc, { ...svc.getState(), ...NO_IAP_RESTORE }, IAP_RESTORE_KEYS);
|
|
509
544
|
svc.load();
|
|
510
545
|
export { svc };
|