@drawbridge/drawbridge-utils 0.0.90 → 0.0.92

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/plans.cjs CHANGED
@@ -423,8 +423,8 @@ var plans = {
423
423
  }
424
424
  };
425
425
  var conversionRate = (subscription) => {
426
- var _a;
427
- return (subscription == null ? void 0 : subscription.conversion) ?? ((_a = plans[subscription == null ? void 0 : subscription.plan]) == null ? void 0 : _a.conversion) ?? free.conversion;
426
+ var _a, _b;
427
+ return ((_a = subscription == null ? void 0 : subscription.overrides) == null ? void 0 : _a.conversion) ?? (subscription == null ? void 0 : subscription.conversion) ?? ((_b = plans[subscription == null ? void 0 : subscription.plan]) == null ? void 0 : _b.conversion) ?? free.conversion;
428
428
  };
429
429
  // Annotate the CommonJS export names for ESM import in node:
430
430
  0 && (module.exports = {
package/dist/plans.d.cts CHANGED
@@ -196,11 +196,14 @@ const plans = {
196
196
  };
197
197
 
198
198
  // Resolve the conversion-fee percentage for an order's owning org.
199
- // Precedence: explicit per-subscription override (admin/negotiated)
200
- // the plan's catalog default → the free-org rate. `subscription` is null
201
- // for free orgs (they have no subscription doc).
199
+ // Precedence: explicit per-subscription override (admin/negotiated) in
200
+ // subscription.overrides → the legacy top-level field (pre-migration docs;
201
+ // remove after the overrides cleanup release) the plan's catalog default →
202
+ // the free-org rate. `??` throughout — a stored 0 is a valid 0% override.
203
+ // `subscription` is null for free orgs (they have no subscription doc).
202
204
  const conversionRate = ( subscription ) =>
203
- subscription?.conversion
205
+ subscription?.overrides?.conversion
206
+ ?? subscription?.conversion
204
207
  ?? plans[ subscription?.plan ]?.conversion
205
208
  ?? free.conversion;
206
209
 
package/dist/plans.d.ts CHANGED
@@ -196,11 +196,14 @@ const plans = {
196
196
  };
197
197
 
198
198
  // Resolve the conversion-fee percentage for an order's owning org.
199
- // Precedence: explicit per-subscription override (admin/negotiated)
200
- // the plan's catalog default → the free-org rate. `subscription` is null
201
- // for free orgs (they have no subscription doc).
199
+ // Precedence: explicit per-subscription override (admin/negotiated) in
200
+ // subscription.overrides → the legacy top-level field (pre-migration docs;
201
+ // remove after the overrides cleanup release) the plan's catalog default →
202
+ // the free-org rate. `??` throughout — a stored 0 is a valid 0% override.
203
+ // `subscription` is null for free orgs (they have no subscription doc).
202
204
  const conversionRate = ( subscription ) =>
203
- subscription?.conversion
205
+ subscription?.overrides?.conversion
206
+ ?? subscription?.conversion
204
207
  ?? plans[ subscription?.plan ]?.conversion
205
208
  ?? free.conversion;
206
209
 
package/dist/plans.js CHANGED
@@ -386,8 +386,8 @@ var plans = {
386
386
  }
387
387
  };
388
388
  var conversionRate = (subscription) => {
389
- var _a;
390
- return (subscription == null ? void 0 : subscription.conversion) ?? ((_a = plans[subscription == null ? void 0 : subscription.plan]) == null ? void 0 : _a.conversion) ?? free.conversion;
389
+ var _a, _b;
390
+ return ((_a = subscription == null ? void 0 : subscription.overrides) == null ? void 0 : _a.conversion) ?? (subscription == null ? void 0 : subscription.conversion) ?? ((_b = plans[subscription == null ? void 0 : subscription.plan]) == null ? void 0 : _b.conversion) ?? free.conversion;
391
391
  };
392
392
  export {
393
393
  conversionRate,
@@ -0,0 +1,158 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // lib/social.js
20
+ var social_exports = {};
21
+ __export(social_exports, {
22
+ matchSocial: () => matchSocial,
23
+ platforms: () => platforms
24
+ });
25
+ module.exports = __toCommonJS(social_exports);
26
+ var segment = /^\/.+/;
27
+ var platforms = [
28
+ {
29
+ platform: "instagram",
30
+ label: "Instagram",
31
+ hosts: ["instagram.com"],
32
+ requirePath: true,
33
+ // content permalinks, not profiles
34
+ exclude: /^\/(p|reel|reels|stories|explore)(\/|$)/
35
+ },
36
+ {
37
+ platform: "facebook",
38
+ label: "Facebook",
39
+ hosts: ["facebook.com", "fb.com"],
40
+ requirePath: true,
41
+ exclude: /^\/(sharer|share|dialog|plugins)(\/|\.|$)/
42
+ },
43
+ {
44
+ platform: "tiktok",
45
+ label: "TikTok",
46
+ hosts: ["tiktok.com"],
47
+ path: /^\/@./
48
+ },
49
+ {
50
+ platform: "youtube",
51
+ label: "YouTube",
52
+ hosts: [
53
+ { host: "youtube.com", path: /^\/(@.|channel\/|c\/|user\/)/ },
54
+ { host: "youtu.be", requirePath: true }
55
+ ]
56
+ },
57
+ {
58
+ platform: "x",
59
+ label: "X",
60
+ hosts: ["x.com", "twitter.com"],
61
+ requirePath: true,
62
+ exclude: /^\/(intent|share)(\/|$)/
63
+ },
64
+ {
65
+ platform: "linkedin",
66
+ label: "LinkedIn",
67
+ hosts: ["linkedin.com"],
68
+ path: /^\/(company|in)\/./
69
+ },
70
+ {
71
+ platform: "pinterest",
72
+ label: "Pinterest",
73
+ hosts: ["pinterest.com"],
74
+ requirePath: true,
75
+ exclude: /^\/pin(\/|$)/
76
+ },
77
+ {
78
+ platform: "threads",
79
+ label: "Threads",
80
+ hosts: ["threads.net", "threads.com"],
81
+ path: /^\/@./
82
+ },
83
+ {
84
+ platform: "snapchat",
85
+ label: "Snapchat",
86
+ hosts: ["snapchat.com"],
87
+ path: /^\/add\/./
88
+ },
89
+ {
90
+ platform: "whatsapp",
91
+ label: "WhatsApp",
92
+ hosts: [
93
+ { host: "wa.me", requirePath: true },
94
+ { host: "api.whatsapp.com", path: /^\/send/ }
95
+ ]
96
+ },
97
+ {
98
+ platform: "spotify",
99
+ label: "Spotify",
100
+ hosts: ["open.spotify.com"],
101
+ path: /^\/(artist|show|user|playlist)\/./
102
+ },
103
+ {
104
+ platform: "discord",
105
+ label: "Discord",
106
+ hosts: [
107
+ { host: "discord.gg", requirePath: true },
108
+ { host: "discord.com", path: /^\/invite\/./ }
109
+ ]
110
+ },
111
+ {
112
+ platform: "twitch",
113
+ label: "Twitch",
114
+ hosts: ["twitch.tv"],
115
+ requirePath: true,
116
+ exclude: /^\/(videos|directory)(\/|$)/
117
+ },
118
+ {
119
+ platform: "github",
120
+ label: "GitHub",
121
+ hosts: ["github.com"],
122
+ requirePath: true
123
+ }
124
+ ];
125
+ var matchSocial = (url) => {
126
+ if (!url || typeof url !== "string") return null;
127
+ let parsed;
128
+ try {
129
+ parsed = new URL(url.includes("://") ? url : "https://" + url.replace(/^\/+/, ""));
130
+ } catch {
131
+ return null;
132
+ }
133
+ ;
134
+ if (!/^https?:$/.test(parsed.protocol)) return null;
135
+ const host = parsed.hostname.toLowerCase().replace(/^(www|m)\./, "");
136
+ const path = parsed.pathname.replace(/\/+$/, "") || "/";
137
+ for (const entry of platforms) {
138
+ const matched = entry.hosts.map((h) => typeof h === "string" ? { host: h } : h).find((h) => host === h.host || host.endsWith("." + h.host));
139
+ if (!matched) continue;
140
+ const exclude = matched.exclude || entry.exclude;
141
+ const pattern = matched.path || entry.path;
142
+ const requirePath = matched.requirePath ?? entry.requirePath;
143
+ if (exclude && exclude.test(path)) return null;
144
+ if (pattern && !pattern.test(path)) return null;
145
+ if (requirePath && !segment.test(path)) return null;
146
+ return {
147
+ label: entry.label,
148
+ platform: entry.platform
149
+ };
150
+ }
151
+ ;
152
+ return null;
153
+ };
154
+ // Annotate the CommonJS export names for ESM import in node:
155
+ 0 && (module.exports = {
156
+ matchSocial,
157
+ platforms
158
+ });
@@ -0,0 +1,163 @@
1
+ // Social platform detection — the single source of truth for the platform-slug
2
+ // vocabulary shared across the family: sync writes link.icon from it during AI
3
+ // campaign generation, app-web derives the link form's live icon preview and
4
+ // the icon persisted on save, and @drawbridge/components keys its social glyph
5
+ // map on the same slugs. Adding a platform = one entry here + one glyph there.
6
+ //
7
+ // Order is load-bearing: it is the canonical display order for auto-created
8
+ // social links on the share page.
9
+
10
+ const segment = /^\/.+/;
11
+
12
+ const platforms = [
13
+ {
14
+ platform : 'instagram',
15
+ label : 'Instagram',
16
+ hosts : [ 'instagram.com' ],
17
+ requirePath : true,
18
+ // content permalinks, not profiles
19
+ exclude : /^\/(p|reel|reels|stories|explore)(\/|$)/
20
+ },
21
+ {
22
+ platform : 'facebook',
23
+ label : 'Facebook',
24
+ hosts : [ 'facebook.com', 'fb.com' ],
25
+ requirePath : true,
26
+ exclude : /^\/(sharer|share|dialog|plugins)(\/|\.|$)/
27
+ },
28
+ {
29
+ platform : 'tiktok',
30
+ label : 'TikTok',
31
+ hosts : [ 'tiktok.com' ],
32
+ path : /^\/@./
33
+ },
34
+ {
35
+ platform : 'youtube',
36
+ label : 'YouTube',
37
+ hosts : [
38
+ { host : 'youtube.com', path : /^\/(@.|channel\/|c\/|user\/)/ },
39
+ { host : 'youtu.be', requirePath : true }
40
+ ]
41
+ },
42
+ {
43
+ platform : 'x',
44
+ label : 'X',
45
+ hosts : [ 'x.com', 'twitter.com' ],
46
+ requirePath : true,
47
+ exclude : /^\/(intent|share)(\/|$)/
48
+ },
49
+ {
50
+ platform : 'linkedin',
51
+ label : 'LinkedIn',
52
+ hosts : [ 'linkedin.com' ],
53
+ path : /^\/(company|in)\/./
54
+ },
55
+ {
56
+ platform : 'pinterest',
57
+ label : 'Pinterest',
58
+ hosts : [ 'pinterest.com' ],
59
+ requirePath : true,
60
+ exclude : /^\/pin(\/|$)/
61
+ },
62
+ {
63
+ platform : 'threads',
64
+ label : 'Threads',
65
+ hosts : [ 'threads.net', 'threads.com' ],
66
+ path : /^\/@./
67
+ },
68
+ {
69
+ platform : 'snapchat',
70
+ label : 'Snapchat',
71
+ hosts : [ 'snapchat.com' ],
72
+ path : /^\/add\/./
73
+ },
74
+ {
75
+ platform : 'whatsapp',
76
+ label : 'WhatsApp',
77
+ hosts : [
78
+ { host : 'wa.me', requirePath : true },
79
+ { host : 'api.whatsapp.com', path : /^\/send/ }
80
+ ]
81
+ },
82
+ {
83
+ platform : 'spotify',
84
+ label : 'Spotify',
85
+ hosts : [ 'open.spotify.com' ],
86
+ path : /^\/(artist|show|user|playlist)\/./
87
+ },
88
+ {
89
+ platform : 'discord',
90
+ label : 'Discord',
91
+ hosts : [
92
+ { host : 'discord.gg', requirePath : true },
93
+ { host : 'discord.com', path : /^\/invite\/./ }
94
+ ]
95
+ },
96
+ {
97
+ platform : 'twitch',
98
+ label : 'Twitch',
99
+ hosts : [ 'twitch.tv' ],
100
+ requirePath : true,
101
+ exclude : /^\/(videos|directory)(\/|$)/
102
+ },
103
+ {
104
+ platform : 'github',
105
+ label : 'GitHub',
106
+ hosts : [ 'github.com' ],
107
+ requirePath : true
108
+ }
109
+ ];
110
+
111
+ // Match a url against the platform table. Tolerates missing schemes and
112
+ // www./m. subdomains; keystroke-safe (never throws) so app-web can call it
113
+ // on every url-field change. Returns { platform, label } or null.
114
+ const matchSocial = ( url ) => {
115
+
116
+ if( ! url || typeof url !== 'string' ) return null;
117
+
118
+ let parsed;
119
+
120
+ try {
121
+
122
+ parsed = new URL( url.includes( '://' ) ? url : 'https://' + url.replace( /^\/+/, '' ) );
123
+
124
+ } catch {
125
+
126
+ return null;
127
+
128
+ }
129
+ if( ! /^https?:$/.test( parsed.protocol ) ) return null;
130
+
131
+ const host = parsed.hostname.toLowerCase().replace( /^(www|m)\./, '' );
132
+ const path = parsed.pathname.replace( /\/+$/, '' ) || '/';
133
+
134
+ for( const entry of platforms ){
135
+
136
+ const matched = entry.hosts
137
+ .map( ( h ) => ( typeof h === 'string' ? { host : h } : h ) )
138
+ .find( ( h ) => host === h.host || host.endsWith( '.' + h.host ) );
139
+
140
+ if( ! matched ) continue;
141
+
142
+ // per-host rules override the platform-level ones (discord.gg/<invite>
143
+ // vs discord.com/invite/<invite>); a host match that fails its path
144
+ // rules is terminal — no other platform owns this host
145
+ const exclude = matched.exclude || entry.exclude;
146
+ const pattern = matched.path || entry.path;
147
+ const requirePath = matched.requirePath ?? entry.requirePath;
148
+
149
+ if( exclude && exclude.test( path ) ) return null;
150
+ if( pattern && ! pattern.test( path ) ) return null;
151
+ if( requirePath && ! segment.test( path ) ) return null;
152
+
153
+ return {
154
+ label : entry.label,
155
+ platform : entry.platform
156
+ };
157
+
158
+ }
159
+ return null;
160
+
161
+ };
162
+
163
+ export { matchSocial, platforms };
@@ -0,0 +1,163 @@
1
+ // Social platform detection — the single source of truth for the platform-slug
2
+ // vocabulary shared across the family: sync writes link.icon from it during AI
3
+ // campaign generation, app-web derives the link form's live icon preview and
4
+ // the icon persisted on save, and @drawbridge/components keys its social glyph
5
+ // map on the same slugs. Adding a platform = one entry here + one glyph there.
6
+ //
7
+ // Order is load-bearing: it is the canonical display order for auto-created
8
+ // social links on the share page.
9
+
10
+ const segment = /^\/.+/;
11
+
12
+ const platforms = [
13
+ {
14
+ platform : 'instagram',
15
+ label : 'Instagram',
16
+ hosts : [ 'instagram.com' ],
17
+ requirePath : true,
18
+ // content permalinks, not profiles
19
+ exclude : /^\/(p|reel|reels|stories|explore)(\/|$)/
20
+ },
21
+ {
22
+ platform : 'facebook',
23
+ label : 'Facebook',
24
+ hosts : [ 'facebook.com', 'fb.com' ],
25
+ requirePath : true,
26
+ exclude : /^\/(sharer|share|dialog|plugins)(\/|\.|$)/
27
+ },
28
+ {
29
+ platform : 'tiktok',
30
+ label : 'TikTok',
31
+ hosts : [ 'tiktok.com' ],
32
+ path : /^\/@./
33
+ },
34
+ {
35
+ platform : 'youtube',
36
+ label : 'YouTube',
37
+ hosts : [
38
+ { host : 'youtube.com', path : /^\/(@.|channel\/|c\/|user\/)/ },
39
+ { host : 'youtu.be', requirePath : true }
40
+ ]
41
+ },
42
+ {
43
+ platform : 'x',
44
+ label : 'X',
45
+ hosts : [ 'x.com', 'twitter.com' ],
46
+ requirePath : true,
47
+ exclude : /^\/(intent|share)(\/|$)/
48
+ },
49
+ {
50
+ platform : 'linkedin',
51
+ label : 'LinkedIn',
52
+ hosts : [ 'linkedin.com' ],
53
+ path : /^\/(company|in)\/./
54
+ },
55
+ {
56
+ platform : 'pinterest',
57
+ label : 'Pinterest',
58
+ hosts : [ 'pinterest.com' ],
59
+ requirePath : true,
60
+ exclude : /^\/pin(\/|$)/
61
+ },
62
+ {
63
+ platform : 'threads',
64
+ label : 'Threads',
65
+ hosts : [ 'threads.net', 'threads.com' ],
66
+ path : /^\/@./
67
+ },
68
+ {
69
+ platform : 'snapchat',
70
+ label : 'Snapchat',
71
+ hosts : [ 'snapchat.com' ],
72
+ path : /^\/add\/./
73
+ },
74
+ {
75
+ platform : 'whatsapp',
76
+ label : 'WhatsApp',
77
+ hosts : [
78
+ { host : 'wa.me', requirePath : true },
79
+ { host : 'api.whatsapp.com', path : /^\/send/ }
80
+ ]
81
+ },
82
+ {
83
+ platform : 'spotify',
84
+ label : 'Spotify',
85
+ hosts : [ 'open.spotify.com' ],
86
+ path : /^\/(artist|show|user|playlist)\/./
87
+ },
88
+ {
89
+ platform : 'discord',
90
+ label : 'Discord',
91
+ hosts : [
92
+ { host : 'discord.gg', requirePath : true },
93
+ { host : 'discord.com', path : /^\/invite\/./ }
94
+ ]
95
+ },
96
+ {
97
+ platform : 'twitch',
98
+ label : 'Twitch',
99
+ hosts : [ 'twitch.tv' ],
100
+ requirePath : true,
101
+ exclude : /^\/(videos|directory)(\/|$)/
102
+ },
103
+ {
104
+ platform : 'github',
105
+ label : 'GitHub',
106
+ hosts : [ 'github.com' ],
107
+ requirePath : true
108
+ }
109
+ ];
110
+
111
+ // Match a url against the platform table. Tolerates missing schemes and
112
+ // www./m. subdomains; keystroke-safe (never throws) so app-web can call it
113
+ // on every url-field change. Returns { platform, label } or null.
114
+ const matchSocial = ( url ) => {
115
+
116
+ if( ! url || typeof url !== 'string' ) return null;
117
+
118
+ let parsed;
119
+
120
+ try {
121
+
122
+ parsed = new URL( url.includes( '://' ) ? url : 'https://' + url.replace( /^\/+/, '' ) );
123
+
124
+ } catch {
125
+
126
+ return null;
127
+
128
+ }
129
+ if( ! /^https?:$/.test( parsed.protocol ) ) return null;
130
+
131
+ const host = parsed.hostname.toLowerCase().replace( /^(www|m)\./, '' );
132
+ const path = parsed.pathname.replace( /\/+$/, '' ) || '/';
133
+
134
+ for( const entry of platforms ){
135
+
136
+ const matched = entry.hosts
137
+ .map( ( h ) => ( typeof h === 'string' ? { host : h } : h ) )
138
+ .find( ( h ) => host === h.host || host.endsWith( '.' + h.host ) );
139
+
140
+ if( ! matched ) continue;
141
+
142
+ // per-host rules override the platform-level ones (discord.gg/<invite>
143
+ // vs discord.com/invite/<invite>); a host match that fails its path
144
+ // rules is terminal — no other platform owns this host
145
+ const exclude = matched.exclude || entry.exclude;
146
+ const pattern = matched.path || entry.path;
147
+ const requirePath = matched.requirePath ?? entry.requirePath;
148
+
149
+ if( exclude && exclude.test( path ) ) return null;
150
+ if( pattern && ! pattern.test( path ) ) return null;
151
+ if( requirePath && ! segment.test( path ) ) return null;
152
+
153
+ return {
154
+ label : entry.label,
155
+ platform : entry.platform
156
+ };
157
+
158
+ }
159
+ return null;
160
+
161
+ };
162
+
163
+ export { matchSocial, platforms };
package/dist/social.js ADDED
@@ -0,0 +1,133 @@
1
+ // lib/social.js
2
+ var segment = /^\/.+/;
3
+ var platforms = [
4
+ {
5
+ platform: "instagram",
6
+ label: "Instagram",
7
+ hosts: ["instagram.com"],
8
+ requirePath: true,
9
+ // content permalinks, not profiles
10
+ exclude: /^\/(p|reel|reels|stories|explore)(\/|$)/
11
+ },
12
+ {
13
+ platform: "facebook",
14
+ label: "Facebook",
15
+ hosts: ["facebook.com", "fb.com"],
16
+ requirePath: true,
17
+ exclude: /^\/(sharer|share|dialog|plugins)(\/|\.|$)/
18
+ },
19
+ {
20
+ platform: "tiktok",
21
+ label: "TikTok",
22
+ hosts: ["tiktok.com"],
23
+ path: /^\/@./
24
+ },
25
+ {
26
+ platform: "youtube",
27
+ label: "YouTube",
28
+ hosts: [
29
+ { host: "youtube.com", path: /^\/(@.|channel\/|c\/|user\/)/ },
30
+ { host: "youtu.be", requirePath: true }
31
+ ]
32
+ },
33
+ {
34
+ platform: "x",
35
+ label: "X",
36
+ hosts: ["x.com", "twitter.com"],
37
+ requirePath: true,
38
+ exclude: /^\/(intent|share)(\/|$)/
39
+ },
40
+ {
41
+ platform: "linkedin",
42
+ label: "LinkedIn",
43
+ hosts: ["linkedin.com"],
44
+ path: /^\/(company|in)\/./
45
+ },
46
+ {
47
+ platform: "pinterest",
48
+ label: "Pinterest",
49
+ hosts: ["pinterest.com"],
50
+ requirePath: true,
51
+ exclude: /^\/pin(\/|$)/
52
+ },
53
+ {
54
+ platform: "threads",
55
+ label: "Threads",
56
+ hosts: ["threads.net", "threads.com"],
57
+ path: /^\/@./
58
+ },
59
+ {
60
+ platform: "snapchat",
61
+ label: "Snapchat",
62
+ hosts: ["snapchat.com"],
63
+ path: /^\/add\/./
64
+ },
65
+ {
66
+ platform: "whatsapp",
67
+ label: "WhatsApp",
68
+ hosts: [
69
+ { host: "wa.me", requirePath: true },
70
+ { host: "api.whatsapp.com", path: /^\/send/ }
71
+ ]
72
+ },
73
+ {
74
+ platform: "spotify",
75
+ label: "Spotify",
76
+ hosts: ["open.spotify.com"],
77
+ path: /^\/(artist|show|user|playlist)\/./
78
+ },
79
+ {
80
+ platform: "discord",
81
+ label: "Discord",
82
+ hosts: [
83
+ { host: "discord.gg", requirePath: true },
84
+ { host: "discord.com", path: /^\/invite\/./ }
85
+ ]
86
+ },
87
+ {
88
+ platform: "twitch",
89
+ label: "Twitch",
90
+ hosts: ["twitch.tv"],
91
+ requirePath: true,
92
+ exclude: /^\/(videos|directory)(\/|$)/
93
+ },
94
+ {
95
+ platform: "github",
96
+ label: "GitHub",
97
+ hosts: ["github.com"],
98
+ requirePath: true
99
+ }
100
+ ];
101
+ var matchSocial = (url) => {
102
+ if (!url || typeof url !== "string") return null;
103
+ let parsed;
104
+ try {
105
+ parsed = new URL(url.includes("://") ? url : "https://" + url.replace(/^\/+/, ""));
106
+ } catch {
107
+ return null;
108
+ }
109
+ ;
110
+ if (!/^https?:$/.test(parsed.protocol)) return null;
111
+ const host = parsed.hostname.toLowerCase().replace(/^(www|m)\./, "");
112
+ const path = parsed.pathname.replace(/\/+$/, "") || "/";
113
+ for (const entry of platforms) {
114
+ const matched = entry.hosts.map((h) => typeof h === "string" ? { host: h } : h).find((h) => host === h.host || host.endsWith("." + h.host));
115
+ if (!matched) continue;
116
+ const exclude = matched.exclude || entry.exclude;
117
+ const pattern = matched.path || entry.path;
118
+ const requirePath = matched.requirePath ?? entry.requirePath;
119
+ if (exclude && exclude.test(path)) return null;
120
+ if (pattern && !pattern.test(path)) return null;
121
+ if (requirePath && !segment.test(path)) return null;
122
+ return {
123
+ label: entry.label,
124
+ platform: entry.platform
125
+ };
126
+ }
127
+ ;
128
+ return null;
129
+ };
130
+ export {
131
+ matchSocial,
132
+ platforms
133
+ };
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "tinycolor2": "1.6.0"
14
14
  },
15
15
  "devDependencies": {
16
- "@drawbridge/drawbridge-agents": "0.1.24",
16
+ "@drawbridge/drawbridge-agents": "0.1.28",
17
17
  "@node-oauth/oauth2-server": "5.3.0",
18
18
  "tsup": "8.5.1",
19
19
  "typescript": "5.9.3"
@@ -132,6 +132,11 @@
132
132
  "import": "./dist/slugify.js",
133
133
  "require": "./dist/slugify.cjs"
134
134
  },
135
+ "./social": {
136
+ "types": "./dist/social.d.ts",
137
+ "import": "./dist/social.js",
138
+ "require": "./dist/social.cjs"
139
+ },
135
140
  "./token": {
136
141
  "types": "./dist/token.d.ts",
137
142
  "import": "./dist/token.js",
@@ -164,5 +169,5 @@
164
169
  "test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
165
170
  },
166
171
  "types": "dist/index.d.ts",
167
- "version": "0.0.90"
172
+ "version": "0.0.92"
168
173
  }