webspresso 0.0.93 → 0.0.94

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.
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ function dateLabel(value) {
4
+ if (!value) return '';
5
+ if (value instanceof Date) {
6
+ return Number.isNaN(value.getTime()) ? '' : value.toISOString().slice(0, 10);
7
+ }
8
+ const str = String(value);
9
+ if (/^\d{4}-\d{2}-\d{2}/.test(str)) return str.slice(0, 10);
10
+ const parsed = new Date(str);
11
+ return Number.isNaN(parsed.getTime()) ? str.slice(0, 10) : parsed.toISOString().slice(0, 10);
12
+ }
13
+
14
+ function createSafeTruncateFilter(originalTruncate) {
15
+ return function safeTruncate(value, length, end) {
16
+ if (value == null) return '';
17
+ if (value instanceof Date) {
18
+ return dateLabel(value);
19
+ }
20
+ if (typeof value === 'object') {
21
+ try {
22
+ return originalTruncate(JSON.stringify(value), length, end);
23
+ } catch {
24
+ return originalTruncate(String(value), length, end);
25
+ }
26
+ }
27
+ return originalTruncate(String(value), length, end);
28
+ };
29
+ }
30
+
31
+ function defaultTruncate(value, length = 20, end = '…') {
32
+ const str = value instanceof Date ? dateLabel(value) : String(value ?? '');
33
+ if (str.length <= length) return str;
34
+ return `${str.slice(0, length)}${end}`;
35
+ }
36
+
37
+ function registerNunjucksFilters(nunjucksEnv) {
38
+ nunjucksEnv.addFilter('dateLabel', dateLabel);
39
+
40
+ let existing = null;
41
+ if (typeof nunjucksEnv.getFilter === 'function') {
42
+ existing = nunjucksEnv.getFilter('truncate');
43
+ }
44
+
45
+ const truncateFn = typeof existing === 'function'
46
+ ? createSafeTruncateFilter(existing)
47
+ : defaultTruncate;
48
+
49
+ nunjucksEnv.addFilter('truncate', truncateFn, true);
50
+ }
51
+
52
+ module.exports = {
53
+ dateLabel,
54
+ createSafeTruncateFilter,
55
+ registerNunjucksFilters,
56
+ };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const { polarApiRequest } = require('./api');
4
+ const { isValidPortalRedirectUrl } = require('./urls');
5
+
6
+ function getDefaultPortalUrl(config) {
7
+ if (!config.orgSlug) return null;
8
+ const host = config.sandbox ? 'https://sandbox.polar.sh' : 'https://polar.sh';
9
+ return `${host}/${config.orgSlug}/portal`;
10
+ }
11
+
12
+ async function getPolarPortalUrl(user, baseUrl, config) {
13
+ const defaultPortal = getDefaultPortalUrl(config);
14
+ const f = config.fields;
15
+
16
+ if (config.accessToken && user[f.polarCustomerId]) {
17
+ try {
18
+ const payload = { customer_id: user[f.polarCustomerId] };
19
+ if (baseUrl) {
20
+ const returnPath = config.urls.return.startsWith('http')
21
+ ? config.urls.return
22
+ : `${baseUrl}${config.urls.return}`;
23
+ payload.return_url = returnPath;
24
+ }
25
+
26
+ const data = await polarApiRequest('/customer-sessions/', {
27
+ method: 'POST',
28
+ body: payload,
29
+ accessToken: config.accessToken,
30
+ sandbox: config.sandbox,
31
+ });
32
+
33
+ if (data.customer_portal_url && isValidPortalRedirectUrl(data.customer_portal_url)) {
34
+ return data.customer_portal_url;
35
+ }
36
+ } catch (err) {
37
+ console.error('[polar] Portal API error:', err.message);
38
+ }
39
+ }
40
+
41
+ if (defaultPortal && isValidPortalRedirectUrl(defaultPortal)) {
42
+ return defaultPortal;
43
+ }
44
+
45
+ throw new Error('Polar customer portal is not configured. Set POLAR_ORG_SLUG to your Polar organization slug.');
46
+ }
47
+
48
+ module.exports = {
49
+ getDefaultPortalUrl,
50
+ getPolarPortalUrl,
51
+ };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_RATE_LIMITS = {
4
+ webhook: {
5
+ limit: 120,
6
+ windowMs: 60_000,
7
+ message: { error: 'Too many webhook requests' },
8
+ },
9
+ checkout: {
10
+ limit: 5,
11
+ windowMs: 60_000,
12
+ message: { error: 'Too many checkout attempts. Please try again later.' },
13
+ keyGenerator: (req) => `polar:checkout:${req.user?.id ?? req.ip}`,
14
+ },
15
+ portal: {
16
+ limit: 10,
17
+ windowMs: 60_000,
18
+ message: { error: 'Too many portal requests. Please try again later.' },
19
+ keyGenerator: (req) => `polar:portal:${req.user?.id ?? req.ip}`,
20
+ },
21
+ status: {
22
+ limit: 60,
23
+ windowMs: 60_000,
24
+ message: { error: 'Too many billing status requests' },
25
+ keyGenerator: (req) => `polar:status:${req.user?.id ?? req.ip}`,
26
+ },
27
+ };
28
+
29
+ /**
30
+ * Build per-route limiter middleware from rateLimitPlugin factory.
31
+ * @param {Object} ctx - plugin onRoutesReady context
32
+ * @param {import('./config').PolarConfig} config
33
+ * @returns {{ webhook?: Function, checkout?: Function, portal?: Function, status?: Function }}
34
+ */
35
+ function resolvePolarRateLimiters(ctx, config) {
36
+ const rlOpt = config.rateLimit;
37
+ if (rlOpt === false || rlOpt?.enabled === false) {
38
+ return {};
39
+ }
40
+
41
+ const factory = ctx.middlewares?.rateLimit;
42
+ if (typeof factory !== 'function') {
43
+ if (rlOpt === true || (rlOpt && typeof rlOpt === 'object')) {
44
+ console.warn(
45
+ '[polar] rateLimit is enabled but rateLimitPlugin is not loaded — add rateLimitPlugin() before polarPlugin in plugins[]'
46
+ );
47
+ }
48
+ return {};
49
+ }
50
+
51
+ const overrides = rlOpt === true ? {} : (rlOpt || {});
52
+ const routes = ['webhook', 'checkout', 'portal', 'status'];
53
+ const out = {};
54
+
55
+ for (const route of routes) {
56
+ if (overrides[route] === false) continue;
57
+ const opts = {
58
+ ...DEFAULT_RATE_LIMITS[route],
59
+ ...(typeof overrides[route] === 'object' ? overrides[route] : {}),
60
+ };
61
+ out[route] = factory(opts);
62
+ }
63
+
64
+ return out;
65
+ }
66
+
67
+ module.exports = {
68
+ DEFAULT_RATE_LIMITS,
69
+ resolvePolarRateLimiters,
70
+ };
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ const { verifyPolarWebhook, handlePolarWebhookEvent } = require('./webhooks');
4
+ const { getPolarCheckoutUrl } = require('./checkout');
5
+ const { getPolarPortalUrl } = require('./portal');
6
+ const { syncPolarBillingForUser } = require('./sync');
7
+ const { userHasProTier } = require('./user-resolver');
8
+
9
+ function resolveKnex(db) {
10
+ return db?.knex || (typeof db?.raw === 'function' ? db : null);
11
+ }
12
+
13
+ function wantsJson(req) {
14
+ return req.xhr
15
+ || req.headers.accept?.includes('application/json')
16
+ || req.is('json');
17
+ }
18
+
19
+ function resolveBaseUrl(req, config) {
20
+ return config.baseUrl || `${req.protocol}://${req.get('host')}`;
21
+ }
22
+
23
+ function createInjectDb(db) {
24
+ return (req, res, next) => {
25
+ if (db && !req.db) req.db = db;
26
+ next();
27
+ };
28
+ }
29
+
30
+ function createWebhookHandler(config) {
31
+ return async function polarWebhookHandler(req, res) {
32
+ try {
33
+ const secret = config.webhookSecret;
34
+ if (!secret) {
35
+ return res.status(500).json({ error: 'POLAR_WEBHOOK_SECRET is not configured' });
36
+ }
37
+
38
+ const rawPayload = req.rawBody
39
+ ? (Buffer.isBuffer(req.rawBody) ? req.rawBody.toString('utf8') : String(req.rawBody))
40
+ : (typeof req.body === 'string' ? req.body : JSON.stringify(req.body));
41
+
42
+ if (!verifyPolarWebhook(rawPayload, req.headers, secret)) {
43
+ return res.status(401).json({ error: 'Invalid webhook signature', received: false });
44
+ }
45
+
46
+ const knex = resolveKnex(config.db);
47
+ if (!knex) {
48
+ return res.status(500).json({ error: 'Database unavailable' });
49
+ }
50
+
51
+ const event = typeof req.body === 'object' && req.body && !Buffer.isBuffer(req.body)
52
+ ? req.body
53
+ : JSON.parse(rawPayload);
54
+
55
+ const result = await handlePolarWebhookEvent(event, knex, config);
56
+ return res.status(200).json({
57
+ received: true,
58
+ processed: result.processed,
59
+ action: result.action,
60
+ userId: result.userId,
61
+ });
62
+ } catch (err) {
63
+ console.error('[polar] Webhook error:', err);
64
+ return res.status(400).json({ error: err.message || 'Webhook processing failed' });
65
+ }
66
+ };
67
+ }
68
+
69
+ function createCheckoutHandler(config) {
70
+ return async function polarCheckoutHandler(req, res) {
71
+ try {
72
+ if (!req.user) {
73
+ if (wantsJson(req)) return res.status(401).json({ error: 'Authentication required' });
74
+ return res.redirect(config.urls.login);
75
+ }
76
+
77
+ const knex = resolveKnex(config.db);
78
+ const f = config.fields;
79
+ let user = req.user;
80
+
81
+ if (knex) {
82
+ const dbUser = await knex(config.userTable).where(f.id, req.user[f.id] || req.user.id).first();
83
+ if (dbUser) user = dbUser;
84
+ if (config.accessToken) {
85
+ user = await syncPolarBillingForUser(user, knex, config);
86
+ }
87
+ }
88
+
89
+ if (userHasProTier(user, config)) {
90
+ const alreadyUrl = config.urls.alreadySubscribed.startsWith('http')
91
+ ? config.urls.alreadySubscribed
92
+ : config.urls.alreadySubscribed;
93
+ if (wantsJson(req)) {
94
+ return res.status(409).json({
95
+ error: 'You already have an active subscription.',
96
+ tier: user[f.tier],
97
+ polarStatus: user[f.polarStatus] || 'active',
98
+ });
99
+ }
100
+ return res.redirect(alreadyUrl);
101
+ }
102
+
103
+ const plan = req.body?.plan || req.query?.plan || 'pro_monthly';
104
+ const baseUrl = resolveBaseUrl(req, config);
105
+ const checkoutUrl = await getPolarCheckoutUrl({ user, plan, baseUrl, config });
106
+
107
+ if (wantsJson(req)) {
108
+ return res.json({ url: checkoutUrl });
109
+ }
110
+ return res.redirect(checkoutUrl);
111
+ } catch (err) {
112
+ console.error('[polar] Checkout error:', err.message);
113
+ if (wantsJson(req)) {
114
+ return res.status(503).json({ error: 'Checkout is temporarily unavailable. Please try again later.' });
115
+ }
116
+ const errUrl = config.urls.return.includes('?')
117
+ ? `${config.urls.return}&error=checkout_unavailable`
118
+ : `${config.urls.return}?error=checkout_unavailable`;
119
+ return res.redirect(errUrl);
120
+ }
121
+ };
122
+ }
123
+
124
+ function createPortalHandler(config) {
125
+ return async function polarPortalHandler(req, res) {
126
+ try {
127
+ if (!req.user) {
128
+ return res.redirect(config.urls.login);
129
+ }
130
+
131
+ const baseUrl = resolveBaseUrl(req, config);
132
+ const portalUrl = await getPolarPortalUrl(req.user, baseUrl, config);
133
+ return res.redirect(portalUrl);
134
+ } catch (err) {
135
+ console.error('[polar] Portal error:', err.message);
136
+ const errUrl = config.urls.return.includes('?')
137
+ ? `${config.urls.return}&error=portal_unavailable`
138
+ : `${config.urls.return}?error=portal_unavailable`;
139
+ return res.redirect(errUrl);
140
+ }
141
+ };
142
+ }
143
+
144
+ function createStatusHandler(config) {
145
+ return async function polarStatusHandler(req, res, next) {
146
+ try {
147
+ const knex = resolveKnex(config.db);
148
+ const f = config.fields;
149
+ let user = req.user;
150
+
151
+ if (!user) {
152
+ return res.status(401).json({ error: 'Unauthorized' });
153
+ }
154
+
155
+ if (knex) {
156
+ const dbUser = await knex(config.userTable).where(f.id, user[f.id] || user.id).first();
157
+ if (dbUser) user = dbUser;
158
+ if (config.accessToken) {
159
+ user = await syncPolarBillingForUser(user, knex, config);
160
+ }
161
+ }
162
+
163
+ const tier = user[f.tier] || config.freeTierName;
164
+ const isPro = userHasProTier(user, config);
165
+
166
+ return res.json({
167
+ userId: user[f.id],
168
+ publicId: user[f.publicId],
169
+ tier,
170
+ isPro,
171
+ polarStatus: user[f.polarStatus] || 'none',
172
+ polarCustomerId: user[f.polarCustomerId] || null,
173
+ polarSubscriptionId: user[f.polarSubscriptionId] || null,
174
+ polarCurrentPeriodEnd: user[f.polarCurrentPeriodEnd] || null,
175
+ polarCancelAtPeriodEnd: Boolean(user[f.polarCancelAtPeriodEnd]),
176
+ });
177
+ } catch (err) {
178
+ next(err);
179
+ }
180
+ };
181
+ }
182
+
183
+ function createRequireAuth(config, ctx) {
184
+ if (typeof config.requireAuth === 'function') {
185
+ return config.requireAuth;
186
+ }
187
+
188
+ const authFactory = ctx.middlewares?.auth;
189
+ if (typeof authFactory === 'function') {
190
+ return (req, res, next) => {
191
+ const isApi = req.path.startsWith('/api/') || wantsJson(req);
192
+ return authFactory({ api: isApi })(req, res, next);
193
+ };
194
+ }
195
+
196
+ return (req, res, next) => {
197
+ if (req.user) return next();
198
+ if (wantsJson(req) || req.path.startsWith('/api/')) {
199
+ return res.status(401).json({ error: 'Authentication required' });
200
+ }
201
+ return res.redirect(config.urls.login);
202
+ };
203
+ }
204
+
205
+ module.exports = {
206
+ createInjectDb,
207
+ createWebhookHandler,
208
+ createCheckoutHandler,
209
+ createPortalHandler,
210
+ createStatusHandler,
211
+ createRequireAuth,
212
+ };
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const { polarApiRequest } = require('./api');
4
+ const {
5
+ userHasProTier,
6
+ listPolarSubscriptions,
7
+ pickBestActiveSubscription,
8
+ normalizePolarTimestamp,
9
+ collectExternalCustomerIds,
10
+ } = require('./user-resolver');
11
+
12
+ async function applyPolarProSubscription(knex, user, customerState, subscription, config) {
13
+ const f = config.fields;
14
+ const update = {
15
+ [f.tier]: config.proTierName,
16
+ [f.polarStatus]: subscription.status || 'active',
17
+ [f.polarCustomerId]: customerState?.id || subscription.customer_id || user[f.polarCustomerId] || null,
18
+ [f.polarSubscriptionId]: subscription.id || user[f.polarSubscriptionId] || null,
19
+ [f.polarCurrentPeriodEnd]: normalizePolarTimestamp(subscription.current_period_end),
20
+ [f.polarCancelAtPeriodEnd]: Boolean(subscription.cancel_at_period_end),
21
+ updated_at: new Date(),
22
+ };
23
+
24
+ if (config.hooks.onSubscriptionChange) {
25
+ await config.hooks.onSubscriptionChange({
26
+ user,
27
+ tier: config.proTierName,
28
+ polarStatus: update[f.polarStatus],
29
+ subscription,
30
+ knex,
31
+ update,
32
+ });
33
+ } else {
34
+ await knex(config.userTable).where(f.id, user[f.id]).update(update);
35
+ }
36
+
37
+ return knex(config.userTable).where(f.id, user[f.id]).first();
38
+ }
39
+
40
+ async function syncFromCustomerState(user, knex, customerState, config) {
41
+ if (!customerState) return user;
42
+
43
+ const activeSub = pickBestActiveSubscription(customerState.active_subscriptions, config);
44
+ if (activeSub) {
45
+ return applyPolarProSubscription(knex, user, customerState, activeSub, config);
46
+ }
47
+
48
+ const f = config.fields;
49
+ if (customerState.id && customerState.id !== user[f.polarCustomerId]) {
50
+ const patch = {
51
+ [f.polarCustomerId]: customerState.id,
52
+ updated_at: new Date(),
53
+ };
54
+ await knex(config.userTable).where(f.id, user[f.id]).update(patch);
55
+ return { ...user, [f.polarCustomerId]: customerState.id };
56
+ }
57
+
58
+ return user;
59
+ }
60
+
61
+ async function syncPolarBillingForUser(user, knex, config) {
62
+ if (!user || !knex || !config.accessToken) {
63
+ return user;
64
+ }
65
+
66
+ const apiOpts = { accessToken: config.accessToken, sandbox: config.sandbox };
67
+ const externalIds = collectExternalCustomerIds(user, config);
68
+
69
+ for (const externalId of externalIds) {
70
+ try {
71
+ const state = await polarApiRequest(
72
+ `/customers/external/${encodeURIComponent(externalId)}/state`,
73
+ apiOpts
74
+ );
75
+ user = await syncFromCustomerState(user, knex, state, config);
76
+ if (userHasProTier(user, config)) return user;
77
+ } catch (err) {
78
+ if (!String(err.message || '').includes('404')) {
79
+ console.error('[polar] Sync error (external state):', externalId, err.message);
80
+ }
81
+ }
82
+ }
83
+
84
+ const f = config.fields;
85
+ if (user[f.polarCustomerId]) {
86
+ try {
87
+ const state = await polarApiRequest(`/customers/${user[f.polarCustomerId]}/state`, apiOpts);
88
+ user = await syncFromCustomerState(user, knex, state, config);
89
+ if (userHasProTier(user, config)) return user;
90
+ } catch (err) {
91
+ if (!String(err.message || '').includes('404')) {
92
+ console.error('[polar] Sync error (customer state):', user[f.polarCustomerId], err.message);
93
+ }
94
+ }
95
+ }
96
+
97
+ if (user[f.email]) {
98
+ try {
99
+ const customersData = await polarApiRequest(
100
+ `/customers/?email=${encodeURIComponent(String(user[f.email]).trim())}&limit=5`,
101
+ apiOpts
102
+ );
103
+ for (const customer of listPolarSubscriptions(customersData)) {
104
+ if (!customer?.id) continue;
105
+ const state = await polarApiRequest(`/customers/${customer.id}/state`, apiOpts);
106
+ user = await syncFromCustomerState(user, knex, state, config);
107
+ if (userHasProTier(user, config)) return user;
108
+ }
109
+ } catch (err) {
110
+ console.error('[polar] Sync error (email lookup):', user[f.email], err.message);
111
+ }
112
+ }
113
+
114
+ for (const externalId of externalIds) {
115
+ try {
116
+ const subsData = await polarApiRequest(
117
+ `/subscriptions/?external_customer_id=${encodeURIComponent(externalId)}&limit=10`,
118
+ apiOpts
119
+ );
120
+ const activeSub = pickBestActiveSubscription(listPolarSubscriptions(subsData), config);
121
+ if (activeSub) {
122
+ return applyPolarProSubscription(knex, user, { id: activeSub.customer_id }, activeSub, config);
123
+ }
124
+ } catch (err) {
125
+ if (!String(err.message || '').includes('404')) {
126
+ console.error('[polar] Sync error (subscriptions list):', externalId, err.message);
127
+ }
128
+ }
129
+ }
130
+
131
+ return user;
132
+ }
133
+
134
+ module.exports = {
135
+ applyPolarProSubscription,
136
+ syncFromCustomerState,
137
+ syncPolarBillingForUser,
138
+ };
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ function getPolarBuyBase(sandbox) {
4
+ return sandbox ? 'https://sandbox-buy.polar.sh' : 'https://buy.polar.sh';
5
+ }
6
+
7
+ function getPolarWebBase(sandbox) {
8
+ return sandbox ? 'https://sandbox.polar.sh' : 'https://polar.sh';
9
+ }
10
+
11
+ function isBlockedPolarUrl(urlString) {
12
+ try {
13
+ const url = new URL(urlString);
14
+ const path = url.pathname.replace(/\/+$/, '') || '/';
15
+ return (
16
+ path === '/404'
17
+ || path === '/purchases/subscriptions'
18
+ || (url.hostname === 'polar.sh' && path === '/')
19
+ );
20
+ } catch {
21
+ return true;
22
+ }
23
+ }
24
+
25
+ function isValidCheckoutRedirectUrl(urlString) {
26
+ try {
27
+ const url = new URL(urlString);
28
+ if (isBlockedPolarUrl(urlString)) return false;
29
+
30
+ if (
31
+ url.hostname === 'buy.polar.sh'
32
+ || url.hostname === 'sandbox-buy.polar.sh'
33
+ ) {
34
+ return true;
35
+ }
36
+
37
+ const webHosts = new Set(['polar.sh', 'sandbox.polar.sh', 'www.polar.sh']);
38
+ if (webHosts.has(url.hostname)) {
39
+ return (
40
+ url.pathname.startsWith('/checkout/')
41
+ || url.pathname.includes('/polar_c_')
42
+ || url.pathname.includes('/polar_cl_')
43
+ );
44
+ }
45
+
46
+ return false;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function isValidPortalRedirectUrl(urlString) {
53
+ try {
54
+ const url = new URL(urlString);
55
+ if (isBlockedPolarUrl(urlString)) return false;
56
+ if (url.hostname === 'polar.sh' || url.hostname === 'sandbox.polar.sh') {
57
+ return /^\/[^/]+\/portal(?:\/|$)/.test(url.pathname);
58
+ }
59
+ return url.hostname.endsWith('.polar.sh') && url.pathname.includes('/portal');
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ function extractCheckoutUrlFromResponse(data, sandbox = false) {
66
+ if (!data || typeof data !== 'object') return null;
67
+
68
+ const webBase = getPolarWebBase(sandbox);
69
+ const candidates = [
70
+ data.url,
71
+ data.checkout_url,
72
+ data.checkout?.url,
73
+ ].filter(Boolean);
74
+
75
+ for (const candidate of candidates) {
76
+ const normalized = candidate.startsWith('/')
77
+ ? `${webBase}${candidate}`
78
+ : candidate;
79
+ if (isValidCheckoutRedirectUrl(normalized)) {
80
+ return normalized;
81
+ }
82
+ }
83
+
84
+ if (data.client_secret) {
85
+ const built = `${webBase}/checkout/${data.client_secret}`;
86
+ if (isValidCheckoutRedirectUrl(built)) {
87
+ return built;
88
+ }
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ function polarCspDirectives(opts = {}) {
95
+ const domains = opts.domains || [
96
+ 'https://buy.polar.sh',
97
+ 'https://sandbox-buy.polar.sh',
98
+ 'https://polar.sh',
99
+ 'https://sandbox.polar.sh',
100
+ 'https://api.polar.sh',
101
+ 'https://sandbox-api.polar.sh',
102
+ ];
103
+ return {
104
+ formAction: domains,
105
+ connectSrc: domains,
106
+ };
107
+ }
108
+
109
+ module.exports = {
110
+ getPolarBuyBase,
111
+ getPolarWebBase,
112
+ isBlockedPolarUrl,
113
+ isValidCheckoutRedirectUrl,
114
+ isValidPortalRedirectUrl,
115
+ extractCheckoutUrlFromResponse,
116
+ polarCspDirectives,
117
+ };