A tool for viewing and editing Saysimple apps. In this readme you will find how to install and use the devkit and also a tutorial on how to create and develop Saysimple apps
- Saysimple plugins devkit
- Installation of the devKit
- Creation of an app
- Installing the app in the devKit
- Understanding and developing the app
- Usage of the devkit
To create an app, you first need to make sure the devkit is installed and know how to install apps onto it. This way you can see, create, and test your apps before they will be installed on the Saysimple platform.
- NodeJs with a version between
12
and16
,12
is recommended
we recommend nvm if you're running a newer version and need to switch easily between versions - Linux or Mac is recommended, the devKit is not tested on Windows
git clone https://github.com/saysimple/saysimple-plugin-devkit.git
After a Git clone you need to copy the package.dist.json and src/saysimpleApps.dist.ts to files without the dist
cd ./saysimple-plugin-devkit
cp package.dist.json package.json
cp src/saysimpleApps.dist.ts src/saysimpleApps.ts
To complete the installation you need to run a npm install
npm i
To start the environment you have to run
npm start
After you've run the above command you can access the devkit by going to http://localhost:3000 in a browser
A Saysimple app is a VueJs component that is loaded inside the Saysimple platform for users to use.
To create an app, you first need to pull from an existing app. There are 2 packages you can pick from initially: the skeleton app or the example app.
If you want to start developing immediately without extra code, you can pull from the skeleton app. If you want to see how everything works in action and play around with it, you can pull and use the example app
Install the skeleton app
git clone https://github.com/saysimple/skeleton-plugin.git
Or install the example app
git clone https://github.com/saysimple/example-plugin.git
After the installation of the existing app, you can make the following changes to the package.json
to fully customize
the existing app to create your own Saysimple app
Change example in main
and module
to your plugin name
change
"main": "dist/example.esm.js",
"module": "dist/example.esm.js",
to
"main": "dist/<your-app-name>.esm.js",
"module": "dist/<your-app-name>.esm.js",
The Saysimple object consists of the data the Saysimple platform will use to install and display the plugin. The object looks like this
{
"saysimple": {
"name": "App name",
"description": "A short description of the app",
"dataRequired": [
"contact",
"agent"
]
}
}
The name
and description
are shown to the end user to display your app.
Data Required is an array of strings that notifies what kind of data will be sent to your app by the Saysimple platform.
The array can exist of the following data types
- contact
The user that sent a message to Saysimple. - contact_metadata
Custom data about the contact, this can be different for every customer. - agent
The agent that is currently logged in. - assignedAgent
The agent the current conversation is assigned to. - conversation
The conversation that is opened by the agent. - messages
Messages that are sent in the current conversation. - tags
The tags that are assigned to the current
The icon file is located inside the assets
folder and is named icon.png. If your icon has another extension, or you
need to change its name, you can edit the icon location inside the icon.js
file.
import iconFile from "../assets/icon.png" // <--- change this to your icon location
export const icon = iconFile
First you need to install your app via npm
npm i <app>
You can also install the app from location if your app is not yet in the npm registry
npm i ../location/to/app
Make sure your app is built while installing it this way
After the installation you need to register the app to see the app in the devKit. This can be done by modifying
the src/saysimpleApps.ts
file.
Import your app and add it to the
exported saysimpleApps
object inside the file.
import yourAppName from "<yourAppNpmName>";
export const saysimpleApps: Record<string, NpmAppInterface> = {
yourAppName,
};
You can add as many plugins to this list, and you will see them all in a list in the devKit.
If you have the app installed and want to develop in real time, you first need to make sure that the following packages are not installed or are listed as peer dependencies:
- vue
- @vue/composition-api
- vue-i18n
They are inherited from the Saysimple platform and the devkit will break if you try to develop while they are installed.
Then you have to create a symlink to develop in realtime. To do so, go to the directory of your app and run
npm link
After you've done that you need to go to the directory of the Saysimple devkit
npm link <npmName of app>
Once that is done you have to start the watcher in the app so it will rebuild after you make changes
npm run watch
Finally restart the Saysimple devkit and you should be able to develop in realtime. This works for all packages that are installed locally and from npm.
You need to restart the devKit if changes in the package.json are made.
Every app needs at least 2 components settings
and content
. The settings component will be rendered when a user
clicks on app settings. The content will be rendered if the end user selects your app when in an active conversation.
Both the settings and the content components receive 2 props: app
and appData
. With these, you can interact with the
Saysimple platform.
The appData is a prop delivered to your app settings and content components. It contains information about the current instance of the Saysimple platform.
You have to request AppData in the dataRequired
array inside the package.json
otherwise the data won't be delivered.
Below are the possible attributes of the appData.
The contact exists out of the following attributes:
interface contact {
id: number;
name: string;
countryCode?: string;
email?: string;
phone?: string;
street?: string;
houseNumber?: string;
zipCode?: string;
city?: string;
province?: string;
country?: string;
reference?: string;
company?: string;
jobTitle?: string;
isHighPriority?: number;
createdAt?: Date;
isBlocked?: number;
}
If you have content_metadata
enabled in dataRequired
you also get access to a metadata
attribute inside
the contacts
.
This is an array
that looks like this:
interface metadata {
id: number,
name: string,
value: string
}
[]
If you don't have access to the contact you don't get the metadata.
The agent exists out of the following attributes:
interface user {
id: number;
name?: string;
displayName?: string;
email?: string;
}
The assigned agent looks exactly the same as the normal agent.
The conversation exists out of the following attributes:
interface conversation {
start: Date;
status: "open" | "closed";
}
Messages is an array that exists out of the following attributes:
interface message {
direction: "in" | "out";
time: Date;
status: string;
message: string;
}
Tags is a string array.
The app is a prop delivered to your app settings and content components it contains information about your app and utilities to interact with the Saysimple platform.
The app object always looks like this:
interface app {
id: number,
enabled: number,
name: string,
package: {
name: string,
version: string,
saysimple: Saysimle // Same as the saysimple in package.json
},
utils: Utils // object with functions,
components: {
settings: VueComponent,
content: VueComponent,
},
settings: Record<string, any> // Your app settings,
}
The id
, enabled
, and components
are mainly for the Saysimple platform to render your app properly.
The name
is the same as the name in package.json
.
The package also contains data from the package.json
.
The settings
are the settings of the app.
utils
are useful functions to interact with an app or make api calls.
You can read more about them in the next chapter.
Saysimple gives you a few utility functions.
They are available in the utils
attribute inside the app
prop.
The utils contain the following functions:
This will append a message inside the message editor in the Saysimple platform.
You will receive an alert with the appended message if you're using the devkit.
input
A string
that will be the message that is appended.
output
This function does not return anything.
usage
utils.appendToMessage("message to append")
This is a Vue i18n plugin you can use inside a composable. You can read more about translation in the translations chapter.
This function calls a vue toastification element, styled the same way as a Saysimple notification would.
input
- The first attribute is a
string
that will be the title. - The second attribute is an optional
string
that can be"success"
,"error"
,"warning"
, or"info"
. This is the type of the notification. - If nothing is given it will fall back to
"success"
.
output
This function does not return anything.
usage
utils.notify("Oh no something went wrong", "error")
This function will make an http request with axios via the Saysimple backend as a proxy. Please use this function instead of your own request to avoid CORS issues.
input
- The same as if you called
axios()
, see this example. - You can also supply a return type if you're using typescript.
output
A promise
of the result of the http request.
usage
utils.apiCall<DataInterface>({
url: "https://example.com/api/some-location",
method: "POST",
headers: {
authorizarion: app.settings.apiKey
},
data: {
foo: "bar"
},
params: {
hi: "mom"
}
})
/* Returns
{
hello: "world",
number: 1337
}
*/
This is just an example with placeholders, it won't work if you try it.
For internal Saysimple use only.
Request a single setting with a fallback if it does not exist.
input
- The first argument is a
string
. This will be the path of the setting. - The second argument is optional. This will be the default value if the setting does not exist.
- You can supply the setting type if you're using typescript.
output
This function returns the setting if it exists, otherwise it will fall back to the default value
usage
// settings
{
existingSetting: 1337
}
utils.getSetting<number>("existingSetting", 45) // 1337
utils.getSetting<number>("notExistingSetting", 45) // 45
This is a way to save settings to the Saysimple database.
You can retrieve them again with the getSetting
util or in
the settings
attribute of the app prop.
input
The input is an object with all the settings you want to save.
output
The function returns an empty promise.
usage
utils.saveSettings({
existingSetting: 1337,
anotherSetting: "cool",
words: ["hello", "world"]
})
Send an email to the address inside the toEmailAddress
setting.
input
The input is a string with the content of the email.
Keep in mind that this also requires a toEmailAddress setting.
output
An axios response
usage
utils.saveSettings({
toEmailAddress: "info@example.com"
})
utils.sendEmail(
`<b>The content of an email</b>`
)
This is deprecated; please use saveStorage instead.
A way to save additional data that aren't settings.
input
- The first element is a
string
that will be the name of the data. - The second element will be the value of the data.
output
An empty promise.
usage
utils.saveData(
`userProfile:${contact.id}`,
{
externalId: yourApp.id,
success: true
}
)
This is deprecated; please use getStorage instead.
A way to retrieve additional data that was saved before.
input
- The first element is a
string
that will be the name of the data. - The second is optional and will be the fallback if the name is not set.
- You can supply the setting type if you're using typescript.
output
An empty promise.
usage
utils.getData<UserProfile>("userProfile:77", { success: false })
/*
{
externalId: yourApp.id,
success: true
}
*/
utils.getData<UserProfile>("unkown", { success: false }) // {success: false}
This function scrolls to the top of the conversation.
input
This function does not require an input.
output
This function does not require an output.
usage
utils.scrollToTop()
This is a function used internally by Saysimple. Please do not touch this - doing so has the risk of breaking the emit and toast functionality.
Saysimple uses i18n
and the vue-i18n plugin for translation, at this moment we
support 3 languages:
- English (en)
- Spanish (es)
- Dutch (nl)
We make it possible for you to translate your app to these 3 languages. Your app will be shown to the user in the language they've selected.
We highly recommend you supply translations in all languages, but English is the only one which is absolutely mandatory and also the fallback if one of the other languages is not supplied.
To create translations, go to the locales.js
. This will be an object with the language codes in the root,
and they possess translation objects for example:
export const locales = {
en: {
settings: {
active: "Active",
save: "Save",
"save-success-title": "Settings saved",
"save-error-title": "Error saving settings",
name: "Name",
phone: "Phone number"
},
location: {
house: "House",
address: "Address",
street: "Street",
city: "City"
}
},
nl: {
settings: {
active: "Actief",
save: "Opslaan",
"save-success-title": "Settings opgeslagen",
"save-error-title": "Fout bij opslaan settings",
name: "Naam",
phone: "Telefoonnummer"
},
location: {
house: "Huis",
address: "Adres",
street: "Straat",
city: "Stad"
}
},
es: {
settings: {
active: "Activo",
save: "Ahorrar",
"save-success-title": "Ajustes guardados",
"save-error-title": "Error al guardar la configuración",
name: "Nombre",
phone: "Número de teléfono"
},
location: {
house: "Casa",
address: "Dirección",
street: "Calle",
city: "Ciudad"
}
},
};
If you want to use your translation it is a bit less straight forward. Normally if you want to translate with i18n you do something like this:
<p> {{ $t("location.house") }} </p>
But in our platform, you have to add it with a prefix @app/<your-app-translation-key>
. The plugin translation key is
your app name in kebab case, so if the app name is myAwesomeApp
the translation key would be my-awesome-app
.
So for the example app it would be
<p> {{ $t("@app/example.location.house") }}</p>
Don't forget to change example
to your app translation key.
There is also a way to get translations inside the composition api.
We supply a vue i18n class with all your translations inside
the app.utils.i18n
. It would work something like this:
export default {
setup(props) {
const $t = props.app.utils.i18n.t;
const $notify = props.app.utils.notify;
const onButtonClick = () => {
$notify({
$t("@app/example.settings.save-success-title"),
"success"
})
}
}
}
And again, please don't forget to change example to your app translation key.
We use fontAwesome as our iconset and make it available to all apps.
If you want to use an fontAwesome icon you have to register it.
You can do that inside the faIcons.js
files.
There you'll find that it exports an array with all the icons something like this:
import { faFaceSmile } from "@fortawesome/free-solid-svg-icons"
export const faIcons = [
faFaceSmile
]
You can add your icons in this array and Saysimple will install them for you.
You can add icons from these packages
- fontawesome-pro
- free-brands-svg-icons
- free-regular-svg-icons
- free-solid-svg-icons
- pro-duotone-svg-icons
- pro-light-svg-icons
- pro-regular-svg-icons
- pro-solid-svg-icons
Please register all icons you use, even if you're certain the icon is already imported. If you don't register the icon, there might be changes in the platform where it is possible your icon might be lost.
Only the free icons are installed by default. This is because you'll need a fontAwesome pro license to use fontAwesome pro. Our Saysimple frontend has a pro license installed, but if you want to be able to use pro icons in your plugin you'll need to have a license yourself.
To use pro icons in your plugin, you need to configure your access in the devkit package (You don't have to do this in your plugin itself). Follow these steps in fontAwesome to configure your access.
You can install the icon packages you need inside the devkit after your access is configured.
npm install pro-solid-svg-icons
When that’s complete, you can export pro icons in your plugin just like the free icons:
import { faFaceSmile } from "@fortawesome/free-solid-svg-icons"
import { faFaceSmileBeam } from "@fortawesome/pro-solid-svg-icons"
export const faIcons = [
faFaceSmile,
faFaceSmileBeam
]
If you're using assets, you will have to place them inside the assets
folder. Then you can make a reference like this:
<div>
<img src="../assets/your-image.jpg" />
</div>
If you need assets programmatically, you can use something like this:
import image1 from "../assets/image1.jpg"
import image2 from "../assets/image2.jpg"
import image3 from "../assets/image3.jpg"
Assets are converted in base64, so please be aware of the size.
The devkit is a great way to debug and test your plugin, but you do need to know how to use it.
When opening the devkit, you'll notice 3 important elements: The navbar, the settings page, and the viewer page. You can use these components to tests your app and mock Saysimple interactions.
The navbar is always visible on top of the page. The left side contains the Saysimple logo and the right side contains 3 handy functions.
The first is the language. You can control the language of the app with this. When you click this, you get a dropdown and you can select a language. This will only change the language of the app and not the devkit.
The second item is the plugin select. You'll see the name of the current plugin (example when you first open the devkit). When clicked you'll see all the installed plugins and you can switch between apps.
The third and last item is a refresh button. When clicked it will refresh the app while leaving the devkit the rest of the devKit untouched.
The settings page will show you how your app will look inside the settings page from Saysimple. When opened you'll see 2 components.
The one on the left is a preview of how your app will be displayed on the app selection page.
The component on the right will display the content of your settings.vue inside your app, this is what will be displayed inside the Saysimple settings page. When you save your settings (using the saveSettings util), the data will be saved inside the localStorage and will also be given to the overview page, so you can test with the settings you selected.
You can use this page to debug your app itself. On the left you can see the controls (more on that in the next chapter), and on the right you can see your app's content.vue component. It will be populated with appData which you can configure in the controls and settings which are found on the settings page.
Inside the viewer page of the Devkit you'll find a few components where you can manipulate the AppData going in to the app. This way you can manipulate the devkit to mock the Saysimple platform and see how your app will react.
The first 4 control items are about manipulating appData
- contact
- agent
- assignedAgent
- chat
They all contain a dropdown where you can select which data goes to your app. You can select a few presets and a random option. You can learn how to create your own preset in the next chapter.
Some control items may be grayed out which means that you don't have the right data enabled inside your dataRequired inside the package.json. Read more about that here.
Presets are .json
files located in the /src/data
directory of the Devkit.
You'll see 4 subdirectories:
- chats
which contains presets for the chat controller, the structure looks like this:interface chat { "conversation": { "start": DateString; // example: "2022-09-19T13:25:03.390Z" "status": "open" | "closed"; }, "messages": { "direction": "in" | "out"; "time": DateString; // example: "2022-09-19T13:25:03.390Z"; "status": "rejected" | "pending" | "sent" | "received" | "read" | "none"; "message": string; }[], "tags": string[]; }
- users
which contains presets for the agent and assignedAgent controller, the structure looks like this:interface user { "name": string, "displayName": string, "email": string }
- contacts
which contains presets for the contact controller, the structure looks like this:interface contacts { "name": string; "countryCode"?: string; "email"?: string; "phone"?: string; "street"?: string; "houseNumber"?: string; "zipCode"?: string; "city"?: string; "province"?: string; "country"?: string; "reference"?: string; "company"?: string; "jobTitle"?: string; "isHighPriority"?: 0 | 1; "createdAt"?: DateString; // example: "2022-09-19T13:25:03.390Z"; "isBlocked"?: 0 | 1; "metadata"?: { "id": number, "name": string, "value": any }[] }
- channels
which contains presets for the channels controller, the structure looks like this:interface channels { name: string; provider: string; platform: string; identity: string; auth?: string; active: 1 | 0; }
If you want to create your own preset, you have to create a new json file in the subfolder according to the type above. The name of the file will return as the name of the preset.
You can copy an existing preset and change the data, or you can create a preset from scratch according to the structure above. Please be careful that the structure of your preset matches the structure of the data itself. There is no check if the preset is correct and if it's not you may be relying on data that will be different in the Saysimple platform.
When creating a preset you might want to use random data.
You can do that by adding the string [RANDOM]
to your preset at the value you want randomized.
So if your app relies on different email address and you want a random address everytime, you can
do email: "[RANDOM]"
.
Random data will be generated using Faker and will be different every time you use the refresh button. Random data is unique per preset and it will only be regenerated after the refresh button is pressed.
So if 2 users have a random email field they will have 2 different email addresses, but you can switch between the users and they keep their address, until you hit the refresh button in the navbar, then both users get a different address.
The "[RANDOM]"
string can be used for most fields and will be given sensible data according to the field type. For
example city: [RANDOM]
will generate a random city, and name: [RANDOM]
will return a random name.
Random data will also look at surrounding data for example if you have the name set like this name: "Walter White"
and
you have a random email in your preset it will return random email like walter.white@gmail.com
,
and w.white@hotmail.com
.
The randomizer also looks at the countryCode
field, if you have countryCode: "DE"
in your preset it would return
German data. For example city: [RANDOM]
would return cities like Bremmen
or Berlin
, and fullName: [RANDOM]
would
return names like Lillian Pöge
and Merle Büttner
.
If the data is not clearly randomised you can supply the random method in your field like
this "[RANDOM:category:function]"
. To get the category and function you have to look at
the Faker api documentation.
So for example "[RANDOM:animal:cat]"
would return a random cat species
and "[RANDOM:music:songName]"
would return a random song name.
There are also 3 other control items.
- App info
- Settings
- App Data
They are here to give you more information of the app. Data is displayed in raw JSON to let you know exactly what is given to the app.
This contains the info that is given from your app to the platform package.json
. You can check if everything is
given as expected.
These are the settings saved by the settings page.
This is the AppData object that is given to your app. You can use it to quickly check what kind of data is given and if your dataRequired is set up as expected.