@omelhorsite/sdk 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -131
- package/dist/index.js +1049 -1246
- package/dist/types/auth/device.d.ts +1 -1
- package/dist/types/auth/index.d.ts +2 -2
- package/dist/types/auth/tokens.d.ts +15 -15
- package/dist/types/client.d.ts +10 -10
- package/dist/types/errors.d.ts +12 -15
- package/dist/types/http.d.ts +74 -118
- package/dist/types/index.d.ts +2 -2
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/local/qr.d.ts +1 -1
- package/dist/types/local/wordlist.d.ts +2 -3
- package/dist/types/resources/account.d.ts +19 -19
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +17 -13
- package/dist/types/resources/auth/index.d.ts +1 -1
- package/dist/types/resources/auth/passkeys.d.ts +127 -163
- package/dist/types/resources/auth/sessions.d.ts +116 -161
- package/dist/types/resources/chests.d.ts +27 -31
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +41 -236
- package/dist/types/resources/dynamicQrs.d.ts +29 -45
- package/dist/types/resources/forms.d.ts +37 -58
- package/dist/types/resources/jobs.d.ts +35 -44
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +20 -22
- package/dist/types/resources/media.d.ts +48 -61
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +27 -53
- package/dist/types/resources/music/artists.d.ts +182 -246
- package/dist/types/resources/music/imports.d.ts +190 -218
- package/dist/types/resources/music/index.d.ts +8 -7
- package/dist/types/resources/music/playlists.d.ts +85 -117
- package/dist/types/resources/music/social.d.ts +153 -228
- package/dist/types/resources/music/songs.d.ts +165 -227
- package/dist/types/resources/realtime.d.ts +75 -88
- package/dist/types/resources/shortLinks.d.ts +38 -47
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +21 -3
- package/dist/types/resources/storage/upload.d.ts +42 -56
- package/dist/types/resources/storage.d.ts +94 -109
- package/dist/types/resources/tickets.d.ts +19 -10
- package/dist/types/resources/tools/backgroundRemoval.d.ts +11 -13
- package/dist/types/resources/tools/captions.d.ts +107 -135
- package/dist/types/resources/tools/upscale.d.ts +12 -16
- package/dist/types/types.d.ts +29 -38
- package/package.json +1 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `realtime` namespace:
|
|
2
|
+
* The `realtime` namespace: Action Cable over a raw WebSocket.
|
|
3
3
|
*
|
|
4
|
-
* This is the only transport in the SDK that is not HTTP
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* and
|
|
9
|
-
*
|
|
10
|
-
* eighty lines of protocol it implements.
|
|
4
|
+
* This is the only transport in the SDK that is not HTTP. Everything else
|
|
5
|
+
* talks to `/`-rooted JSON endpoints through {@link ApiClient}; this talks the
|
|
6
|
+
* Action Cable v1 wire protocol to `/cable`, and it deliberately does NOT
|
|
7
|
+
* depend on `@rails/actioncable` - that package assumes a browser, pulls in
|
|
8
|
+
* its own global logger, and is bigger than the eighty lines of protocol it
|
|
9
|
+
* implements.
|
|
11
10
|
*
|
|
12
11
|
* ## The protocol, in full
|
|
13
12
|
*
|
|
@@ -36,46 +35,41 @@
|
|
|
36
35
|
* ## The identifier is a string, and it is compared as one
|
|
37
36
|
*
|
|
38
37
|
* `identifier` is a JSON-encoded string such as `'{"channel":"PlaybackChannel"}'`.
|
|
39
|
-
* The server echoes back the exact bytes it received and
|
|
40
|
-
* frames by looking that string up
|
|
38
|
+
* The server echoes back the exact bytes it received and routes incoming
|
|
39
|
+
* frames by looking that string up. Re-serialising with the keys in a
|
|
41
40
|
* different order produces a different string, the lookup misses, and the
|
|
42
41
|
* symptom is not an error: it is a subscription that confirms and then never
|
|
43
42
|
* receives anything. So this module builds the identifier ONCE per
|
|
44
43
|
* subscription, keys its own registry by that same string, and never rebuilds
|
|
45
44
|
* it - including across a reconnect.
|
|
46
45
|
*
|
|
47
|
-
* ## Four traps
|
|
46
|
+
* ## Four traps
|
|
48
47
|
*
|
|
49
48
|
* 1. **The handshake authenticates on the FIRST candidate, with no fallback.**
|
|
50
|
-
* `
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* allowlist, so neither the 600/min authed ceiling nor the 120/min anonymous
|
|
75
|
-
* one applies to the handshake or to anything sent over the socket. That is
|
|
76
|
-
* a convenience for `position_tick` at 1 Hz, not a licence: the reconnect
|
|
77
|
-
* backoff below exists because an unthrottled reconnect loop against a
|
|
78
|
-
* restarting server is a self-inflicted denial of service.
|
|
49
|
+
* The `Authorization` header, then `?token=`, then the session cookie: the
|
|
50
|
+
* handshake takes the first one that is merely NON-BLANK. The HTTP API
|
|
51
|
+
* tries each candidate until one resolves to a live session; the cable does
|
|
52
|
+
* not. A stale `Authorization` header therefore shadows a perfectly good
|
|
53
|
+
* cookie on the cable and on the cable only. This module never sends an
|
|
54
|
+
* `Authorization` header on the handshake (browsers cannot attach one to a
|
|
55
|
+
* WebSocket anyway) and puts the token in the query string.
|
|
56
|
+
* 2. **Anonymous connections are ACCEPTED.** A bad token, an expired token, or
|
|
57
|
+
* no token at all produces a perfectly healthy socket that says `welcome`
|
|
58
|
+
* and then pings forever. The identity failure surfaces ONE LEVEL DOWN, as
|
|
59
|
+
* `reject_subscription` on each channel that needs a user. A client that
|
|
60
|
+
* only handles connection errors will sit there believing it is signed in.
|
|
61
|
+
* Handle {@link CableHandlers.onReject}, always.
|
|
62
|
+
* 3. **Only session tokens work.** An OAuth access token that authenticates
|
|
63
|
+
* every REST call in this SDK produces an ANONYMOUS cable connection, i.e.
|
|
64
|
+
* trap 2. There is no scope that fixes this and no error that says it; you
|
|
65
|
+
* get silent rejections on every channel. Cable access needs a session
|
|
66
|
+
* token or the session cookie.
|
|
67
|
+
* 4. **`/cable` is exempt from the rate limits.** Neither the 600 requests a
|
|
68
|
+
* minute authed ceiling nor the 120 a minute anonymous one applies to the
|
|
69
|
+
* handshake or to anything sent over the socket. That is a convenience for
|
|
70
|
+
* `position_tick` at 1 Hz, not a licence: the reconnect backoff below
|
|
71
|
+
* exists because an unthrottled reconnect loop against a restarting server
|
|
72
|
+
* is a self-inflicted denial of service.
|
|
79
73
|
*
|
|
80
74
|
* ## Injecting the socket
|
|
81
75
|
*
|
|
@@ -254,7 +248,7 @@ export interface CableSubscription {
|
|
|
254
248
|
readonly active: boolean;
|
|
255
249
|
}
|
|
256
250
|
/**
|
|
257
|
-
* Every channel
|
|
251
|
+
* Every channel the server exposes, as of this writing.
|
|
258
252
|
*
|
|
259
253
|
* There are exactly five, and none of them is generic: each has its own
|
|
260
254
|
* subscription params, its own rejection rule, and its own message vocabulary.
|
|
@@ -273,26 +267,22 @@ export type CableChannelName = (typeof CABLE_CHANNELS)[number];
|
|
|
273
267
|
/**
|
|
274
268
|
* The playback state as the cable serialises it.
|
|
275
269
|
*
|
|
276
|
-
* **
|
|
277
|
-
* `API.md` says flatly that "on the cable, song ids and queue entries are
|
|
278
|
-
* STRINGS". Half of that is true and the Rails source says which half:
|
|
270
|
+
* **Two id fields, two types.**
|
|
279
271
|
*
|
|
280
|
-
* - `queue` is
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* NUMBER. It is not a string, and code that does `state.song_id === queue[i]`
|
|
286
|
-
* compares a number to a string and silently never matches.
|
|
272
|
+
* - `queue` really is `string[]` coming back, whatever the client sent: the
|
|
273
|
+
* server stringifies it on write;
|
|
274
|
+
* - `song_id` is sent as a string and comes back as a JSON NUMBER. Code that
|
|
275
|
+
* does `state.song_id === queue[i]` compares a number to a string and
|
|
276
|
+
* silently never matches.
|
|
287
277
|
*
|
|
288
278
|
* The rule that does hold everywhere is: normalise with `String(...)` at the
|
|
289
279
|
* boundary and compare strings. This SDK types the fields as the wire actually
|
|
290
|
-
* carries them
|
|
291
|
-
*
|
|
280
|
+
* carries them, so the mismatch is a type error at your call site instead of a
|
|
281
|
+
* bug at runtime.
|
|
292
282
|
*/
|
|
293
283
|
export interface PlaybackSnapshotState {
|
|
294
284
|
readonly active_device_id: string | null;
|
|
295
|
-
/** v1 shim,
|
|
285
|
+
/** v1 shim, scheduled for removal. */
|
|
296
286
|
readonly active_session_id: string | null;
|
|
297
287
|
/** A NUMBER, not a string. See the note on this interface. */
|
|
298
288
|
readonly song_id: number | null;
|
|
@@ -321,7 +311,7 @@ export interface PlaybackSnapshotState {
|
|
|
321
311
|
readonly vocal_volume: number;
|
|
322
312
|
readonly instrumental_volume: number;
|
|
323
313
|
/**
|
|
324
|
-
* The full
|
|
314
|
+
* The full record of every queued song - by far the heaviest part of the
|
|
325
315
|
* payload, and OMITTED whenever the queue itself did not change.
|
|
326
316
|
*
|
|
327
317
|
* This is the single most misread field on the cable. `state_changed` for a
|
|
@@ -439,11 +429,11 @@ export type PlaybackMessage = {
|
|
|
439
429
|
readonly reason: string;
|
|
440
430
|
};
|
|
441
431
|
/**
|
|
442
|
-
* The remote-control vocabulary
|
|
432
|
+
* The remote-control vocabulary.
|
|
443
433
|
*
|
|
444
434
|
* A name outside this set is answered with `error: unknown_command` and never
|
|
445
|
-
* reaches a broadcast. `jam_add_song` is missing on purpose:
|
|
446
|
-
*
|
|
435
|
+
* reaches a broadcast. `jam_add_song` is missing on purpose: the server builds
|
|
436
|
+
* it itself so a client cannot forge a jam proposal.
|
|
447
437
|
*/
|
|
448
438
|
export type PlaybackCommandName = "play" | "pause" | "toggle" | "next" | "previous" | "seek" | "set_queue_index" | "set_queue_order" | "set_shuffle" | "set_loop_mode" | "set_volume" | "add_to_queue" | "play_next" | "remove_from_queue" | "reorder_queue";
|
|
449
439
|
/** Subscription params for `PlaybackChannel`. */
|
|
@@ -477,7 +467,7 @@ export interface PlaybackSubscribeParams {
|
|
|
477
467
|
* `perform` is still there for anything added server-side before this file
|
|
478
468
|
* catches up, but every action the channel defines today has a method, because
|
|
479
469
|
* the argument shapes are validated strictly and a typo in an action name is
|
|
480
|
-
* silently ignored by
|
|
470
|
+
* silently ignored by the server rather than reported.
|
|
481
471
|
*/
|
|
482
472
|
export interface PlaybackSubscription extends CableSubscription {
|
|
483
473
|
/**
|
|
@@ -580,7 +570,7 @@ export type JamMessage = {
|
|
|
580
570
|
readonly jam: unknown;
|
|
581
571
|
} | {
|
|
582
572
|
readonly type: "song_proposed";
|
|
583
|
-
/** `song.id` is an INTEGER here
|
|
573
|
+
/** `song.id` is an INTEGER here. */
|
|
584
574
|
readonly song: {
|
|
585
575
|
readonly id: number;
|
|
586
576
|
readonly title: string;
|
|
@@ -593,7 +583,7 @@ export type JamMessage = {
|
|
|
593
583
|
};
|
|
594
584
|
} | {
|
|
595
585
|
readonly type: "skip_votes";
|
|
596
|
-
/** The host's `
|
|
586
|
+
/** The host's playback `song_id`: a NUMBER, not a string. */
|
|
597
587
|
readonly song_id: number | null;
|
|
598
588
|
readonly count: number;
|
|
599
589
|
readonly needed: number;
|
|
@@ -646,7 +636,7 @@ export type FriendListeningMessage = {
|
|
|
646
636
|
export interface JobMessage {
|
|
647
637
|
/** Always `"snapshot"`, on subscribe and on every change alike. */
|
|
648
638
|
readonly type: "snapshot";
|
|
649
|
-
/** The
|
|
639
|
+
/** The job record, as `oms.jobs.get` answers it. Finished when `finished_at` is non-null. */
|
|
650
640
|
readonly job: unknown;
|
|
651
641
|
}
|
|
652
642
|
/** Subscription params for `JobChannel`. */
|
|
@@ -670,16 +660,16 @@ export type NotificationsMessage = {
|
|
|
670
660
|
readonly unread_count: number;
|
|
671
661
|
} | {
|
|
672
662
|
readonly type: "created";
|
|
673
|
-
/** The
|
|
663
|
+
/** The notification record. */
|
|
674
664
|
readonly notification: unknown;
|
|
675
665
|
readonly unread_count: number;
|
|
676
666
|
};
|
|
677
667
|
/**
|
|
678
668
|
* One multiplexed cable connection.
|
|
679
669
|
*
|
|
680
|
-
* All five channels share a single WebSocket
|
|
681
|
-
*
|
|
682
|
-
*
|
|
670
|
+
* All five channels share a single WebSocket; the `identifier` is the
|
|
671
|
+
* demultiplexing key. Open one of these per identity and keep it for the
|
|
672
|
+
* lifetime of the session.
|
|
683
673
|
*
|
|
684
674
|
* ## What a reconnect does to your subscriptions
|
|
685
675
|
*
|
|
@@ -694,13 +684,13 @@ export type NotificationsMessage = {
|
|
|
694
684
|
* valid across all of it - you never re-subscribe by hand and you must not, or
|
|
695
685
|
* you will end up with the same identifier registered twice.
|
|
696
686
|
*
|
|
697
|
-
* The thing to expect is that server-side subscribe-time work RUNS AGAIN
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
* resubscribe
|
|
687
|
+
* The thing to expect is that server-side subscribe-time work RUNS AGAIN.
|
|
688
|
+
* Concretely: `PlaybackChannel` re-registers the device and re-broadcasts
|
|
689
|
+
* `devices_changed`, every channel re-transmits its snapshot, and
|
|
690
|
+
* `FriendListeningChannel` re-reads the friend roster. That last one is
|
|
691
|
+
* load-bearing in the other direction too - the roster is fixed at subscribe
|
|
692
|
+
* time, so a new friend or a privacy flip only appears after a resubscribe;
|
|
693
|
+
* resubscribe it on foreground.
|
|
704
694
|
*
|
|
705
695
|
* The thing that does NOT happen is a replay of anything you sent. Frames sent
|
|
706
696
|
* while disconnected are dropped, not queued: see {@link CableSubscription.perform}.
|
|
@@ -774,9 +764,9 @@ export declare function cableEndpoint(baseUrl: string, path?: string): string;
|
|
|
774
764
|
*
|
|
775
765
|
* The token goes in the query and NOWHERE else. A browser cannot put a header
|
|
776
766
|
* on a WebSocket handshake at all, and on the runtimes that can, doing so would
|
|
777
|
-
* hit trap 1:
|
|
778
|
-
*
|
|
779
|
-
*
|
|
767
|
+
* hit trap 1: the handshake takes the first non-blank candidate, header first,
|
|
768
|
+
* so any header at all - including a stale one - decides the identity and the
|
|
769
|
+
* query param is never consulted.
|
|
780
770
|
*
|
|
781
771
|
* An empty string is treated as no credential, which is the cookie-auth case:
|
|
782
772
|
* the handshake goes out bare and the browser attaches the httpOnly session
|
|
@@ -789,29 +779,26 @@ export declare function handshakeUrl(endpoint: string, token: string | null | un
|
|
|
789
779
|
* It holds no socket of its own. {@link connect} builds one and hands it back,
|
|
790
780
|
* so a host that needs two identities (a signed-in user and an anonymous job
|
|
791
781
|
* watcher) gets two connections rather than a hidden singleton it cannot
|
|
792
|
-
* separate.
|
|
793
|
-
* implementations, which each keep a module-level singleton - fine for one app,
|
|
794
|
-
* wrong for a library.
|
|
782
|
+
* separate.
|
|
795
783
|
*
|
|
796
784
|
* ## Why the credential is passed in and not taken from the client
|
|
797
785
|
*
|
|
798
786
|
* `Oms` already holds a credential, and this namespace deliberately does not
|
|
799
|
-
* reach into it. Two reasons
|
|
800
|
-
* taste:
|
|
787
|
+
* reach into it. Two reasons:
|
|
801
788
|
*
|
|
802
|
-
* - the cable resolves ONLY
|
|
803
|
-
*
|
|
804
|
-
*
|
|
789
|
+
* - the cable resolves ONLY session tokens, so the OAuth access token an `Oms`
|
|
790
|
+
* may be carrying is not a cable credential at all - silently reusing it
|
|
791
|
+
* would hand you an anonymous connection;
|
|
805
792
|
* - `sessionCookie: true` clients have no token to reuse, and their handshake
|
|
806
793
|
* needs no `?token=` because the browser sends the cookie. For those, pass
|
|
807
794
|
* nothing.
|
|
808
795
|
*
|
|
809
796
|
* ## Rate limits
|
|
810
797
|
*
|
|
811
|
-
* None. `/cable` is
|
|
812
|
-
* ceiling nor the 120
|
|
813
|
-
* frame after it. The backoff in {@link CableConnectOptions.reconnectMaxMs}
|
|
814
|
-
* therefore the only thing standing between a restarting server and a
|
|
798
|
+
* None. `/cable` is exempt, so neither the 600 requests a minute authed
|
|
799
|
+
* ceiling nor the 120 a minute anonymous one applies to the handshake or to
|
|
800
|
+
* any frame after it. The backoff in {@link CableConnectOptions.reconnectMaxMs}
|
|
801
|
+
* is therefore the only thing standing between a restarting server and a
|
|
815
802
|
* reconnect storm; it is not decorative.
|
|
816
803
|
*/
|
|
817
804
|
export declare class RealtimeNamespace extends Resource {
|
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `shortLinks` namespace: URL shortening plus per-link click statistics.
|
|
3
3
|
*
|
|
4
|
-
* This module is the reference implementation of the resource pattern. Every
|
|
5
|
-
* other resource in the SDK is shaped exactly like it, and CONTRACT.md quotes
|
|
6
|
-
* it verbatim. Change the pattern here and change it everywhere, or do not
|
|
7
|
-
* change it at all.
|
|
8
|
-
*
|
|
9
4
|
* Creating a link works anonymously; listing, editing and statistics need a
|
|
10
5
|
* credential. Creation is the most tightly throttled write in the whole API -
|
|
11
6
|
* see {@link ShortLinksNamespace.create} before you spend one.
|
|
12
7
|
*
|
|
13
8
|
* There is deliberately no `get(id)` here, and its absence is the API's, not
|
|
14
|
-
* an omission:
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* find it in {@link ShortLinksNamespace.list} - or by endpoint with
|
|
9
|
+
* an omission: `GET /short_links/:id` does not exist and answers 404 for every
|
|
10
|
+
* id, including your own links. To read one link, find it in
|
|
11
|
+
* {@link ShortLinksNamespace.list} - or by endpoint with
|
|
18
12
|
* {@link ShortLinksNamespace.resolve}, which explains what that costs.
|
|
19
13
|
*/
|
|
20
14
|
import { Resource } from "../http";
|
|
21
|
-
import
|
|
22
|
-
|
|
15
|
+
import type { ListParams } from "../listing";
|
|
16
|
+
import { type BaseRecord, type Id, type Json, type Paginated, type RequestOptions, type Timestamp } from "../types";
|
|
17
|
+
/** Public host short links are served from. See {@link ShortLinksNamespace.shortUrl}. */
|
|
23
18
|
export declare const SHORT_LINK_BASE_URL = "https://omelhor.site";
|
|
24
19
|
/**
|
|
25
20
|
* Primary key of a short link.
|
|
26
21
|
*
|
|
27
|
-
* Short links predate the string ids the rest of the API uses: the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
22
|
+
* Short links predate the string ids the rest of the API uses: the JSON
|
|
23
|
+
* carries a **number** here while the `user_id` right next to it is a string.
|
|
24
|
+
* Every method accepts either form and interpolates it into the path, so a
|
|
25
|
+
* caller never has to care.
|
|
31
26
|
*/
|
|
32
27
|
export type ShortLinkId = Id | number;
|
|
33
28
|
/** One recorded visit to a short link. */
|
|
@@ -41,15 +36,15 @@ export interface ShortLinkClick {
|
|
|
41
36
|
readonly device_name: string | null;
|
|
42
37
|
}
|
|
43
38
|
/**
|
|
44
|
-
* The owner of a link, rendered inline
|
|
39
|
+
* The owner of a link, rendered inline.
|
|
45
40
|
*
|
|
46
|
-
* Only the keys that
|
|
47
|
-
*
|
|
48
|
-
* `
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
41
|
+
* Only the keys that are always present are named. `group`, `email`,
|
|
42
|
+
* `gender`, `last_seen_at`, `sessions_count`, `deactivated_at`,
|
|
43
|
+
* `allowed_to_use_spotify` and `share_listening` appear or not depending on
|
|
44
|
+
* who is ASKING - the caller's own privileges rather than the record. That is
|
|
45
|
+
* why they are reached through the index signature instead of being declared
|
|
46
|
+
* here as optionals: an optional would suggest the record decides, and it does
|
|
47
|
+
* not.
|
|
53
48
|
*/
|
|
54
49
|
export interface ShortLinkOwner {
|
|
55
50
|
readonly id: Id;
|
|
@@ -68,11 +63,6 @@ export interface ShortLinkOwner {
|
|
|
68
63
|
* {@link ShortLinkId} for why the identifier is a number on this one table.
|
|
69
64
|
*
|
|
70
65
|
* Every key below is present on every response this namespace can produce.
|
|
71
|
-
* `ShortLinkBlueprint` does declare a second, narrower `:admin` view - it drops
|
|
72
|
-
* `updated_at`, both associations, and adds `owner`, `click_count` and
|
|
73
|
-
* `last_click_at` - but that view is only ever rendered by the admin
|
|
74
|
-
* shortlinks tool under `/admin/short_links`, which is not this resource. A
|
|
75
|
-
* record that arrived here has the full default shape.
|
|
76
66
|
*/
|
|
77
67
|
export interface ShortLink extends Omit<BaseRecord, "id"> {
|
|
78
68
|
/** Integer primary key. See {@link ShortLinkId}. */
|
|
@@ -86,22 +76,21 @@ export interface ShortLink extends Omit<BaseRecord, "id"> {
|
|
|
86
76
|
* internal tools reserve `"n"` (notepads), `"c"` (chests), `"ss"` (storage
|
|
87
77
|
* shares), `"qr"` (dynamic QR), `"f"` (forms) and `"t"` (link trees). That
|
|
88
78
|
* is why {@link ShortLink} and `DynamicQr` are separate resources even
|
|
89
|
-
* though both are
|
|
79
|
+
* though both are short links.
|
|
90
80
|
*
|
|
91
81
|
* A link that reached you through {@link ShortLinksNamespace.list} always
|
|
92
|
-
* holds `null` or `""`, because the listing
|
|
82
|
+
* holds `null` or `""`, because the listing only returns user-managed links.
|
|
93
83
|
*/
|
|
94
84
|
readonly namespace: string | null;
|
|
95
85
|
/** Owner, or `null` for a link created anonymously. */
|
|
96
86
|
readonly user_id: Id | null;
|
|
97
87
|
/**
|
|
98
|
-
* EVERY click ever recorded, inlined
|
|
99
|
-
*
|
|
88
|
+
* EVERY click ever recorded, inlined - not a count, not a page. A link with
|
|
89
|
+
* 50 000 visits sends 50 000 objects here. Use
|
|
100
90
|
* {@link ShortLinksNamespace.stats} for anything analytical and treat this
|
|
101
91
|
* field as a payload hazard, not as a feature.
|
|
102
92
|
*
|
|
103
|
-
* Always present, and `[]` rather than `null` for a link nobody has clicked
|
|
104
|
-
* a Blueprinter association over an empty `has_many` renders an empty array.
|
|
93
|
+
* Always present, and `[]` rather than `null` for a link nobody has clicked.
|
|
105
94
|
*/
|
|
106
95
|
readonly short_link_clicks: ShortLinkClick[];
|
|
107
96
|
/** The owner rendered inline, or `null` for a link created anonymously. */
|
|
@@ -129,7 +118,7 @@ export interface ShortLinkDeviceClicks {
|
|
|
129
118
|
* `GET /dynamic_qrs/:id/stats` returns.
|
|
130
119
|
*
|
|
131
120
|
* Five keys, always all five. There is no referrer breakdown and no unique
|
|
132
|
-
* visitor count: the
|
|
121
|
+
* visitor count: the server stores neither.
|
|
133
122
|
*/
|
|
134
123
|
export interface ShortLinkStats {
|
|
135
124
|
readonly total_clicks: number;
|
|
@@ -150,8 +139,8 @@ export interface ShortLinkStats {
|
|
|
150
139
|
/** Arguments for creating a short link. */
|
|
151
140
|
export interface CreateShortLinkInput {
|
|
152
141
|
/**
|
|
153
|
-
* Absolute destination URL. Must be `http` or `https` and must parse
|
|
154
|
-
*
|
|
142
|
+
* Absolute destination URL. Must be `http` or `https` and must parse as a
|
|
143
|
+
* URL; anything else is a 400, not a silent rewrite.
|
|
155
144
|
*/
|
|
156
145
|
readonly url: string;
|
|
157
146
|
/**
|
|
@@ -169,14 +158,16 @@ export interface UpdateShortLinkInput {
|
|
|
169
158
|
readonly url?: string;
|
|
170
159
|
readonly endpoint?: string;
|
|
171
160
|
}
|
|
161
|
+
/** Filter columns of `GET /short_links`, on top of {@link BASE_FILTER_COLUMNS}. */
|
|
162
|
+
export declare const SHORT_LINK_FILTER_COLUMNS: readonly ["user_id"];
|
|
172
163
|
/** Filters for {@link ShortLinksNamespace.list}. */
|
|
173
|
-
export interface ListShortLinksParams extends
|
|
164
|
+
export interface ListShortLinksParams extends ListParams<(typeof SHORT_LINK_FILTER_COLUMNS)[number]> {
|
|
174
165
|
/**
|
|
175
166
|
* Narrow to one owner, sent as `exact_search[user_id]`.
|
|
176
167
|
*
|
|
177
168
|
* The listing is already scoped to the caller server-side, so this can only
|
|
178
|
-
* ever narrow your own links to yourself or to nothing.
|
|
179
|
-
*
|
|
169
|
+
* ever narrow your own links to yourself or to nothing. There is no admin
|
|
170
|
+
* escape hatch here.
|
|
180
171
|
*/
|
|
181
172
|
readonly userId?: Id;
|
|
182
173
|
}
|
|
@@ -203,10 +194,10 @@ export declare class ShortLinksNamespace extends Resource {
|
|
|
203
194
|
/**
|
|
204
195
|
* `POST /short_links` - shortens a URL.
|
|
205
196
|
*
|
|
206
|
-
* **Rate limit, read this before you spend one:**
|
|
207
|
-
* **10 creations per hour per IP
|
|
208
|
-
*
|
|
209
|
-
*
|
|
197
|
+
* **Rate limit, read this before you spend one:** the server allows
|
|
198
|
+
* **10 creations per hour per IP**, and the rule is keyed by IP for
|
|
199
|
+
* EVERYONE - being signed in does not buy you a bigger budget. The 11th call
|
|
200
|
+
* in an hour answers `429` with a `Retry-After` header
|
|
210
201
|
* measured in whatever is left of that hour, which arrives here as an
|
|
211
202
|
* {@link OmsQuotaError} with `retryAfterMs` set. An agent that shortens URLs
|
|
212
203
|
* in a loop will burn the whole hour's budget in about a second, so batch the
|
|
@@ -247,7 +238,7 @@ export declare class ShortLinksNamespace extends Resource {
|
|
|
247
238
|
/**
|
|
248
239
|
* `GET /short_links/:id/stats` - totals plus a fixed 30-day daily histogram.
|
|
249
240
|
*
|
|
250
|
-
* The window is not configurable: the
|
|
241
|
+
* The window is not configurable: the server always buckets the last 30
|
|
251
242
|
* days. For anything else, read {@link ShortLink.short_link_clicks} off the
|
|
252
243
|
* record and bucket it yourself - at the cost noted on that field.
|
|
253
244
|
*
|
|
@@ -259,8 +250,8 @@ export declare class ShortLinksNamespace extends Resource {
|
|
|
259
250
|
/**
|
|
260
251
|
* Finds one of YOUR links by its endpoint, without counting a click.
|
|
261
252
|
*
|
|
262
|
-
* The public
|
|
263
|
-
*
|
|
253
|
+
* The public redirect records a visit before it redirects, so using it to
|
|
254
|
+
* read a destination would quietly corrupt the owner's statistics. The
|
|
264
255
|
* API offers no read-by-endpoint, and `search[endpoint]` is not on the
|
|
265
256
|
* allowlist (an unknown filter key is a 400, not a wider result), so the only
|
|
266
257
|
* honest implementation is to page through your own listing and match
|