@saooti/octopus-sdk 41.10.4 → 41.10.6

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/BUILD_FIX.md ADDED
@@ -0,0 +1,105 @@
1
+ # Fixing `@` alias resolution when consumed by other projects
2
+
3
+ ## Problem
4
+
5
+ `octopus-sdk` currently publishes raw TypeScript source. When a consumer (e.g. frontoffice)
6
+ bundles the SDK, it processes source files with its own Vite config, so `@/` imports resolve
7
+ against the consumer's `src/` instead of the SDK's. This affects both dev and production builds.
8
+
9
+ ## Solution: build the SDK as a library
10
+
11
+ The SDK should be pre-built so all `@` aliases are resolved before any consumer sees the files.
12
+
13
+ ---
14
+
15
+ ## 1. Install `vite-plugin-dts`
16
+
17
+ ```sh
18
+ npm install -D vite-plugin-dts
19
+ ```
20
+
21
+ ---
22
+
23
+ ## 2. Update `vite.config.js`
24
+
25
+ Add a `lib` build alongside the existing app config. The alias resolution in the `resolve` block
26
+ ensures `@` is rewritten to relative paths in the output.
27
+
28
+ ```js
29
+ import dts from 'vite-plugin-dts';
30
+
31
+ // In the build config:
32
+ build: {
33
+ lib: {
34
+ entry: path.resolve(__dirname, 'index.ts'),
35
+ formats: ['es'],
36
+ fileName: 'index',
37
+ },
38
+ outDir: 'dist',
39
+ rollupOptions: {
40
+ // Mark all dependencies as external so they are not bundled
41
+ external: (id) => !id.startsWith('.') && !path.isAbsolute(id),
42
+ },
43
+ },
44
+ plugins: [
45
+ vue(),
46
+ dts({ rollupTypes: true }),
47
+ ],
48
+ ```
49
+
50
+ Run `npm run build` to produce `dist/index.js` and `dist/index.d.ts`.
51
+
52
+ ---
53
+
54
+ ## 3. Update `package.json`
55
+
56
+ Point `exports` to the built output instead of raw source:
57
+
58
+ ```json
59
+ "exports": {
60
+ ".": {
61
+ "types": "./dist/index.d.ts",
62
+ "default": "./dist/index.js"
63
+ }
64
+ },
65
+ "files": ["dist"]
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 4. Development workflow
71
+
72
+ With a built package, `npm link` / `yalc link` alone is no longer enough — consumers need the
73
+ built output, not the source.
74
+
75
+ **Option A — watch build (recommended for active SDK development):**
76
+
77
+ ```sh
78
+ # Terminal 1 — SDK
79
+ npm run build -- --watch
80
+
81
+ # Terminal 2 — frontoffice
82
+ npm run dev
83
+ ```
84
+
85
+ frontoffice picks up changes via the symlink each time the SDK rebuild completes.
86
+ HMR in frontoffice will trigger on the rebuilt file, not on source edits directly.
87
+
88
+ **Option B — yalc push on change:**
89
+
90
+ ```sh
91
+ # In octopus-sdk, after each change:
92
+ npm run build && yalc push
93
+ ```
94
+
95
+ frontoffice receives the new build automatically if `yalc` is configured with `--watch`.
96
+
97
+ ---
98
+
99
+ ## What to revert in frontoffice
100
+
101
+ Once the SDK is built, remove from frontoffice's `vite.config.js`:
102
+
103
+ - `optimizeDeps.exclude: ['@saooti/octopus-sdk']` — no longer needed
104
+ - `server.watch.ignored: ['!**/node_modules/@saooti/octopus-sdk/**']` — no longer needed
105
+ - `server.fs.allow: ['../../../octopus-sdk']` — no longer needed (for npm link setups)
package/CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
- ## 41.10.4 (En cours)
3
+ ## 41.10.6 (27/05/2026)
4
+
5
+ **Fixes**
6
+
7
+ - **144228** - Correction lecture épisodes non publiés sur orga sécurisée
8
+
9
+ ## 41.10.5 (26/05/2026)
10
+
11
+ **Features**
12
+
13
+ - Possibilité de désactiver l'affichage des podcasts dans `EmissionPlayerItem`
14
+
15
+ **Fixes**
16
+
17
+ - **14486** - Tri des saisons sur la page d'émission
18
+
19
+ ## 41.10.4 (13/05/2026)
4
20
 
5
21
  **Fixes**
6
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.10.4",
3
+ "version": "41.10.6",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -12,9 +12,12 @@ import fetchHelper from "../../../helper/fetchHelper";
12
12
  import dayjs from "dayjs";
13
13
  import { FetchParam } from "@/stores/class/general/fetchParam";
14
14
  import { podcastApi } from "../../../api/podcastApi";
15
+ import { Organisation } from "../../../stores/class/general/organisation";
16
+ import { organisationApi } from "../../../api/organisationApi";
17
+ import { OrganisationPrivacy } from "../../../stores/class/securisation/privateOrganisation";
15
18
 
16
19
  export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
17
- const hlsReady= ref(false);
20
+ const hlsReady = ref(false);
18
21
 
19
22
  const { listenTime, onPlay, setDownloadId, onTimeUpdateProgress, playLive, endingLive, playRadio} = usePlayerLive(hlsReady);
20
23
  const { contentEndedAdsLoader } = usePlayerStitching();
@@ -35,6 +38,10 @@ export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
35
38
 
36
39
  watch(()=>getAudioUrl(), async () => {
37
40
  playerError.value = false;
41
+
42
+ // In some cases, the audio does not go through normal download endpoint
43
+ // Mostly when the podcast is not available to users
44
+ let download = true;
38
45
  if (
39
46
  playerStore.playerMedia ||
40
47
  !playerStore.playerPodcast ||
@@ -42,12 +49,32 @@ export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
42
49
  !playerStore.playerPodcast.availability.visibility ||
43
50
  listenError.value
44
51
  ) {
52
+ download = false;
53
+
54
+ // Not yet available podcasts in secured organisations go through
55
+ // download if possible (see #14228)
56
+ const podcast = playerStore.playerPodcast;
57
+ if (podcast && !podcast.availability.visibility) {
58
+ let organisation: Organisation|null = null;
59
+ if (podcast.organisation) {
60
+ organisation = podcast.organisation;
61
+ } else if ('organisationId' in podcast) {
62
+ // Handle case of missing organisation (for example for SimplifiedPodcast)
63
+ organisation = await organisationApi.get(podcast.organisationId as string);
64
+ }
65
+ if (organisation.privacy === OrganisationPrivacy.SECURED) {
66
+ download = true;
67
+ }
68
+ }
69
+ }
70
+
71
+ if (download) {
72
+ const response = await podcastApi.downloadRegister(playerStore.playerPodcast.podcastId, getAudioUrlParameters());
73
+ setDownloadId(response.downloadId.toString());
74
+ audioUrlToPlay.value = response.location;
75
+ } else {
45
76
  audioUrlToPlay.value = getAudioUrl();
46
- return;
47
77
  }
48
- const response = await podcastApi.downloadRegister(playerStore.playerPodcast.podcastId, getAudioUrlParameters());
49
- setDownloadId(response.downloadId.toString());
50
- audioUrlToPlay.value = response.location;
51
78
  });
52
79
 
53
80
  watch(()=>playerStore.playerPodcast, async () => {
@@ -103,6 +103,7 @@ const PodcastPlayBasicButton = defineAsyncComponent(() => import("../podcasts/Po
103
103
  //Props
104
104
  const props = defineProps({
105
105
  emission: { default: () => ({}), type: Object as () => Emission },
106
+ /** Number of podcasts to display (default: 2); if set to 0, no podcasts will be displayed */
106
107
  nbPodcasts: { default: undefined, type: Number },
107
108
  rubriqueName: { default: undefined, type: String },
108
109
  })
@@ -125,6 +126,10 @@ onBeforeMount(()=>loadPodcasts())
125
126
 
126
127
  //Methods
127
128
  async function loadPodcasts(): Promise<void> {
129
+ if (props.nbPodcasts === 0) {
130
+ return;
131
+ }
132
+
128
133
  const nb = props.nbPodcasts ? props.nbPodcasts : 2;
129
134
  const data = await classicApi.fetchData<ListClassicReturn<Podcast>>({
130
135
  api: 0,
@@ -41,11 +41,15 @@
41
41
  v-model:active-tab="activeSeasonTab"
42
42
  :tab-number="emission.seasons.length"
43
43
  >
44
- <template v-for="(season, i) in emission.seasons" #[tabNameSlot(i)]>
44
+ <template v-for="(season, i) in seasons" #[tabNameSlot(i)]>
45
45
  {{ $t('Podcast - Season N', { season }) }}
46
46
  </template>
47
47
 
48
- <template v-for="(season, i) in emission.seasons" #[tabContentSlot(i)] :key="season">
48
+ <template
49
+ v-for="(season, i) in seasons"
50
+ #[tabContentSlot(i)]
51
+ :key="season"
52
+ >
49
53
  <PodcastList
50
54
  class="flex-grow-1"
51
55
  :first="dfirst"
@@ -137,6 +141,8 @@ const showSeasons = computed(() => {
137
141
  return props.emission !== undefined && areSeasonsEnabled(props.emission) && (props.emission.seasons?.length ?? 0) > 0;
138
142
  });
139
143
 
144
+ const seasons = computed((): Array<number> => [...props.emission.seasons].sort());
145
+
140
146
  const activeSeasonTab = ref(showSeasons.value ? props.emission.seasons?.indexOf(getMaxSeason(props.emission)) ?? 0 : 0);
141
147
 
142
148
  const sort = computed((): PodcastSort => {
@@ -11,6 +11,8 @@ import { Chaptering, ChapteringPercent } from "./class/chaptering/chaptering";
11
11
  import classicApi from "../api/classicApi";
12
12
 
13
13
  import { state as sdkParams } from "./ParamSdkStore";
14
+ import { Canal } from "./class/radio/canal";
15
+ import { Conference } from "./class/conference/conference";
14
16
 
15
17
  interface Transcript {
16
18
  actual: number;
@@ -92,10 +94,18 @@ export const usePlayerStore = defineStore("PlayerStore", {
92
94
  return chapteringPercent;
93
95
  },
94
96
  playerHeight() {
95
- if ("STOPPED" === this.playerStatus) return '0px';
96
- if (this.playerVideo) return "0px" /* "281px" */;
97
- if (this.playerLargeVersion) return "27rem";
98
- if (window.innerWidth > 450) return "6rem";
97
+ if ("STOPPED" === this.playerStatus) {
98
+ return '0px';
99
+ }
100
+ if (this.playerVideo) {
101
+ return "0px" /* "281px" */;
102
+ }
103
+ if (this.playerLargeVersion) {
104
+ return "27rem";
105
+ }
106
+ if (window.innerWidth > 450) {
107
+ return "6rem";
108
+ }
99
109
  return "3.5rem";
100
110
  },
101
111
 
@@ -182,19 +192,27 @@ export const usePlayerStore = defineStore("PlayerStore", {
182
192
  * @param param The data
183
193
  * @param isVideo If true, enable video mode
184
194
  */
185
- async playerPlay(param?: any, isVideo = false) {
195
+ async playerPlay(param?: Podcast|Media|Canal|Conference, isVideo = false) {
186
196
  if (!param) {
187
197
  this.stop();
188
198
  return;
189
199
  }
190
200
  if (
191
- (this.playerPodcast &&
201
+ (
202
+ this.playerPodcast &&
203
+ 'podcastId' in param &&
192
204
  this.playerPodcast.podcastId === param.podcastId &&
193
- isVideo === this.playerVideo) ||
194
- (this.playerMedia && this.playerMedia.mediaId === param.mediaId) ||
195
- (this.playerLive &&
205
+ isVideo === this.playerVideo
206
+ ) || (
207
+ this.playerMedia &&
208
+ 'mediaId' in param &&
209
+ this.playerMedia.mediaId === param.mediaId
210
+ ) || (
211
+ this.playerLive &&
212
+ 'conferenceId' in param &&
196
213
  this.playerLive.conferenceId === param.conferenceId &&
197
- isVideo === this.playerVideo)
214
+ isVideo === this.playerVideo
215
+ )
198
216
  ) {
199
217
  //Do nothing
200
218
  return;
@@ -215,6 +233,7 @@ export const usePlayerStore = defineStore("PlayerStore", {
215
233
  this.playerChaptering = undefined;
216
234
 
217
235
  if (
236
+ 'conferenceId' in param &&
218
237
  param.conferenceId &&
219
238
  (!param.podcastId || param.processingStatus !== "READY")
220
239
  ) {
@@ -223,24 +242,28 @@ export const usePlayerStore = defineStore("PlayerStore", {
223
242
  this.playerCurrentChange = null;
224
243
  return;
225
244
  }
226
- if (param.podcastId) {
227
- this.playerPodcast = param;
228
- this.playerCurrentChange = param.podcastId;
229
- if (param.annotations?.chaptering) {
245
+
246
+ if ('podcastId' in param && param.podcastId) {
247
+ const podcast = param as Podcast;
248
+ this.playerPodcast = podcast;
249
+ this.playerCurrentChange = podcast.podcastId;
250
+ if (podcast.annotations?.chaptering) {
230
251
  this.playerChaptering = await classicApi.fetchData<Chaptering>({
231
- api:4,
232
- path:param.annotations.chaptering as string,
233
- isNotAuth:true
252
+ api: 4,
253
+ path: podcast.annotations.chaptering as string,
254
+ isNotAuth: true
234
255
  });
235
256
  }
236
257
  return;
237
258
  }
238
- if (param.mediaId) {
259
+
260
+ if ('mediaId' in param && param.mediaId) {
239
261
  this.playerMedia = param;
240
262
  this.playerCurrentChange = null;
241
263
  return;
242
264
  }
243
- if (param.canalId) {
265
+
266
+ if ('canalId' in param && param.canalId) {
244
267
  this.playerRadio = { ...param, isInit: false };
245
268
  this.playerCurrentChange = -param.canalId;
246
269
  }
@@ -1,5 +1,10 @@
1
1
  import { Person } from "../user/person";
2
2
 
3
+ export enum MonetisationOptions {
4
+ YES = 'YES',
5
+ NO = 'NO'
6
+ }
7
+
3
8
  export type OrganisationAttributes = {
4
9
  //[key: string]: string | number | boolean | undefined;
5
10
  automation?: string;
@@ -13,6 +18,10 @@ export type OrganisationAttributes = {
13
18
  'translation-config'?: string;
14
19
  /** Language of the RSS */
15
20
  'rss-language'?: string;
21
+ /** Monetisation of the organisation */
22
+ MONETISABLE?: MonetisationOptions;
23
+ /** Privacy parameters (JSON) */
24
+ PRIVATE?: string;
16
25
  };
17
26
 
18
27
  export interface Organisation {
@@ -26,7 +35,7 @@ export interface Organisation {
26
35
  longitude: number;
27
36
  latitude: number;
28
37
  };
29
- monetisable?: string;
38
+ monetisable?: MonetisationOptions;
30
39
  name: string;
31
40
  notSeenOnKeycloak?: number;
32
41
  score?: number;
@@ -1,5 +1,11 @@
1
+ export enum OrganisationPrivacy {
2
+ SECURED = 'SECURED',
3
+ PRIVATE = 'PRIVATE',
4
+ PUBLIC = 'PUBLIC'
5
+ }
6
+
1
7
  export interface PrivateOrganisation {
2
- privacy: string; //SECURED PRIVATE PUBLIC
8
+ privacy: OrganisationPrivacy;
3
9
  accountCreation: string; // ENABLED DISABLED
4
10
  referrers: Array<string>;
5
11
  cidrs: Array<string>;
@@ -8,7 +14,7 @@ export interface PrivateOrganisation {
8
14
 
9
15
  export function emptyPrivateOrganisation(): PrivateOrganisation {
10
16
  return {
11
- privacy: "PUBLIC",
17
+ privacy: OrganisationPrivacy.PUBLIC,
12
18
  accountCreation: "DISABLED",
13
19
  referrers: [],
14
20
  cidrs: [],