Skip to content

Commit

Permalink
🔀 Merge pull request #1553 from jovotech/v4/dev
Browse files Browse the repository at this point in the history
🔖 Prepare latest release
  • Loading branch information
jankoenig authored May 4, 2023
2 parents b853afb + cf81ac4 commit d64b821
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 33 deletions.
7 changes: 6 additions & 1 deletion framework/src/plugins/RoutingExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ export class RoutingExecutor {
i < lastRouteMatchIndexWithPrioritizedOverUnhandled;
i++
) {
rankedRouteMatches[i].skip = true;
if (
rankedRouteMatches[i].type === BuiltInHandler.Unhandled ||
!rankedRouteMatches[i].prioritizedOverUnhandled
) {
rankedRouteMatches[i].skip = true;
}
}
}

Expand Down
77 changes: 76 additions & 1 deletion framework/test/Routing.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { App, BaseComponent, Component, Global, InputType, Intents } from '../src';
import {
App,
BaseComponent,
Component,
Global,
InputType,
Intents,
PrioritizedOverUnhandled,
} from '../src';
import { ExamplePlatform, ExampleServer } from './utilities';

test('test handler decorator inheritance', async () => {
Expand Down Expand Up @@ -105,3 +113,70 @@ test('test handler decorator inheritance', async () => {
},
]);
});

