@bash-app/bash-common 30.367.0 → 30.370.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/extendedSchemas.d.ts +2 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/liveActivity/liveActivityTypes.d.ts +59 -0
- package/dist/liveActivity/liveActivityTypes.d.ts.map +1 -0
- package/dist/liveActivity/liveActivityTypes.js +27 -0
- package/dist/liveActivity/liveActivityTypes.js.map +1 -0
- package/dist/utils/__tests__/organizationEntitlements.test.js +42 -9
- package/dist/utils/__tests__/organizationEntitlements.test.js.map +1 -1
- package/dist/utils/organizationEntitlements.d.ts +27 -7
- package/dist/utils/organizationEntitlements.d.ts.map +1 -1
- package/dist/utils/organizationEntitlements.js +28 -11
- package/dist/utils/organizationEntitlements.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +398 -0
- package/src/extendedSchemas.ts +2 -0
- package/src/index.ts +1 -0
- package/src/liveActivity/liveActivityTypes.ts +75 -0
- package/src/utils/__tests__/organizationEntitlements.test.ts +47 -8
- package/src/utils/organizationEntitlements.ts +42 -11
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared shapes for the bash-ticket Live Activity (iOS ActivityKit Lock Screen /
|
|
3
|
+
* Dynamic Island) and Android Live Update / sticky-notification fallback.
|
|
4
|
+
*
|
|
5
|
+
* `api` builds `LiveActivityContentState` server-side to push via APNs
|
|
6
|
+
* (push-to-start/update/end) or an FCM data message; `bash-app` uses the same
|
|
7
|
+
* shape to type the native plugin's `start`/`update` calls for the manual
|
|
8
|
+
* "Track this bash" toggle. Keeping this in bash-common avoids the two repos
|
|
9
|
+
* drifting on field names for what is ultimately one wire contract.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Static, per-activity identifiers — set once, never change during the activity. */
|
|
13
|
+
export interface LiveActivityAttributes {
|
|
14
|
+
bashEventId: string;
|
|
15
|
+
ticketId: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Dynamic content shown on the Lock Screen / Dynamic Island / Android chip.
|
|
20
|
+
* Kept intentionally small — ActivityKit caps combined attributes + state at 4KB,
|
|
21
|
+
* and every field here is re-sent in full on every update (no partial merges).
|
|
22
|
+
*/
|
|
23
|
+
export interface LiveActivityContentState {
|
|
24
|
+
/** Bash event title — primary headline. */
|
|
25
|
+
eventTitle: string;
|
|
26
|
+
/** Short venue/location line — subtitle (e.g. venue name or first line of address). */
|
|
27
|
+
venueLabel: string;
|
|
28
|
+
/** ISO 8601 — bash start time. Widget shows a countdown until this, then a "LIVE" badge. */
|
|
29
|
+
startTime: string;
|
|
30
|
+
/** ISO 8601 — bash end time. Used for the lazy client-side end fallback. */
|
|
31
|
+
endTime: string;
|
|
32
|
+
/**
|
|
33
|
+
* Absolute URL that opens the device's native Maps app for directions
|
|
34
|
+
* (Apple Maps on iOS, Google Maps on Android) — never a relative path,
|
|
35
|
+
* since the widget/notification has no app routing context.
|
|
36
|
+
*/
|
|
37
|
+
directionsUrl: string;
|
|
38
|
+
/**
|
|
39
|
+
* Absolute universal-link URL back into bash-app's ticket QR screen
|
|
40
|
+
* (`bashEventQrCodeUrl` + the app's public origin). Deliberately NOT an
|
|
41
|
+
* embedded QR image/token — keeps the payload small and the QR always fresh.
|
|
42
|
+
*/
|
|
43
|
+
ticketUrl: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Discriminates the three lifecycle pushes sent over APNs (iOS) or as an FCM data message (Android). */
|
|
47
|
+
export type LiveActivityPushEventType =
|
|
48
|
+
| "live_activity_start"
|
|
49
|
+
| "live_activity_update"
|
|
50
|
+
| "live_activity_end";
|
|
51
|
+
|
|
52
|
+
/** Platform values accepted on `LiveActivityPushToken.platform` — iOS only today (Android reuses the FCM token). */
|
|
53
|
+
export const LIVE_ACTIVITY_TOKEN_PLATFORM_IOS = "ios" as const;
|
|
54
|
+
|
|
55
|
+
/** Which native Maps app `buildLiveActivityDirectionsUrl` should target. */
|
|
56
|
+
export type LiveActivityMapsPlatform = "ios" | "android";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Builds the "Directions" deep link shared by the api (server-built push
|
|
60
|
+
* payloads) and bash-app (client-built manual-start payload) — Apple Maps on
|
|
61
|
+
* iOS, Google Maps everywhere else, preferring coordinates over the raw
|
|
62
|
+
* address/title string when available.
|
|
63
|
+
*/
|
|
64
|
+
export function buildLiveActivityDirectionsUrl(
|
|
65
|
+
destinationCoords: { lat: number; lng: number } | null | undefined,
|
|
66
|
+
fallbackLabel: string,
|
|
67
|
+
platform: LiveActivityMapsPlatform
|
|
68
|
+
): string {
|
|
69
|
+
const destination = destinationCoords
|
|
70
|
+
? `${destinationCoords.lat},${destinationCoords.lng}`
|
|
71
|
+
: encodeURIComponent(fallbackLabel);
|
|
72
|
+
return platform === "ios"
|
|
73
|
+
? `https://maps.apple.com/?daddr=${destination}`
|
|
74
|
+
: `https://www.google.com/maps/search/?api=1&query=${destination}`;
|
|
75
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
organizationHasApiAccess,
|
|
5
5
|
organizationHasAuditLogAccess,
|
|
6
6
|
organizationHasComplianceAccess,
|
|
7
|
+
organizationHasCustomDomainAccess,
|
|
7
8
|
organizationHasEnterprisePlatformAccess,
|
|
8
9
|
organizationHasProcurementAccess,
|
|
9
10
|
organizationHasSsoAccess,
|
|
@@ -67,41 +68,79 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
describe("enterprise platform gates (audit logs, SSO, procurement, API access)", () => {
|
|
70
|
-
|
|
71
|
+
// Sales-provisioned: ops set enterpriseSalesProvisioned via the admin
|
|
72
|
+
// enterprise-provisioning endpoint after a signed deal.
|
|
73
|
+
const salesProvisionedEnterprise = {
|
|
71
74
|
subscriptionTier: "Enterprise",
|
|
72
75
|
subscriptionStatus: "Active",
|
|
76
|
+
enterpriseSalesProvisioned: true,
|
|
77
|
+
};
|
|
78
|
+
// Self-serve: bought via Stripe checkout, never grants the platform surface.
|
|
79
|
+
const selfServeEnterprise = {
|
|
80
|
+
subscriptionTier: "Enterprise",
|
|
81
|
+
subscriptionStatus: "Active",
|
|
82
|
+
enterpriseSalesProvisioned: false,
|
|
73
83
|
};
|
|
74
84
|
const growthActive = { subscriptionTier: "Growth", subscriptionStatus: "Active" };
|
|
75
85
|
|
|
76
|
-
test("organizationHasEnterprisePlatformAccess
|
|
77
|
-
expect(organizationHasEnterprisePlatformAccess(
|
|
86
|
+
test("organizationHasEnterprisePlatformAccess requires enterpriseSalesProvisioned", () => {
|
|
87
|
+
expect(organizationHasEnterprisePlatformAccess(salesProvisionedEnterprise)).toBe(true);
|
|
88
|
+
expect(organizationHasEnterprisePlatformAccess(selfServeEnterprise)).toBe(false);
|
|
78
89
|
expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
|
|
79
90
|
expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
|
|
80
91
|
expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
|
|
81
92
|
});
|
|
82
93
|
|
|
94
|
+
test("missing enterpriseSalesProvisioned on an Enterprise org fails closed (not entitled)", () => {
|
|
95
|
+
expect(
|
|
96
|
+
organizationHasEnterprisePlatformAccess({
|
|
97
|
+
subscriptionTier: "Enterprise",
|
|
98
|
+
subscriptionStatus: "Active",
|
|
99
|
+
})
|
|
100
|
+
).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
83
103
|
test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
|
|
84
|
-
expect(organizationHasAuditLogAccess(
|
|
104
|
+
expect(organizationHasAuditLogAccess(salesProvisionedEnterprise)).toBe(true);
|
|
105
|
+
expect(organizationHasAuditLogAccess(selfServeEnterprise)).toBe(false);
|
|
85
106
|
expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
|
|
86
107
|
});
|
|
87
108
|
|
|
88
109
|
test("organizationHasSsoAccess follows the shared enterprise gate", () => {
|
|
89
|
-
expect(organizationHasSsoAccess(
|
|
110
|
+
expect(organizationHasSsoAccess(salesProvisionedEnterprise)).toBe(true);
|
|
111
|
+
expect(organizationHasSsoAccess(selfServeEnterprise)).toBe(false);
|
|
90
112
|
expect(organizationHasSsoAccess(growthActive)).toBe(false);
|
|
91
113
|
});
|
|
92
114
|
|
|
93
115
|
test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
|
|
94
|
-
expect(organizationHasProcurementAccess(
|
|
116
|
+
expect(organizationHasProcurementAccess(salesProvisionedEnterprise)).toBe(true);
|
|
117
|
+
expect(organizationHasProcurementAccess(selfServeEnterprise)).toBe(false);
|
|
95
118
|
expect(organizationHasProcurementAccess(growthActive)).toBe(false);
|
|
96
119
|
});
|
|
97
120
|
|
|
98
121
|
test("organizationHasApiAccess follows the shared enterprise gate", () => {
|
|
99
|
-
expect(organizationHasApiAccess(
|
|
122
|
+
expect(organizationHasApiAccess(salesProvisionedEnterprise)).toBe(true);
|
|
123
|
+
expect(organizationHasApiAccess(selfServeEnterprise)).toBe(false);
|
|
100
124
|
expect(organizationHasApiAccess(growthActive)).toBe(false);
|
|
101
125
|
});
|
|
102
126
|
|
|
103
127
|
test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
|
|
104
|
-
expect(organizationHasComplianceAccess(
|
|
128
|
+
expect(organizationHasComplianceAccess(salesProvisionedEnterprise)).toBe(true);
|
|
129
|
+
expect(organizationHasComplianceAccess(selfServeEnterprise)).toBe(false);
|
|
105
130
|
expect(organizationHasComplianceAccess(growthActive)).toBe(false);
|
|
106
131
|
});
|
|
132
|
+
|
|
133
|
+
test("organizationHasCustomDomainAccess (white-label) is self-serve-eligible — no sales flag required", () => {
|
|
134
|
+
expect(organizationHasCustomDomainAccess(salesProvisionedEnterprise)).toBe(true);
|
|
135
|
+
expect(organizationHasCustomDomainAccess(selfServeEnterprise)).toBe(true);
|
|
136
|
+
expect(organizationHasCustomDomainAccess(growthActive)).toBe(false);
|
|
137
|
+
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
138
|
+
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("organizationHasVanitySubdomain is self-serve-eligible — no sales flag required", () => {
|
|
142
|
+
expect(organizationHasVanitySubdomain(salesProvisionedEnterprise)).toBe(true);
|
|
143
|
+
expect(organizationHasVanitySubdomain(selfServeEnterprise)).toBe(true);
|
|
144
|
+
expect(organizationHasVanitySubdomain(growthActive)).toBe(false);
|
|
145
|
+
});
|
|
107
146
|
});
|
|
@@ -15,14 +15,17 @@ export const ACTIVE_ORG_SUBSCRIPTION_STATUSES = [
|
|
|
15
15
|
export interface OrganizationEntitlementInput {
|
|
16
16
|
subscriptionTier?: string | null;
|
|
17
17
|
subscriptionStatus?: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* True only when ops provisioned Enterprise via a signed deal (admin
|
|
20
|
+
* enterprise-provisioning endpoint). Self-serve Stripe checkout can also set
|
|
21
|
+
* subscriptionTier="Enterprise" but never sets this. Optional/undefined is
|
|
22
|
+
* treated as false so legacy call sites that don't select this field fail
|
|
23
|
+
* closed on the sales-gated surface rather than open.
|
|
24
|
+
*/
|
|
25
|
+
enterpriseSalesProvisioned?: boolean | null;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
* Vanity subdomains ({slug}.bash.community) are an Enterprise-only perk. All other
|
|
22
|
-
* orgs use the canonical path URL (bash.community/org/{slug}). Requires an active
|
|
23
|
-
* (or trialing) subscription so a lapsed Enterprise org falls back to the path.
|
|
24
|
-
*/
|
|
25
|
-
export function organizationHasVanitySubdomain(
|
|
28
|
+
function hasActiveEnterpriseTier(
|
|
26
29
|
org: OrganizationEntitlementInput | null | undefined
|
|
27
30
|
): boolean {
|
|
28
31
|
if (!org) return false;
|
|
@@ -36,18 +39,30 @@ export function organizationHasVanitySubdomain(
|
|
|
36
39
|
);
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Vanity subdomains ({slug}.bash.community) and custom domains (white-label)
|
|
44
|
+
* are self-serve-eligible Enterprise perks — available whether Enterprise was
|
|
45
|
+
* bought via self-serve Stripe checkout or a sales-negotiated deal.
|
|
46
|
+
*/
|
|
47
|
+
export function organizationHasVanitySubdomain(
|
|
48
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
49
|
+
): boolean {
|
|
50
|
+
return hasActiveEnterpriseTier(org);
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
/**
|
|
40
54
|
* Shared gate for the enterprise platform surface: org-scoped audit logs,
|
|
41
55
|
* SSO/identity setup, procurement approval policy, API keys, webhooks, and
|
|
42
|
-
* third-party integrations (Mailchimp/Salesforce).
|
|
43
|
-
* perks
|
|
44
|
-
*
|
|
45
|
-
*
|
|
56
|
+
* third-party integrations (Mailchimp/Salesforce). Unlike the white-label
|
|
57
|
+
* perks above, this surface requires a sales-negotiated deal
|
|
58
|
+
* (`enterpriseSalesProvisioned`) — self-serve $495/mo checkout alone does not
|
|
59
|
+
* unlock SSO, compliance, procurement, or API access, since those typically
|
|
60
|
+
* involve a security review or contract terms sales owns.
|
|
46
61
|
*/
|
|
47
62
|
export function organizationHasEnterprisePlatformAccess(
|
|
48
63
|
org: OrganizationEntitlementInput | null | undefined
|
|
49
64
|
): boolean {
|
|
50
|
-
return
|
|
65
|
+
return hasActiveEnterpriseTier(org) && org?.enterpriseSalesProvisioned === true;
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
/** Org-admin audit log viewer + CSV export. */
|
|
@@ -84,3 +99,19 @@ export function organizationHasComplianceAccess(
|
|
|
84
99
|
): boolean {
|
|
85
100
|
return organizationHasEnterprisePlatformAccess(org);
|
|
86
101
|
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Custom domains (white-label tier): real DNS (e.g. members.acme.com) provisioned
|
|
105
|
+
* via AWS CloudFront SaaS Manager Distribution Tenants, plus the "Powered by Bash"
|
|
106
|
+
* removal toggle. Same Enterprise + active/trialing gate as
|
|
107
|
+
* {@link organizationHasVanitySubdomain} for MVP — reuse the existing tier rather
|
|
108
|
+
* than invent a new pricing tier now. A dedicated "White-label" tier is a
|
|
109
|
+
* fast-follow if sales needs separate pricing. Self-serve-eligible: does NOT
|
|
110
|
+
* require {@link organizationHasEnterprisePlatformAccess}'s
|
|
111
|
+
* `enterpriseSalesProvisioned` flag.
|
|
112
|
+
*/
|
|
113
|
+
export function organizationHasCustomDomainAccess(
|
|
114
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
115
|
+
): boolean {
|
|
116
|
+
return organizationHasVanitySubdomain(org);
|
|
117
|
+
}
|