test('test prioritized handlers not being skipped', async () => {
@Component({ name: 'BottomComponent' })
class BottomComponent extends BaseComponent {
@PrioritizedOverUnhandled()
@Intents('IntentA')
handleIntentA() {
return this.$send('BottomComponent.IntentA');
}
}

@Component({ name: 'MiddleComponent' })
class MiddleComponent extends BaseComponent {
@PrioritizedOverUnhandled()
@Intents('IntentA')
handleIntentA() {
return this.$send('MiddleComponent.IntentA');
}

UNHANDLED() {
return this.$send('MiddleComponent.UNHANDLED');
}
}

@Component({ name: 'TopComponent' })
class TopComponent extends BaseComponent {
UNHANDLED() {
return this.$send('TopComponent.UNHANDLED');
}
}

const app = new App({
plugins: [new ExamplePlatform()],
components: [BottomComponent, MiddleComponent, TopComponent],
});
await app.initialize();

const server = new ExampleServer({
input: {
type: InputType.Intent,
intent: 'IntentA',
},
session: {
data: {
state: [
{
component: 'BottomComponent',
},
{
component: 'MiddleComponent',
resolve: {},
},
{
component: 'TopComponent',
resolve: {},
},
],
},
},
});
await app.handle(server);
expect(server.response.output).toEqual([
{
message: 'MiddleComponent.IntentA',
},
]);
});
24 changes: 11 additions & 13 deletions platforms/platform-alexa/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,19 +570,17 @@ You can use Jovo with [Alexa Skill Connections](https://developer.amazon.com/doc

For this, the Jovo Alexa integration offers convenience [output classes](https://www.jovo.tech/docs/output-classes). Below is an overview of all classes:

| Class | URI |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [`ConnectionAskForPermissionConsentOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionAskForPermissionConsentOutput.ts) | `connection://AMAZON.AskForPermissionsConsent/2` |
| [`ConnectionLinkAppOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionLinkAppOutput.ts) | `connection://AMAZON.LinkApp/2` |
| [`ConnectionPrintImageOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionPrintImageOutput.ts) | `connection://AMAZON.PrintImage/1` |
| [`ConnectionPrintPdfOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionPrintPdfOutput.ts) | `connection://AMAZON.PrintPDF/1` |
| [`ConnectionPrintWebPageOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionPrintWebPageOutput.ts) | `connection://AMAZON.PrintWebPage/1` |
| [`ConnectionScheduleFoodEstablishmentReservationOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionScheduleFoodEstablishmentReservationOutput.ts) | `connection://AMAZON.ScheduleFoodEstablishmentReservation/1` |
| [`ConnectionScheduleTaxiReservationOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionScheduleTaxiReservationOutput.ts) | `connection://AMAZON.ScheduleTaxiReservation/1` |
| [`ConnectionTestStatusCodeOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionTestStatusCodeOutput.ts) | `connection://AMAZON.TestStatusCode/1` |
| [`ConnectionVerifyPersonOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionVerifyPersonOutput.ts) | `connection://AMAZON.VerifyPerson/2` |
| [`ConnectionAddToShoppingCartOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionAddToShoppingCartOutput.ts) | `connection://AMAZON.AddToShoppingCart/1` |
| [`ConnectionBuyShoppingProductsOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionBuyShoppingProductsOutput.ts) | `connection://AMAZON.BuyShoppingProducts/1` |
- [`ConnectionAskForPermissionConsentOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionAskForPermissionConsentOutput.ts)
- [`ConnectionLinkAppOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionLinkAppOutput.ts)
- [`ConnectionPrintImageOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionPrintImageOutput.ts)
- [`ConnectionPrintPdfOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionPrintPdfOutput.ts)
- [`ConnectionPrintWebPageOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionPrintWebPageOutput.ts)
- [`ConnectionScheduleFoodEstablishmentReservationOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionScheduleFoodEstablishmentReservationOutput.ts)
- [`ConnectionScheduleTaxiReservationOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionScheduleTaxiReservationOutput.ts)
- [`ConnectionTestStatusCodeOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionTestStatusCodeOutput.ts)
- [`ConnectionVerifyPersonOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionVerifyPersonOutput.ts)
- [`ConnectionAddToShoppingCartOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionAddToShoppingCartOutput.ts)
- [`ConnectionBuyShoppingProductsOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionBuyShoppingProductsOutput.ts)

You can find the output options in each class implementation. For example, you use the [`ConnectionAskForPermissionConsentOutput`](https://github.com/jovotech/jovo-framework/tree/v4/latest/platforms/platform-alexa/src/output/templates/ConnectionAskForPermissionConsentOutput.ts) like this:

Expand Down
21 changes: 21 additions & 0 deletions platforms/platform-alexa/docs/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,27 @@ You can add an APL RenderDocument directive ([see official Alexa docs](https://d
}
```

Alternatively you could also use the [`AplRenderDocumentOutput`](https://github.com/jovotech/jovo-framework/blob/v4/latest/platforms/platform-alexa/src/output/templates/AplRenderDocumentOutput.ts) [class](#alexa-output-classes) provided by Jovo, which will wrap your data in a directive in the response for you:

```typescript
import { AplRenderDocumentOutput } from '@jovotech/platform-alexa';
// ...

someHandler() {
// ...

return this.$send(AplRenderDocumentOutput, {
token: '<some-token>',
document: {
/* ... */
},
datasources: {
/* ... */
},
});
}
```

Learn more about APL in the following sections:

- [APL Configuration](#apl-configuration): How to enable APL for your Alexa Skill
Expand Down
52 changes: 52 additions & 0 deletions platforms/platform-alexa/docs/project-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,58 @@ new AlexaCli({
});
```

In combination with the [`files` property](#files), you can use the generic `locales` to populate publishing and privacy & compliance information in the Alexa Skill Manifest:

```js
new AlexaCli({
locales: {
en: ['en-US', 'en-GB', 'en-IN'],
de: ['de-DE'],
},
files: {
'skill-package/skill.json': {
manifest: {
publishingInformation: {
locales: {
en: {
info: "All EN locales have this"
},
de: {
info: "All DE locales have this"
}
}
},
privacyAndCompliance: {
locales: {
'de': {
privacyPolicyUrl: 'https://test.com/de/datenschutz/',
termsOfUseUrl: 'https://test.com/de/agb/',
},
// Will be applied to en-US and en-IN
'en': {
privacyPolicyUrl: 'https://test.com/en/privacy/',
termsOfUseUrl: 'https://test.com/en/tos/',
},
// It's still possible to add locale-specific information like below
'en-GB': {
privacyPolicyUrl: 'https://test-au.com/en/privacy-australia/',
termsOfUseUrl: 'https://test-au.com/en/tos-australia/',
},
},
allowsPurchases: false,
containsAds: false,
isChildDirected: false,
isExportCompliant: true,
usesPersonalInfo: false,
},
},
},
}
// ...
});
```


## skillId

The first time you run the [`deploy` command](./cli-commands.md#deploy) for a new project, a new Alexa Skill project with a new ID is created in the [Alexa Developer Console](https://developer.amazon.com/alexa/console/ask#/).
Expand Down
3 changes: 3 additions & 0 deletions platforms/platform-alexa/src/AlexaRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ export class AlexaRequest extends JovoRequest {
if (supportedInterfaces['Alexa.Presentation.APL']) {
capabilities.push(Capability.Screen, AlexaCapability.Apl);
}
if (supportedInterfaces.VideoApp) {
capabilities.push(Capability.Video);
}
return capabilities;
}

Expand Down
71 changes: 53 additions & 18 deletions platforms/platform-alexa/src/cli/hooks/BuildHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,38 +438,73 @@ export class BuildHook extends AlexaHook<BuildPlatformEvents> {
_set(projectFiles, skillIdPath, skillId);
}

this.checkLocales(projectFiles);

FileBuilder.buildDirectory(projectFiles, this.$plugin.platformPath);
}

checkLocales(projectFiles: FileObject): void {
const skillName: string = this.$cli.project!.getProjectName();
const locales: string[] = this.$context.locales.reduce((locales: string[], locale: string) => {
locales.push(...getResolvedLocales(locale, SupportedLocales, this.$plugin.config.locales));
return locales;
}, []);

const privacyAndCompliances = _get(
projectFiles,
`skill-package/["skill.json"].manifest.privacyAndCompliance.locales`,
) as FileObject;
const publishingInfos = _get(
projectFiles,
`skill-package/["skill.json"].manifest.publishingInformation.locales`,
) as FileObject;

for (const locale of locales) {
const genericLocaleKey = Object.keys(this.$plugin.config.locales || {}).find((key) =>
([...this.$plugin.config.locales![key]!] as string[]).includes(locale),
);

// Check whether publishing information has already been set.
const publishingInformationPath = `skill-package/["skill.json"].manifest.publishingInformation.locales.${locale}`;
if (!_has(projectFiles, publishingInformationPath)) {
_set(projectFiles, publishingInformationPath, {
summary: 'Sample Short Description',
examplePhrases: ['Alexa open hello world'],
keywords: ['hello', 'world'],
name: skillName,
description: 'Sample Full Description',
smallIconUri: 'https://via.placeholder.com/108/09f/09f.png',
largeIconUri: 'https://via.placeholder.com/512/09f/09f.png',
});
if (!_has(publishingInfos, locale)) {
const fallback = genericLocaleKey ? _get(publishingInfos, genericLocaleKey) : undefined;
_set(
publishingInfos,
locale,
fallback || {
summary: 'Sample Short Description',
examplePhrases: ['Alexa open hello world'],
keywords: ['hello', 'world'],
name: skillName,
description: 'Sample Full Description',
smallIconUri: 'https://via.placeholder.com/108/09f/09f.png',
largeIconUri: 'https://via.placeholder.com/512/09f/09f.png',
},
);
}

const privacyAndCompliancePath = `skill-package/["skill.json"].manifest.privacyAndCompliance.locales.${locale}`;
// Check whether privacy and compliance information has already been set.
if (!_has(projectFiles, privacyAndCompliancePath)) {
_set(projectFiles, privacyAndCompliancePath, {
privacyPolicyUrl: 'http://example.com/policy',
termsOfUseUrl: '',
});
if (!_has(privacyAndCompliances, locale)) {
const fallback = genericLocaleKey
? _get(privacyAndCompliances, genericLocaleKey)
: undefined;
_set(
privacyAndCompliances,
locale,
fallback || {
privacyPolicyUrl: 'http://example.com/policy',
termsOfUseUrl: '',
},
);
}
}

FileBuilder.buildDirectory(projectFiles, this.$plugin.platformPath);
// clear generic locales from projectFiles
for (const key of Object.keys(privacyAndCompliances)) {
if (!locales.includes(key)) delete privacyAndCompliances[key];
}
for (const key of Object.keys(publishingInfos)) {
if (!locales.includes(key)) delete publishingInfos[key];
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Output, BaseOutput, OutputTemplate, OutputOptions } from '@jovotech/framework';
import { AplRenderDocumentDirective } from '../models';

interface AplRenderDocumentOutputOptions extends OutputOptions {
document: AplRenderDocumentDirective['document'];
sources?: AplRenderDocumentDirective['sources'];
datasources?: AplRenderDocumentDirective['datasources'];
token?: AplRenderDocumentDirective['token'];
}

@Output()
export class AplRenderDocumentOutput extends BaseOutput<AplRenderDocumentOutputOptions> {
async build(): Promise<OutputTemplate> {
const aplRenderDocDirective: AplRenderDocumentDirective = {
type: 'Alexa.Presentation.APL.RenderDocument',
document: this.options.document,
datasources: this.options.datasources,
sources: this.options.sources,
token: this.options.token || 'token',
};

return {
platforms: {
alexa: {
nativeResponse: {
response: {
directives: [aplRenderDocDirective],
},
},
},
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Output, BaseOutput, OutputTemplate, OutputOptions } from '@jovotech/framework';
import { VideoAppLaunchDirective } from '../models';

interface VideoAppLaunchOutputOptions extends OutputOptions {
videoItem: VideoAppLaunchDirective['videoItem'];
}

@Output()
export class VideoAppLaunchOutput extends BaseOutput<VideoAppLaunchOutputOptions> {
async build(): Promise<OutputTemplate> {
const videoAppLaunchDirective: VideoAppLaunchDirective = {
type: 'VideoApp.Launch',
videoItem: this.options.videoItem,
};

return {
platforms: {
alexa: {
nativeResponse: {
response: {
directives: [videoAppLaunchDirective],
// Responses containing a VideoApp.Launch Directive must not have a shouldEndSession parameter
shouldEndSession: undefined,
},
},
},
},
};
}
}
4 changes: 4 additions & 0 deletions platforms/platform-alexa/src/output/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export * from './LinkAccountCardOutput';
export * from './AudioPlayerPlayOutput';
export * from './AudioPlayerStopOutput';

export * from './VideoAppLaunchOutput';

export * from './ConnectionAddToShoppingCartOutput';
export * from './ConnectionAskForPermissionConsentOutput';
export * from './ConnectionBuyShoppingProductsOutput';
Expand All @@ -26,3 +28,5 @@ export * from './ConnectionVerifyPersonOutput';
export * from './ConnectionScheduleFoodEstablishmentReservationOutput';
export * from './ProgressiveResponseOutput';
export * from './CanFulfillIntentOutput';

export * from './AplRenderDocumentOutput';

0 comments on commit d64b821

Please sign in to comment.